From 0e8fcd59cc3140cb31eb83ccec6d9425ebdddebf Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 22 Jun 2015 16:02:28 +0000 Subject: [PATCH 001/183] initial commit, exit if tor is not running. --- usr/lib/sdwdate-python/sdwdate_python | 166 ++++++++++++++++++++++ usr/lib/sdwdate-python/url_to_unixtime.py | 164 +++++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100755 usr/lib/sdwdate-python/sdwdate_python create mode 100644 usr/lib/sdwdate-python/url_to_unixtime.py diff --git a/usr/lib/sdwdate-python/sdwdate_python b/usr/lib/sdwdate-python/sdwdate_python new file mode 100755 index 00000000..1a01e7b4 --- /dev/null +++ b/usr/lib/sdwdate-python/sdwdate_python @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +import sys +from url_to_unixtime import url_to_unixtime +import time, random + +#import config + +pool_one = ['dtsxnd3ykn32ywv6.onion', + 'znig4bc5rlwyj4mz.onion', + 'vtjkwwcq5osuo6uq.onion', + '33y6fjyhs3phzfjj.onion', + 'y6xjgkgwj47us5ca.onion', + 'strngbxhwyuu37a3.onion', + 'swdi5ymnwmrqhycl.onion', + 'dqeasamlf3jld2kz.onion', + 'pubdrop4dw6rk3aq.onion', + 'hkjpnjbvhrxjvikd.onion', + 'v6gdwmm7ed4oifvd.onion', + 'vbmwh445kf3fs2v4.onion', + 'poulsensqiv6ocq4.onion', + 'tigas3l7uusztiqu.onion', + 'w6csjytbrl273che.onion', + 'ak2uqfavwgmjrvtu.onion'] + +pool_two = ['yn6ocmvu4ok3k3al.onion', + 'acabtd4btrxjjrvr.onion', + '5r4bjnjug3apqdii.onion', + '2dermafialks7aai.onion', + 'ymi7h25hgp3bj63v.onion', + 'ppdz5djzpo3w5k2z.onion', + 'pltloztihmfrg2sw.onion', + 'ur5b2b4brz427ygh.onion', + 'abkjckdgoabr7bmm.onion', + 'bqs3dobnazs7h4u4.onion', + 'fkut2p37apcg6l7f.onion', + '6iolddfbfinntq2b.onion', + 'nzh3fv6jc6jskki3.onion'] + +pool_three = ['cwoiopiifrlzcuos.onion', + 'zsolxunfmbfuq7wf.onion', + 'yfm6sdhnfbulplsw.onion', + 'j6uhdvbhz74oefxf.onion', + '3g2upl4pq6kufc4m.onion', + 'dju2peblv7upfz3q.onion', + 'msydqstlz2kzerdg.onion', + 'uj3wazyk5u4hnvtk.onion', + 'wi7qkxyrdpu5cmvr.onion', + 'ic6au7wa3f6naxjq.onion', + 'timaq4ygg2iegci7.onion', + '344c6kbnjnljjzlz.onion', + 'fncuwbiisyh6ak3i.onion'] + +returned_unixtimes = 0 + +already_picked_index_pool_one = [] +already_picked_index_pool_two = [] +already_picked_index_pool_three = [] +already_picked_index_dummy_pool = [] + +pool_one_done = False +pool_two_done = False +pool_three_done = False +dummy_pool_done = False +url_random_pool_one = [] +url_random_pool_two = [] +url_random_pool_three = [] +url_random_dummy_pool = [] + +print 'Start %s' % (time.time()) + +while returned_unixtimes < 3: + urls = [] + unixtimes = [] + url_random = [] + + random.seed() + + url_index = [] + + if not pool_one_done: + while True: + ## Pick a random pool index. + url_index = random.sample(xrange(len(pool_one)), 1) + + ## Reset to prevent infinite loop, next condition would never be met, + ## probably raise an error before that happens. + if len(already_picked_index_pool_one) == len(pool_one): + already_picked_index_pool_one = [] + url_random_pool_one = [] + + ## Was this index used? + if url_index not in already_picked_index_pool_one: + ## No, add to used indexes. + already_picked_index_pool_one.append(url_index) + ## Add it to the pool url list. + url_random_pool_one.append(pool_one[url_index[0]]) + ## Add it to the url list to fetch. + url_random.append(pool_one[url_index[0]]) + break + + if not pool_two_done: + while True: + url_index = random.sample(xrange(len(pool_two)), 1) + + if len(url_random_pool_two) == len(pool_two): + already_picked_index_pool_two = [] + url_random_pool_two = [] + + if url_index not in already_picked_index_pool_two: + already_picked_index_pool_two.append(url_index) + url_random_pool_two.append(pool_two[url_index[0]]) + url_random.append(pool_two[url_index[0]]) + break + + if not pool_three_done: + while True: + url_index = random.sample(xrange(len(pool_three)), 1) + + if len(url_random_pool_three) == len(pool_three): + already_picked_index_pool_three = [] + url_random_pool_three = [] + + if url_index not in already_picked_index_pool_three: + already_picked_index_pool_three.append(url_index) + url_random_pool_three.append(pool_three[url_index[0]]) + url_random.append(pool_three[url_index[0]]) + break + + print 'pool 1 picked urls %s' % url_random_pool_one + print 'pool 2 picked urls %s' % url_random_pool_two + print 'pool 3 picked urls %s' % url_random_pool_three + print 'random urls %s' % url_random + + ## Fetch remotes. + if len(url_random) > 0: + urls, unix_times = url_to_unixtime(url_random) + else: + ## Add code here. + sys.exit(1) + + ## We can [safely?] infer that something is wrong with tor. + if (urls[0] == 'Connection closed unexpectedly' and + urls[1] == 'Connection closed unexpectedly' and + urls[2] == 'Connection closed unexpectedly'): + ## Raise error, log, user warning. + print urls[0] + sys.exit(1) + + if not pool_one_done: + ## Is last element in the pool in returned urls? + pool_one_done = url_random_pool_one[len(url_random_pool_one) - 1] in urls + + if not pool_two_done: + pool_two_done = url_random_pool_two[len(url_random_pool_two) - 1] in urls + + if not pool_three_done: + pool_three_done = url_random_pool_three[len(url_random_pool_three) - 1] in urls + + returned_unixtimes = len(urls) + print 'Returned unixtimes: %s' % (returned_unixtimes) + +for i in range(0, len(urls)): + print '"%s" %s' % (urls[i], unix_times[i]) + +print 'End %s' % (time.time()) diff --git a/usr/lib/sdwdate-python/url_to_unixtime.py b/usr/lib/sdwdate-python/url_to_unixtime.py new file mode 100644 index 00000000..ea7cf201 --- /dev/null +++ b/usr/lib/sdwdate-python/url_to_unixtime.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +## Copyright (C) 2015 troubadour +## Copyright (C) 2015 Patrick Schleizer +## See the file COPYING for copying conditions. + +import sys +import gevent.monkey +gevent.monkey.patch_socket() +from gevent import Timeout +import socks +from dateutil.parser import parse + +import time + +urls = [] +unix_times = [] + +def unixtime_sanity_check(data, http_time, parsed_unixtime, url): + try: + unixtime_digit = int(parsed_unixtime) + + except ValueError as e: + print >> sys.stderr, 'parsed_unixtime conversion failed!' + print >> sys.stderr, 'data: %s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) + print >> sys.stderr, 'parsed_unixtime not numeric!' + return + #exit(6) + + unixtime_string_length_is = len(parsed_unixtime) + unixtime_string_length_max = 10 + + if unixtime_string_length_is > unixtime_string_length_max: + print >> sys.stderr, 'parsed_unixtime conversion failed!' + print >> sys.stderr, 'data: %s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) + print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) + print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) + print >> sys.stderr, 'parsed_unixtime has excessive string length!' + return + #sys.exit(7) + + urls.append(url) + unix_times.append(parsed_unixtime) + +def http_time_to_parsed_unixtime(data, http_time, url): + try: + ## Thanks to: + ## eumiro + ## http://stackoverflow.com/a/3894047/2605155 + parsed_unixtime = parse(http_time).strftime('%s') + + except ValueError as e: + print >> sys.stderr, 'Parsing http_time from server failed!' + print >> sys.stderr, 'HTTP header data:\n%s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'dateutil ValueError: %s' % (e) + return + #sys.exit(5) + + #print(parsed_unixtime) + unixtime_sanity_check(data, http_time, parsed_unixtime, url) + +def data_to_http_time(data, date_string_start_position, url): + http_time = '' + ## max accepted string length. + http_time = data[date_string_start_position:date_string_start_position + 29].strip() + + http_time_string_length = len(http_time) + + ## min string length = max string length. + if http_time_string_length < 29: + print >> sys.stderr, 'HTTP header date string too short.' + print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length + print >> sys.stderr, 'HTTP header data:\n%s' % (data) + print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) + return + #sys.exit(4) + + #print http_time + http_time_to_parsed_unixtime(data, http_time, url) + +def data_to_date_string_start_position(data, url): + date_string_start_position = data.find('Date:') + + if date_string_start_position == -1: + ## not found, check if lowercase. + date_string_start_position = data.find('date:') + + if date_string_start_position == -1: + ## "Date:" not found. + print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) + #print 'HTTP header data:\n%s' % (data) + return + #sys.exit(3) + + else: + date_string_start_position = date_string_start_position + 6 + data_to_http_time(data, date_string_start_position, url) + + +def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): + s = socks.socksocket() + s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) + #print 'THREAD STARTED "%s"' % url + + try: + s.connect((url, remote_port)) + print 'CONNECTED "%s"' % url + + ## Should occur only when tor is not running (stopped, crashed, + ## gateway shut down...) + except socks.GeneralProxyError as e: + error = '%s' % (e) + urls.append(error) + return urls + + except IOError as e: + ## {{ wheezy compatibility + if str(e).startswith('__init__'): + print >> sys.stderr, 'connect error: URL "%s" not found.' % url + else: + ## }} + ## Should return the errors to sdwdate for logging. + print >> sys.stderr, '"%s" %s ' % (url, e) + return + #sys.exit + + s.send('HEAD / HTTP/1.0\r\n\r\n') + data = '' + buf = s.recv(1024) + print 'SENDING "%s"' % url + while len(buf): + data += buf + buf = s.recv(1024) + s.close() + print 'RECEIVED "%s"' % url + + data_to_date_string_start_position(data, url) + + +def url_to_unixtime(remotes): + threads = [] + + timeout = gevent.Timeout() + timer = [] + seconds = 10 + + for i in range(0, len(remotes)): + timer.append(timeout.start_new(seconds)) + args = (request_data_from_remote_server, '127.0.0.1', '9050', remotes[i], 80) + threads.append(gevent.spawn(*args)) + + for i in range(0, len(remotes)): + try: + threads[i].join(timeout=timer[i]) + + except Timeout: + print >> sys.stderr, '"%s" Timeout' % (threads[i].args[2]) + + return urls, unix_times From f9f71b59bfc45c9abf20a56b8646195d989c6a3a Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 23 Jun 2015 18:27:42 +0000 Subject: [PATCH 002/183] - sdwdate-python --- usr/lib/sdwdate-python/sdwdate_python | 166 ---------------------- usr/lib/sdwdate-python/url_to_unixtime.py | 164 --------------------- 2 files changed, 330 deletions(-) delete mode 100755 usr/lib/sdwdate-python/sdwdate_python delete mode 100644 usr/lib/sdwdate-python/url_to_unixtime.py diff --git a/usr/lib/sdwdate-python/sdwdate_python b/usr/lib/sdwdate-python/sdwdate_python deleted file mode 100755 index 1a01e7b4..00000000 --- a/usr/lib/sdwdate-python/sdwdate_python +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python - -import sys -from url_to_unixtime import url_to_unixtime -import time, random - -#import config - -pool_one = ['dtsxnd3ykn32ywv6.onion', - 'znig4bc5rlwyj4mz.onion', - 'vtjkwwcq5osuo6uq.onion', - '33y6fjyhs3phzfjj.onion', - 'y6xjgkgwj47us5ca.onion', - 'strngbxhwyuu37a3.onion', - 'swdi5ymnwmrqhycl.onion', - 'dqeasamlf3jld2kz.onion', - 'pubdrop4dw6rk3aq.onion', - 'hkjpnjbvhrxjvikd.onion', - 'v6gdwmm7ed4oifvd.onion', - 'vbmwh445kf3fs2v4.onion', - 'poulsensqiv6ocq4.onion', - 'tigas3l7uusztiqu.onion', - 'w6csjytbrl273che.onion', - 'ak2uqfavwgmjrvtu.onion'] - -pool_two = ['yn6ocmvu4ok3k3al.onion', - 'acabtd4btrxjjrvr.onion', - '5r4bjnjug3apqdii.onion', - '2dermafialks7aai.onion', - 'ymi7h25hgp3bj63v.onion', - 'ppdz5djzpo3w5k2z.onion', - 'pltloztihmfrg2sw.onion', - 'ur5b2b4brz427ygh.onion', - 'abkjckdgoabr7bmm.onion', - 'bqs3dobnazs7h4u4.onion', - 'fkut2p37apcg6l7f.onion', - '6iolddfbfinntq2b.onion', - 'nzh3fv6jc6jskki3.onion'] - -pool_three = ['cwoiopiifrlzcuos.onion', - 'zsolxunfmbfuq7wf.onion', - 'yfm6sdhnfbulplsw.onion', - 'j6uhdvbhz74oefxf.onion', - '3g2upl4pq6kufc4m.onion', - 'dju2peblv7upfz3q.onion', - 'msydqstlz2kzerdg.onion', - 'uj3wazyk5u4hnvtk.onion', - 'wi7qkxyrdpu5cmvr.onion', - 'ic6au7wa3f6naxjq.onion', - 'timaq4ygg2iegci7.onion', - '344c6kbnjnljjzlz.onion', - 'fncuwbiisyh6ak3i.onion'] - -returned_unixtimes = 0 - -already_picked_index_pool_one = [] -already_picked_index_pool_two = [] -already_picked_index_pool_three = [] -already_picked_index_dummy_pool = [] - -pool_one_done = False -pool_two_done = False -pool_three_done = False -dummy_pool_done = False -url_random_pool_one = [] -url_random_pool_two = [] -url_random_pool_three = [] -url_random_dummy_pool = [] - -print 'Start %s' % (time.time()) - -while returned_unixtimes < 3: - urls = [] - unixtimes = [] - url_random = [] - - random.seed() - - url_index = [] - - if not pool_one_done: - while True: - ## Pick a random pool index. - url_index = random.sample(xrange(len(pool_one)), 1) - - ## Reset to prevent infinite loop, next condition would never be met, - ## probably raise an error before that happens. - if len(already_picked_index_pool_one) == len(pool_one): - already_picked_index_pool_one = [] - url_random_pool_one = [] - - ## Was this index used? - if url_index not in already_picked_index_pool_one: - ## No, add to used indexes. - already_picked_index_pool_one.append(url_index) - ## Add it to the pool url list. - url_random_pool_one.append(pool_one[url_index[0]]) - ## Add it to the url list to fetch. - url_random.append(pool_one[url_index[0]]) - break - - if not pool_two_done: - while True: - url_index = random.sample(xrange(len(pool_two)), 1) - - if len(url_random_pool_two) == len(pool_two): - already_picked_index_pool_two = [] - url_random_pool_two = [] - - if url_index not in already_picked_index_pool_two: - already_picked_index_pool_two.append(url_index) - url_random_pool_two.append(pool_two[url_index[0]]) - url_random.append(pool_two[url_index[0]]) - break - - if not pool_three_done: - while True: - url_index = random.sample(xrange(len(pool_three)), 1) - - if len(url_random_pool_three) == len(pool_three): - already_picked_index_pool_three = [] - url_random_pool_three = [] - - if url_index not in already_picked_index_pool_three: - already_picked_index_pool_three.append(url_index) - url_random_pool_three.append(pool_three[url_index[0]]) - url_random.append(pool_three[url_index[0]]) - break - - print 'pool 1 picked urls %s' % url_random_pool_one - print 'pool 2 picked urls %s' % url_random_pool_two - print 'pool 3 picked urls %s' % url_random_pool_three - print 'random urls %s' % url_random - - ## Fetch remotes. - if len(url_random) > 0: - urls, unix_times = url_to_unixtime(url_random) - else: - ## Add code here. - sys.exit(1) - - ## We can [safely?] infer that something is wrong with tor. - if (urls[0] == 'Connection closed unexpectedly' and - urls[1] == 'Connection closed unexpectedly' and - urls[2] == 'Connection closed unexpectedly'): - ## Raise error, log, user warning. - print urls[0] - sys.exit(1) - - if not pool_one_done: - ## Is last element in the pool in returned urls? - pool_one_done = url_random_pool_one[len(url_random_pool_one) - 1] in urls - - if not pool_two_done: - pool_two_done = url_random_pool_two[len(url_random_pool_two) - 1] in urls - - if not pool_three_done: - pool_three_done = url_random_pool_three[len(url_random_pool_three) - 1] in urls - - returned_unixtimes = len(urls) - print 'Returned unixtimes: %s' % (returned_unixtimes) - -for i in range(0, len(urls)): - print '"%s" %s' % (urls[i], unix_times[i]) - -print 'End %s' % (time.time()) diff --git a/usr/lib/sdwdate-python/url_to_unixtime.py b/usr/lib/sdwdate-python/url_to_unixtime.py deleted file mode 100644 index ea7cf201..00000000 --- a/usr/lib/sdwdate-python/url_to_unixtime.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python - -## Copyright (C) 2015 troubadour -## Copyright (C) 2015 Patrick Schleizer -## See the file COPYING for copying conditions. - -import sys -import gevent.monkey -gevent.monkey.patch_socket() -from gevent import Timeout -import socks -from dateutil.parser import parse - -import time - -urls = [] -unix_times = [] - -def unixtime_sanity_check(data, http_time, parsed_unixtime, url): - try: - unixtime_digit = int(parsed_unixtime) - - except ValueError as e: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'parsed_unixtime not numeric!' - return - #exit(6) - - unixtime_string_length_is = len(parsed_unixtime) - unixtime_string_length_max = 10 - - if unixtime_string_length_is > unixtime_string_length_max: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) - print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) - print >> sys.stderr, 'parsed_unixtime has excessive string length!' - return - #sys.exit(7) - - urls.append(url) - unix_times.append(parsed_unixtime) - -def http_time_to_parsed_unixtime(data, http_time, url): - try: - ## Thanks to: - ## eumiro - ## http://stackoverflow.com/a/3894047/2605155 - parsed_unixtime = parse(http_time).strftime('%s') - - except ValueError as e: - print >> sys.stderr, 'Parsing http_time from server failed!' - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'dateutil ValueError: %s' % (e) - return - #sys.exit(5) - - #print(parsed_unixtime) - unixtime_sanity_check(data, http_time, parsed_unixtime, url) - -def data_to_http_time(data, date_string_start_position, url): - http_time = '' - ## max accepted string length. - http_time = data[date_string_start_position:date_string_start_position + 29].strip() - - http_time_string_length = len(http_time) - - ## min string length = max string length. - if http_time_string_length < 29: - print >> sys.stderr, 'HTTP header date string too short.' - print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) - return - #sys.exit(4) - - #print http_time - http_time_to_parsed_unixtime(data, http_time, url) - -def data_to_date_string_start_position(data, url): - date_string_start_position = data.find('Date:') - - if date_string_start_position == -1: - ## not found, check if lowercase. - date_string_start_position = data.find('date:') - - if date_string_start_position == -1: - ## "Date:" not found. - print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) - #print 'HTTP header data:\n%s' % (data) - return - #sys.exit(3) - - else: - date_string_start_position = date_string_start_position + 6 - data_to_http_time(data, date_string_start_position, url) - - -def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): - s = socks.socksocket() - s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) - #print 'THREAD STARTED "%s"' % url - - try: - s.connect((url, remote_port)) - print 'CONNECTED "%s"' % url - - ## Should occur only when tor is not running (stopped, crashed, - ## gateway shut down...) - except socks.GeneralProxyError as e: - error = '%s' % (e) - urls.append(error) - return urls - - except IOError as e: - ## {{ wheezy compatibility - if str(e).startswith('__init__'): - print >> sys.stderr, 'connect error: URL "%s" not found.' % url - else: - ## }} - ## Should return the errors to sdwdate for logging. - print >> sys.stderr, '"%s" %s ' % (url, e) - return - #sys.exit - - s.send('HEAD / HTTP/1.0\r\n\r\n') - data = '' - buf = s.recv(1024) - print 'SENDING "%s"' % url - while len(buf): - data += buf - buf = s.recv(1024) - s.close() - print 'RECEIVED "%s"' % url - - data_to_date_string_start_position(data, url) - - -def url_to_unixtime(remotes): - threads = [] - - timeout = gevent.Timeout() - timer = [] - seconds = 10 - - for i in range(0, len(remotes)): - timer.append(timeout.start_new(seconds)) - args = (request_data_from_remote_server, '127.0.0.1', '9050', remotes[i], 80) - threads.append(gevent.spawn(*args)) - - for i in range(0, len(remotes)): - try: - threads[i].join(timeout=timer[i]) - - except Timeout: - print >> sys.stderr, '"%s" Timeout' % (threads[i].args[2]) - - return urls, unix_times From aa95418cfd3bf3b4ea71dd33bec91ddcf60c3fee Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 23 Jun 2015 18:37:24 +0000 Subject: [PATCH 003/183] - bash files --- usr/bin/sdwdate | 60 - usr/lib/sdwdate/modules.d/sdwdate | 1712 ------------------------ usr/lib/sdwdate/restart_fresh | 5 - usr/lib/sdwdate/sclockadj | 230 ---- usr/lib/sdwdate/sclockadj_debug_helper | 10 - usr/lib/sdwdate/sclockadj_kill_helper | 38 - usr/lib/sdwdate/url_to_unixtime | 149 --- usr/lib/tmpfiles.d/sdwdate.conf | 6 - usr/share/sdwdate/unit_test | 109 -- 9 files changed, 2319 deletions(-) delete mode 100755 usr/bin/sdwdate delete mode 100755 usr/lib/sdwdate/modules.d/sdwdate delete mode 100755 usr/lib/sdwdate/restart_fresh delete mode 100755 usr/lib/sdwdate/sclockadj delete mode 100755 usr/lib/sdwdate/sclockadj_debug_helper delete mode 100755 usr/lib/sdwdate/sclockadj_kill_helper delete mode 100755 usr/lib/sdwdate/url_to_unixtime delete mode 100644 usr/lib/tmpfiles.d/sdwdate.conf delete mode 100755 usr/share/sdwdate/unit_test diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate deleted file mode 100755 index bb2c1e27..00000000 --- a/usr/bin/sdwdate +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -#set -x - -set -e -set -o pipefail -set -o errtrace - -for i in /usr/lib/sdwdate/modules.d/*; do - if [ -f "$i" ]; then - ## If the last character is a ~, ignore that file, - ## because it was created by some editor, - ## which creates backup files. - if [ "${i: -1}" = "~" ]; then - continue - fi - ## Skipping files such as .dpkg-old and .dpkg-dist. - if ( echo "$i" | grep -q ".dpkg-" ); then - true "skip $i" - continue - fi - bash -n "$i" - source "$i" - fi -done - -SCRIPTNAME="$(basename "$BASH_SOURCE")" -ownpid="$$" -ID="$ownpid" - -## XXX: do not hardcode path to /var/run/sdwdate -if [ ! -f "/var/run/sdwdate/first_success" ]; then - SDW_MODE="startup" -else - SDW_MODE="restartup" -fi - -trap "sdwdate_error_handler" ERR - -set +e - -trap "sdwdate_signal_sigint" SIGINT ## ctrl + c - -trap "sdwdate_signal_sigterm" SIGTERM - -trap "sdwdate_signal_sigusr1" SIGUSR1 - -trap "sdwdate_signal_sighup" SIGHUP - -## XXX: do not hardcode path to /var/run/sdwdate -## XXX: remove workaround during migration to systemd -echo "$ownpid" > "/var/run/sdwdate/pid" || echo "$ownpid" > "/var/run/sdwdate/pid_" - -sdwdate_main ${1+"$@"} - -sdwdate_log "End." diff --git a/usr/lib/sdwdate/modules.d/sdwdate b/usr/lib/sdwdate/modules.d/sdwdate deleted file mode 100755 index 5afb95a9..00000000 --- a/usr/lib/sdwdate/modules.d/sdwdate +++ /dev/null @@ -1,1712 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -## CREDITS -## - sdwdate is a rewrite of tails_htp in bash -## - defaults taken from tails_htp -## - options inspired by tails_htp -## - log file layout inspired by tails_htp -## - sclockadj has been written by Jason Ayala (Jason@JasonAyala.com) - -## TODO: -## - implement --user - -sdwdate_usage() { - echo "\ -$SCRIPTNAME [-aDdlpqTux] [long options...] - -d --debug debug - --help print usage message and exit - -q --quiet quiet - -x --dont_set_date do not set the time (only show) - --no-move-forward do not move the time forward - --no-move-backwards do not move the time backwards - --systohc update hardware clock - -a --user_agent http user agent to use - -l --log_file log to this file rather than to STDOUT - -D --done_file create this file after one cycle of sdwdate - -T --success_file create this file after setting time successfully - --first_success_file create this file after setting time successfully for the first time - --pool_one distrusted hostnames - --pool_two distrusted hostnames - --pool_three distrusted hostnames - --allowed_per_pool_failure_ratio ratio (0.0-1.0) of allowed per-pool failure - --cache-dir cache directory - --temp-dir temporary directory - -p --proxy-ip proxy IP - -o --port-port proxy port - -i --interval continuously run every x minutes - --mininterval wait minimum between intervals - -r --randomize randomize interval - -n --noconfig ignore /etc/sdwdate.d/ config folder - --parsecmd_only_on_startup parse command line arguments only on startup - -w --dispatchpre exec before run - -y --dispatchprerequisite check before run - -x --dispatchpostsuccess exec after successful run - -z --dispatchpostfailure exec after unsuccessful run - -m --dispatchposterror exec when an error is caught - --dispatch_pre_one exec before run of url to unixtime tool - --dispatch_post_one exec after run of url to unixtime tool - --dispatch_pre_two exec before run of url to unixtime tool - --dispatch_post_two exec after run of url to unixtime tool - --dispatch_pre_three exec before first run of url to unixtime tool - --dispatch_post_three exec after run of url to unixtime tool - --nodonefileonerror do not create done file on error - --noexitonerror do not exit on error - --echo-unixtime echo remote unix time even when using --quiet" - -# -u --user userid to run as -} - -sdwdate_defaults() { - if [ "$DEBUG" = "" ]; then - DEBUG="0" - fi - - if [ "$USER" = "" ]; then - USER="" - fi - - if [ "$DONT_SET_DATE" = "" ]; then - DONT_SET_DATE="0" - fi - - if [ "$NO_MOVE_FORWARD" = "" ]; then - NO_MOVE_FORWARD=1 - fi - - if [ "$NO_MOVE_BACKWARDS" = "" ]; then - NO_MOVE_BACKWARDS=1 - fi - - if [ "$SYSTOHC" = "" ]; then - SYSTOHC=0 - SDWDATE_SCLOCKADJ_SYSTOHC="--nosystohc" - fi - - if [ "$LOG_FILE" = "" ]; then - LOG_FILE=~/.sdwdate/log - fi - - if [ "$DONE_FILE" = "" ]; then - DONE_FILE=~/.sdwdate/done - fi - - if [ "$SUCCESS_FILE" = "" ]; then - SUCCESS_FILE=~/.sdwdate/success - fi - - if [ "$FIRST_SUCCESS_FILE" = "" ]; then - FIRST_SUCCESS_FILE=~/.sdwdate/first_success - fi - - if [ "$ALLOWED_PER_POOL_FAILURE_RATIO" = "" ]; then - ALLOWED_PER_POOL_FAILURE_RATIO="0.34" - fi - - if [ "$TEMP_DIR" = "" ]; then - TEMP_DIR="$(mktemp --directory)" - fi - ## Export so plugins run by sdwdate can use it. - export TEMP_DIR - - if [ "$SDW_CACHE_DIR" = "" ]; then - SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" - fi - - if [ "$PROXY_IP" = "" ]; then - PROXY_IP="127.0.0.1" - fi - - if [ "$PROXY_PORT" = "" ]; then - PROXY_PORT="9050" - fi - - if [ "$INTERVAL" = "" ]; then - INTERVAL="" - fi - - if [ "$MIN_INTERVAL" = "" ]; then - MIN_INTERVAL="60" - fi - - if [ "$NOCONFIG" = "" ]; then - NOCONFIG="" - fi - - if [ "$PARSE_CMD_ONLY_ON_STARTUP" = "" ]; then - PARSE_CMD_ONLY_ON_STARTUP="" - fi - - if [ "$RANDOMIZE" = "" ]; then - RANDOMIZE="" - fi - - if [ "$DISPATCH_PREREQUISITE" = "" ]; then - DISPATCH_PREREQUISITE="true" - fi - - if [ "$DISPATCH_POST_ERROR" = "" ]; then - DISPATCH_POST_ERROR="exit 1" - fi - - if [ "$SDW_TOUCH_DONE_FILE_ON_ERROR" = "" ]; then - SDW_TOUCH_DONE_FILE_ON_ERROR="1" - fi - - if [ "$SDW_EXIT_ON_ERROR" = "" ]; then - SDW_EXIT_ON_ERROR="1" - fi - - if [ "$DISPATCH_PRE" = "" ]; then - DISPATCH_PRE="true" - fi - - if [ "$DISPATCH_POST_SUCCESS" = "" ]; then - DISPATCH_POST_SUCCESS="true" - fi - - if [ "$DISPATCH_POST_FAILURE" = "" ]; then - DISPATCH_POST_FAILURE="true" - fi - - if [ "$HTTP_USER_AGENT" = "" ]; then - HTTP_USER_AGENT="" - #HTTP_USER_AGENT="$(/usr/bin/getTorbuttonUserAgent)" - #HTTP_USER_AGENT="Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0" - fi - - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP" = "" ]; then - SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" - fi - - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP" = "" ]; then - SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" - fi - - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON" = "" ]; then - SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" - fi - - if [ "$SDWDATE_SCLOCKADJ_VERBOSE" = "" ]; then - SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" - fi - - if [ "$SDWDATE_SCLOCKADJ_CHANGE_DATE" = "" ]; then - SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" - fi - - if [ "$SDWDATE_SCLOCKADJ_FIRST_WAIT" = "" ]; then - SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" - fi - - if [ "$SDWDATE_SCLOCKADJ_MOVE_MIN" = "" ]; then - SDWDATE_SCLOCKADJ_MOVE_MIN="500000" - fi - - if [ "$SDWDATE_SCLOCKADJ_MOVE_MAX" = "" ]; then - SDWDATE_SCLOCKADJ_MOVE_MAX="500000" - fi - - if [ "$SDWDATE_SCLOCKADJ_WAIT_MIN" = "" ]; then - SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" - fi - - if [ "$SDWDATE_SCLOCKADJ_WAIT_MAX" = "" ]; then - SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" - fi - - if [ "$ECHO_UNIX_TIME" = "" ]; then - ECHO_UNIX_TIME="false" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_PRE - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="true" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_POST - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="true" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_PRE - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="true" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_POST - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="true" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_PRE - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="true" - fi - - if [ "${SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]}" = "" ]; then - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_POST - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="true" - fi - - if [ "$SDWDATE_POOL_ONE" = "" ]; then - declare -g SDWDATE_POOL_ONE - - SDWDATE_POOL_ONE=( - boum.org - chavez.indymedia.org - db.debian.org - debian.org - epic.org - mail.riseup.net - sarava.org - squat.net - tachanka.org - www.1984.is - www.eff.org - www.immerda.ch - www.privacyinternational.org - www.torproject.org - freedom.press - ) - fi - - if [ "$SDWDATE_POOL_TWO" = "" ]; then - declare -g SDWDATE_POOL_TWO - - SDWDATE_POOL_TWO=( - grc.com - www.tarsnap.com - wikileaks.org - pressfreedomfoundation.org - securedrop.theguardian.com - safesource.forbes.com - en.wikipedia.org - cve.mitre.org - duckduckgo.com - lkml.org - www.gnu.org - fsf.org - fsfe.org - defectivebydesign.org - www.kernel.org - opentechfund.org - firstlook.org - ) - fi - - if [ "$SDWDATE_POOL_THREE" = "" ]; then - declare -g SDWDATE_POOL_THREE - - SDWDATE_POOL_THREE=( - www.startpage.com - www.apache.org - www.centos.org - www.piratenpartei.de - www.torservers.net - www.accessnow.org - rsf.org - www.wauland.de - www.ccc.de - netzpolitik.org - privacyfoundation.ch - www.noisebridge.net - en.bitcoin.it - www.calyxinstitute.org - gnupg.org - gpgtools.org - schneier.com - ) - fi -} - -sdwdate_read_config_folder() { - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_PRE - declare -A -g SDWDATE_TIME_TOOL_DISPATCH_POST - - if [ "$NOCONFIG" = "1" ]; then - return 0 - fi - for i in /etc/sdwdate.d/*; do - if [ -f "$i" ]; then - ## If the last character is a ~, ignore that file, because it was created - ## by some editor, which creates backup files. - if [ "${i: -1}" = "~" ]; then - continue - fi - ## Skipping files such as .dpkg-old and .dpkg-dist. - if ( echo "$i" | grep -q ".dpkg-" ); then - true "skip $i" - continue - fi - local bash_n_exit_code bash_n_output - bash_n_exit_code="0" - bash_n_output="$(bash -n "$i" 2>&1)" || { bash_n_exit_code="$?" ; true; }; - if [ ! "$bash_n_exit_code" = "0" ]; then - sdwdate_error_handler "Invalid config file: $i -bash_n_exit_code: $bash_n_exit_code -bash_n_output: -$bash_n_output" || true - fi - source "$i" - fi - done -} - -sdwdate_parse_cmd_options() { - ## Thanks to: - ## http://mywiki.wooledge.org/BashFAQ/035 - - while : - do - case $1 in - -h | --help | -\?) - sdwdate_usage - exit 0 - ;; - -d | --debug) - DEBUG=1 - shift - ;; - -u | --user) - USER=$2 - RUN_AS="sudo -u $USER" - shift 2 - ;; - -x | --dont_set_date) - DONT_SET_DATE=1 - shift - ;; - --no-move-forward) - NO_MOVE_FORWARD=1 - shift - ;; - --no-move-backwards) - NO_MOVE_BACKWARDS=1 - shift - ;; - --systohc) - SYSTOHC=1 - SDWDATE_SCLOCKADJ_SYSTOHC="--systohc" - shift - ;; - -a | --user_agent) - HTTP_USER_AGENT=$2 - shift 2 - ;; - -l | --log_file) - LOG_FILE=$2 - shift 2 - ;; - -D | --done_file) - DONE_FILE=$2 - shift 2 - ;; - -T | --success_file) - SUCCESS_FILE=$2 - shift 2 - ;; - -T | --first_success_file) - FIRST_SUCCESS_FILE=$2 - shift 2 - ;; - -P | --pool_one) - SDWDATE_POOL_ONE=$2 - shift 2 - ;; - -N | --pool_two) - SDWDATE_POOL_TWO=$2 - shift 2 - ;; - -F | --pool_three) - SDWDATE_POOL_THREE=$2 - shift 2 - ;; - -A | --allowed_per_pool_failure_ratio) - ALLOWED_PER_POOL_FAILURE_RATIO=$2 - shift 2 - ;; - --cache-dir) - SDW_CACHE_DIR=$2 - shift 2 - ;; - --temp-dir) - TEMP_DIR=$2 - shift 2 - ;; - -p | --proxy-ip) - PROXY_IP=$2 - shift 2 - ;; - -o | --proxy-port) - PROXY_PORT=$2 - shift 2 - ;; - -i | --interval) - INTERVAL=$2 - shift 2 - ;; - --mininterval) - MIN_INTERVAL=$2 - shift 2 - ;; - -n | --noconfig) - NOCONFIG="1" - shift 1 - ;; - --parsecmd_only_on_startup) - PARSE_CMD_ONLY_ON_STARTUP="1" - shift 1 - ;; - -r | --randomize) - RANDOMIZE="1" - shift 1 - ;; - --timewarp-on-startup) - SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" - shift 1 - ;; - --timewarp-on-restartup) - SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="0" - shift 1 - ;; - --timewarp-on-daemon) - SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="0" - shift 1 - ;; - --sclockadj-on-startup) - SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="1" - shift 1 - ;; - --sclockadj-on-daemon) - SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" - shift 1 - ;; - --sclockadj-verbose) - SDWDATE_SCLOCKADJ_VERBOSE="--verbose" - shift 1 - ;; - --sclockadj-no-change-date) - SDWDATE_SCLOCKADJ_CHANGE_DATE="--debug" - shift 1 - ;; - --sclockadj-first-wait) - SDWDATE_SCLOCKADJ_FIRST_WAIT="--first-wait" - shift 1 - ;; - --sclockadj-move-min) - SDWDATE_SCLOCKADJ_MOVE_MIN=$2 - shift 2 - ;; - --sclockadj-move-max) - SDWDATE_SCLOCKADJ_MOVE_MAX=$2 - shift 2 - ;; - --sclockadj-wait-min) - SDWDATE_SCLOCKADJ_WAIT_MIN=$2 - shift 2 - ;; - --sclockadj-wait-max) - SDWDATE_SCLOCKADJ_WAIT_MAX=$2 - shift 2 - ;; - -y | --dispatchprerequisite) - DISPATCH_PREREQUISITE=$2 - shift 2 - ;; - -m | --dispatchposterror) - DISPATCH_POST_ERROR=$2 - shift 2 - ;; - --nodonefileonerror) - SDW_TOUCH_DONE_FILE_ON_ERROR="0" - shift 2 - ;; - --noexitonerror) - SDW_EXIT_ON_ERROR="0" - shift 2 - ;; - -w | --dispatchpre) - DISPATCH_PRE=$2 - shift 2 - ;; - -x | --dispatchpostsuccess) - DISPATCH_POST_SUCCESS=$2 - shift 2 - ;; - -z | --dispatchpostfailure) - DISPATCH_POST_FAILURE=$2 - shift 2 - ;; - --dispatch_pre_one) - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]=$2 - shift 2 - ;; - --dispatch_post_one) - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]=$2 - shift 2 - ;; - --dispatch_pre_two) - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]=$2 - shift 2 - ;; - --dispatch_post_two) - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]=$2 - shift 2 - ;; - --dispatch_pre_three) - SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]=$2 - shift 2 - ;; - --dispatch_post_three) - SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]=$2 - shift 2 - ;; - -q | --quiet) - QUIET=1 - shift - ;; - --echo-unixtime) - ECHO_UNIX_TIME=true - shift - ;; - --) - shift - break - ;; - -*) - sdwdate_log "$SCRIPTNAME unknown option: $1" >&2 - exit 1 - ;; - *) - break - ;; - esac - done - - ## If there are input files (for example) that follow the options, they - ## will remain in the "$@" positional parameters. -} - -sdwdate_preparation() { - mkdir --parents "$(dirname "$LOG_FILE")" - mkdir --parents "$(dirname "$DONE_FILE")" - mkdir --parents "$(dirname "$SUCCESS_FILE")" - mkdir --parents "$(dirname "$FIRST_SUCCESS_FILE")" - - rm --force "$DONE_FILE" - rm --force "$SUCCESS_FILE" - #rm --force "$FIRST_SUCCESS_FILE" - - declare -A -g sdwdate_download_tool_exit_code - declare -A -g SDWDATE_DOWNLOAD_TOOL_PID - declare -A -g SDWDATE_DOWNLOAD_TOOK_TIME - - if [ "$who_ami" = "" ]; then - ## Required for eventual plugins. - who_ami="$(whoami)" - fi - sdwdate_log "$FUNCNAME: who_ami is set to $who_ami." -} - -sdwdate_echo() { - if [ "$QUIET" = "1" ]; then - true - else - echo "$@" - fi -} - -sdwdate_write_log() { - ## Default, if the error happens before defaults are set and before config - ## files are read. - if [ "$LOG_FILE" = "" ]; then - LOG_FILE="/var/log/sdwdate.log" - fi - - if [ "$LOG_FILE" = "" ]; then - true - else - echo "$@" >> "$LOG_FILE" - fi -} - -sdwdate_log() { - if [ "$ID" = "" ] ; then - ID="No_ID" - fi - - local msg msg_string_length max_log_string_length - msg="$ID: $@" - msg_string_length="${#msg}" - max_log_string_length="500" - - if [ "$msg_string_length" -gt "$max_log_string_length" ]; then - ## Shorten excess length $msg to $max_log_string_length chars. - msg="${msg:0:$max_log_string_length}" - msg="${msg} ||| Shortened log msg string length!" - fi - - swddate_last_but_one_msg="$swddate_last_msg" - swddate_last_msg="$msg" - - sdwdate_echo "$msg" - sdwdate_write_log "$msg" -} - -sdwdate_no_duplicate_log() { - if [ ! -f "$LOG_FILE" ]; then - sdwdate_log "$@" - return 0 - fi - - if [ "$swddate_last_msg" = "$ID: $@" ]; then - return 0 - fi - if [ "$swddate_last_but_one_msg" = "$ID: $@" ]; then - return 0 - fi - - sdwdate_log "$@" -} - -sdwdate_error_handler() { - local exit_code="$?" - local error_cause error_text - error_text="$1" - if [ "$error_text" = "" ]; then - error_cause="$FUNCNAME signal ERR detected in line ${BASH_LINENO[0]} with BASH_COMMAND: -$BASH_COMMAND" - else - error_cause="$FUNCNAME called with error_text: -$error_text" - fi - - ## Default, if the error happens before defaults are set and before config - ## files are read. - if [ "$SDW_EXIT_ON_ERROR" = "" ]; then - SDW_EXIT_ON_ERROR="1" - fi - - local current_time - current_time="$(date)" || true - - local error_message - error_message="\ -############################################ -## Error detected! Please report this bug! # -## -## BASH_SOURCE: $BASH_SOURCE -## SDW_EXIT_ON_ERROR: $SDW_EXIT_ON_ERROR -## SDW_MODE: $SDW_MODE -## SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE -## current_time: $current_time -## -## exit_code: $exit_code -## error_cause: -$error_cause -############################################\ -" - - sdwdate_log "error_message: $error_message" || true - - error_message="\ -############################################

-## Error detected! Please report this bug! #

-##

-## BASH_SOURCE: $BASH_SOURCE

-## SDW_EXIT_ON_ERROR: $SDW_EXIT_ON_ERROR

-## SDW_MODE: $SDW_MODE

-## SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE

-## current_time: $current_time

-##

-## exit_code: $exit_code

-## error_cause:

-$error_cause

-############################################

\ -" - - local ret - if [ "$SDW_TOUCH_DONE_FILE_ON_ERROR" = "1" ]; then - touch "$DONE_FILE" - ret="$?" - if [ ! "$ret" = "0" ]; then - sdwdate_log "ERROR $BASH_SOURCE: Could not touch DONE_FILE: $DONE_FILE." || true - fi - fi - - sdwdate_dispatcher "DISPATCH_POST_ERROR" - - if [ "$SDW_EXIT_ON_ERROR" = "1" ]; then - sdwdate_log "$SCRIPTNAME (not timesync!): Error detected. Cleaning up..." || true - ## Check if function sdwdate_terminate_sclockadj is already available and - ## use it if available. - command_v_exit_code="0" - command -v sdwdate_terminate_sclockadj >/dev/null 2>&1 || { command_v_exit_code="$?" ; true; }; - if [ "$command_v_exit_code" = "0" ]; then - sdwdate_terminate_sclockadj || true - fi - SIGNAL_TYPE="ERR" - if [ "$(command -v "sdwdate_ex_funct")" ]; then - sdwdate_ex_funct || true - fi - sdwdate_log "$SCRIPTNAME (not timesync!): Error detected. Exiting." || true - exit 1 - else - sdwdate_log "$SCRIPTNAME (not timesync!): Error detected. Continuing." || true - fi -} - -sdwdate_sanity_tests() { - if [ "$ID" = "" ]; then - sdwdate_error_handler "Variable ID is empty." || true - fi - - command -v bash >/dev/null - command -v bc >/dev/null - command -v basename >/dev/null - command -v mktemp >/dev/null - command -v touch >/dev/null - command -v dirname >/dev/null - command -v awk >/dev/null - command -v date >/dev/null - ## Using GNU's printf instead of bash's printf. - command -v /usr/bin/printf >/dev/null - command -v od >/dev/null - command -v grep >/dev/null - command -v sudo >/dev/null - command -v shuf >/dev/null - command -v /usr/lib/sdwdate/sclockadj >/dev/null - command -v /sbin/hwclock >/dev/null - command -v /usr/lib/sdwdate/url_to_unixtime >/dev/null - command -v timeout >/dev/null - - test -e "/dev/random" - bash -n "$BASH_SOURCE" -} - -sdwdate_ex_funct() { - sdwdate_log "$SCRIPTNAME (not timesync!): signal $SIGNAL_TYPE received. Cleaning up..." - - if [ ! "$sleep_pid" = "" ]; then - kill -9 "$sleep_pid" || true - fi - - if [ ! "${SDWDATE_DOWNLOAD_TOOL_PID[$SDWDATE_CURRENT_POOL]}" = "" ]; then - kill -9 "${SDWDATE_DOWNLOAD_TOOL_PID[$SDWDATE_CURRENT_POOL]}" || true - fi - - if [ -d "$TEMP_DIR" ]; then - rm -r "$TEMP_DIR" - fi - - if [ ! "$DONE_FILE" = "" ]; then - touch "$DONE_FILE" - fi - sdwdate_log "$SCRIPTNAME (not timesync!): signal $SIGNAL_TYPE received. Exiting." -} - -sdwdate_signal_sigint() { - sdwdate_log "$SCRIPTNAME (not timesync!): signal SIGINT received..." - sdwdate_terminate_sclockadj - SIGNAL_TYPE="SIGINT" - sdwdate_ex_funct - exit 130 -} - -sdwdate_signal_sigterm() { - sdwdate_log "$SCRIPTNAME (not timesync!): signal SIGTERM received..." - sdwdate_terminate_sclockadj - SIGNAL_TYPE="SIGTERM" - sdwdate_ex_funct - exit 143 -} - -sdwdate_enable_debugging() { - ## usage: kill -sigusr1 pid - - if [ "$SDWDATE_DEBUG_TRACE_SET" ]; then - return 0 - fi - - set -x - - exec > >(tee -a "$LOG_FILE") - exec 2> >(tee -a "$LOG_FILE" >&2) - - set -o functrace - shopt -s extdebug - - #trap 'true "BASH_COMMAND: $BASH_COMMAND | FUNCNAME: ${FUNCNAME[1]} | BASH_LINENO: ${BASH_LINENO[0]} | BASH_SOURCE: ${BASH_SOURCE[1]}"' DEBUG - trap 'true "${FUNCNAME[1]}: $BASH_COMMAND"' DEBUG - - SDWDATE_DEBUG_TRACE_SET="1" -} - -sdwdate_signal_sigusr1() { - sdwdate_enable_debugging -} - -sdwdate_signal_sighup() { - SDW_MODE="sighup" -} - -sdwdate_pick_single_link_from_maybe_multi_lined_mirror_list() { - local maybe_multiple_lined_link number_lines_counter line - local line_without_spaces random_item_number number_lines random_line - maybe_multiple_lined_link="$@" - - ## {{ count lines without empty or lines with leading/trailing spaces. - number_lines_counter="0" - while read -r -d $'\n' line; do - line_without_spaces="${line// /}" - if [ "$line_without_spaces" = "" ]; then - ## Skipping empty or lines with leading/trailing spaces (such as '" '). - continue - fi - number_lines_counter="$(( $number_lines_counter + 1 ))" - done <<< "$maybe_multiple_lined_link" - ## }} - - ## {{ Generate random number between 1 (first line) and $number_lines_counter. - test -e "/dev/random" - random_item_number="$(shuf -i1-$number_lines_counter -n1 --random-source="/dev/random")" - ## }} - - ## {{ pick random line - number_lines="0" - while read -r -d $'\n' line; do - line_without_spaces="${line// /}" - if [ "$line_without_spaces" = "" ]; then - continue - fi - number_lines="$(( $number_lines + 1 ))" - if [ "$number_lines" = "$random_item_number" ]; then - true "link chosen $random_item_number / $number_lines_counter: $line" - random_line="$line" - break - fi - done <<< "$maybe_multiple_lined_link" - ## }} - - if [ "$random_line" = "" ]; then - sdwdate_error_handler "random_line was empty." - fi - - echo "$random_line" -} - -sdwdate_link_with_comments_to_processed_link() { - ## Example link_unprocessed: - ## atlas777hhh7mcs7.onion:80#Hosted by Thomas White. - - link_url_part="${link_unprocessed%"#"*}" - ## Examples link_url_part: - ## atlas777hhh7mcs7.onion:80 - ## atlas777hhh7mcs7.onion - link_comment_part="${link_unprocessed#*"#"}" - ## Example link_comment_part: - ## Hosted by Thomas White. - - if echo "$link_url_part" | grep -q ":" ; then - ## When link_url_part is for example: - ## atlas777hhh7mcs7.onion:80 - link_url="${link_url_part%":"*}" - link_port="${link_url_part#*":"}" - else - ## When link_url_part is for example: - ## atlas777hhh7mcs7.onion - link_url="$link_url_part" - link_port="80" - fi - - link_processed="${link_url}:${link_port}" - ## Example: - ## atlas777hhh7mcs7.onion:80 -} - -sdwdate_get_time_from_remote() { - sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]="0" - - local TIME_START - TIME_START="$(date +%s)" - - sdwdate_dispatcher "DISPATCH_PREREQUISITE" - - sdwdate_dispatcher "{SDWDATE_TIME_TOOL_DISPATCH_PRE[$SDWDATE_CURRENT_POOL]}" - - ## Sanity test. - touch "$TEMP_DIR/$SDWDATE_CURRENT_POOL" - rm "$TEMP_DIR/$SDWDATE_CURRENT_POOL" - touch "$TEMP_DIR/$SDWDATE_CURRENT_POOL.stderr" - rm "$TEMP_DIR/$SDWDATE_CURRENT_POOL.stderr" - - sdwdate_log "get : $link_processed | local time: $(date) | link_comment_part: $link_comment_part" - - timeout_after="180" - kill_after="10" - - ## Debugging. - #timeout_after="0.001" - #kill_after="0.001" - - timeout --kill-after="$kill_after" "$timeout_after" \ - "/usr/lib/sdwdate/url_to_unixtime" \ - "$PROXY_IP" \ - "$PROXY_PORT" \ - "$link_url" \ - "$link_port" \ - "false" \ - 1>"$TEMP_DIR/$SDWDATE_CURRENT_POOL" \ - 2>"$TEMP_DIR/$SDWDATE_CURRENT_POOL.stderr" \ - & - - SDWDATE_DOWNLOAD_TOOL_PID[$SDWDATE_CURRENT_POOL]="$!" - - sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]="0" - wait "${SDWDATE_DOWNLOAD_TOOL_PID[$SDWDATE_CURRENT_POOL]}" || { sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]="$?" ; true; }; - unset SDWDATE_DOWNLOAD_TOOL_PID[$SDWDATE_CURRENT_POOL] - - true "INFO: sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]: ${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" - - local TIME_END - TIME_END="$(date +%s)" - - true "SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]="\$\(\( $TIME_END - $TIME_START \)\)"" - SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]="$(( $TIME_END - $TIME_START ))" - - local stdout stderr - stdout="$(cat $TEMP_DIR/$SDWDATE_CURRENT_POOL)" || true - stderr="$(cat $TEMP_DIR/$SDWDATE_CURRENT_POOL.stderr)" || true - - if [ "${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" = "124" ]; then - sdwdate_log "result: $link_processed | local time: $(date) | status: failed | took: ${SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]}s | download_tool_exit_code: ${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]} | stdout: $stdout | stderr: $stderr | timeout_type: sigterm" - elif [ "${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" = "137" ]; then - sdwdate_log "result: $link_processed | local time: $(date) | status: failed | took: ${SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]}s | download_tool_exit_code: ${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]} | stdout: $stdout | stderr: $stderr | timeout_type: sigkill" - fi - - if [ ! "${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" = "0" ]; then - sdwdate_log "result: $link_processed | local time: $(date) | status: failed | took: ${SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]}s | download_tool_exit_code: ${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]} | stdout: $stdout | stderr: $stderr" - return 0 - fi - - test -f "$TEMP_DIR/$SDWDATE_CURRENT_POOL" - WEB_DATE_UNIXTIME="$stdout" - - local OLD_UNIXTIME - OLD_UNIXTIME="$(date +%s)" - - true "SDWDATE_TIME_DIFF[$SDWDATE_CURRENT_POOL]="\$\(\( $WEB_DATE_UNIXTIME - $OLD_UNIXTIME \)\)"" - SDWDATE_TIME_DIFF[$SDWDATE_CURRENT_POOL]="$(( $WEB_DATE_UNIXTIME - $OLD_UNIXTIME ))" - - sdwdate_log "result: $link_processed | local time: $(date) | status: success | took: ${SDWDATE_DOWNLOAD_TOOK_TIME[$SDWDATE_CURRENT_POOL]}s | diff: ${SDWDATE_TIME_DIFF[$SDWDATE_CURRENT_POOL]} second(s)" - - sdwdate_dispatcher "{SDWDATE_TIME_TOOL_DISPATCH_POST[$SDWDATE_CURRENT_POOL]}" - - return 0 -} - -sdwdate_loop() { - trap "sdwdate_error_handler" ERR - - eval "array=( - \"\${$SDWDATE_CURRENT_POOL[@]}\" - )" - - unset remember - declare -A -g remember - - while true; do - local array_length - array_length="${#array[@]}" - local array_length_remember - array_length_remember="${#remember[@]}" - - if [ "$array_length" = "0" ]; then - sdwdate_log "ERROR: $SDWDATE_CURRENT_POOL not configured!" - - ## Check, if we are running in daemon mode. - if [ "$INTERVAL" = "" ] || [ "$INTERVAL" = "0" ]; then - ## No daemon mode. - exit 1 - else - ## Daemon mode. - return 1 - fi - fi - - if [ "$array_length_remember" -ge "$array_length" ]; then - sdwdate_log "ERROR: No member of the $SDWDATE_CURRENT_POOL could be reached. (debugging information: array_length_remember: $array_length_remember | array_length: $array_length)" - - ## Check, if we are running in daemon mode. - if [ "$INTERVAL" = "" ] || [ "$INTERVAL" = "0" ]; then - ## No daemon mode. - exit 1 - else - ## Daemon mode. - return 1 - fi - fi - - local temp - temp="$(bc -l <<< "$array_length*$ALLOWED_PER_POOL_FAILURE_RATIO")" - ## adjust upward / downward - local allowed_member_failures - allowed_member_failures="$(/usr/bin/printf "%.f" "$temp")" - if [ "$allowed_member_failures" = "0" ]; then - allowed_member_failures="1" - fi - sdwdate_log "SDWDATE_CURRENT_POOL: $SDWDATE_CURRENT_POOL | array_length: $array_length | allowed_member_failures: $allowed_member_failures | temp: $temp | array_length_remember: $array_length_remember" - - if [ "$array_length_remember" -ge "$allowed_member_failures" ]; then - sdwdate_log "ERROR: $allowed_member_failures members of the $SDWDATE_CURRENT_POOL could not be reached. (debugging information: array_length_remember: $array_length_remember | allowed_member_failures: $allowed_member_failures)" - - ## Check, if we are running in daemon mode. - if [ "$INTERVAL" = "" ] || [ "$INTERVAL" = "0" ]; then - ## No daemon mode. - exit 1 - else - ## Daemon mode. - return 1 - fi - fi - - test -e "/dev/random" - local random_integer - random_integer="$(( ($(od -An -N2 -i "/dev/random") )%($array_length) ))" - - if [ "${remember[$random_integer]}" = "1" ]; then - continue - fi - - link_unprocessed="$(sdwdate_pick_single_link_from_maybe_multi_lined_mirror_list "${array[$random_integer]}")" - - ## sets: link_url_part - ## sets: link_comment_part - ## sets: link_url - ## sets: link_port - sdwdate_link_with_comments_to_processed_link "$link_unprocessed" - - ## sets: sdwdate_download_tool_exit_code - ## sets maybe: WEB_DATE_UNIXTIME - sdwdate_get_time_from_remote - - if [ ! "${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" = "0" ]; then - remember[$random_integer]=1 - continue - fi - - break - done - - unset remember - - return 0 -} - -sdwdate_pick() { - declare -A -g SDWDATE_RESULT - declare -A -g SDWDATE_TIME_DIFF - - for SDWDATE_CURRENT_POOL in SDWDATE_POOL_ONE SDWDATE_POOL_TWO SDWDATE_POOL_THREE; do - ## Temporarily disabling error handler. - ## Not using - ## || { sdwdate_loop_return="?"; true; }; - ## here, because then errors any errors caught in sdwdate_loop would be - ## ignored here. - trap "" ERR - sdwdate_loop "$SDWDATE_CURRENT_POOL" - sdwdate_loop_return="$?" - trap "sdwdate_error_handler" ERR - - ## Do not continue, if an error was caught. - if [ ! "$sdwdate_loop_return" = "0" ]; then - return 0 - fi - - SDWDATE_RESULT[$SDWDATE_CURRENT_POOL]="$WEB_DATE_UNIXTIME" - - true "INFO: SDWDATE_RESULT[$SDWDATE_CURRENT_POOL] = ${SDWDATE_RESULT[$SDWDATE_CURRENT_POOL]} | $(date --date "@${SDWDATE_RESULT[$SDWDATE_CURRENT_POOL]}")" - done -} - -sdwdate_build_median() { - local results results_list min max - results="${SDWDATE_TIME_DIFF[SDWDATE_POOL_ONE]} ${SDWDATE_TIME_DIFF[SDWDATE_POOL_TWO]} ${SDWDATE_TIME_DIFF[SDWDATE_POOL_THREE]}" - sdwdate_log "Results summary: \ -one: ${SDWDATE_TIME_DIFF[SDWDATE_POOL_ONE]} | \ -two: ${SDWDATE_TIME_DIFF[SDWDATE_POOL_TWO]} | \ -three: ${SDWDATE_TIME_DIFF[SDWDATE_POOL_THREE]} | \ -second(s)" - - ## Thanks to NotANumber - ## http://unix.stackexchange.com/a/93949 - results_list=( - $( - for item in $results; do - /usr/bin/printf "%d\n" "$item" - done | sort -n - ) - ) - min="${results_list[0]}" - max="${results_list[${#results_list[*]}-1]}" - SDWDATE_MEDIAN_TIME_SECONDS="${results_list[${#results_list[*]}/2]}" - - true "SDWDATE_MEDIAN_TIME_NANOSECONDS="\$\(\( $SDWDATE_MEDIAN_TIME_SECONDS \* 1000000000 \)\)"" - SDWDATE_MEDIAN_TIME_NANOSECONDS="$(( $SDWDATE_MEDIAN_TIME_SECONDS * 1000000000 ))" - - sdwdate_log "Min: $min | Max: $max | Median diff: $SDWDATE_MEDIAN_TIME_SECONDS second(s) [$SDWDATE_MEDIAN_TIME_NANOSECONDS nanosecond(s)]" - - local OLD_UNIXTIME - OLD_UNIXTIME="$(date +%s)" - true "NEW_UNIXTIME="\$\(\( $OLD_UNIXTIME + $SDWDATE_MEDIAN_TIME_SECONDS \)\)"" - NEW_UNIXTIME="$(( $OLD_UNIXTIME + $SDWDATE_MEDIAN_TIME_SECONDS ))" - - sdwdate_log "local unixtime : $(date +%s) | local time : $(date)" - sdwdate_log "remote unixtime: $NEW_UNIXTIME | remote time: $(date --date @${NEW_UNIXTIME})" - - if [ "$ECHO_UNIX_TIME" = "true" ]; then - echo "$NEW_UNIXTIME" - fi -} - -sdwdate_generate_nanoseconds() { - local ZERO_OR_ONE - ## Get a random 0 or 1. - ## Will use this to decide to use plus or minus. - ## - ## Thanks to - ## http://linux.byexamples.com/archives/128/generating-random-numbers/ - test -e "/dev/random" - ZERO_OR_ONE="$(( 0+($(od -An -N2 -i "/dev/random") )%(0+2) ))" - - if [ "$ZERO_OR_ONE" = "0" ]; then - NANOSECONDS_PLUS_OR_MINUS="-" - elif [ "$ZERO_OR_ONE" = "1" ]; then - NANOSECONDS_PLUS_OR_MINUS="+" - else - sdwdate_error_handler "ZERO_OR_ONE is neither 0 nor 1, it's: $ZERO_OR_ONE" || true - fi - - ## Create a random number between 0 and 999999999. - ## - ## Thanks to - ## https://stackoverflow.com/questions/22887891/how-can-i-get-a-random-dev-random-number-between-0-and-999999999-in-bash - test -e "/dev/random" - NANOSECONDS="$(shuf -i0-999999999 -n1 --random-source="/dev/random")" - - local extra_seconds_without_leading_zero extra_seconds - extra_seconds_without_leading_zero="$(bc -l <<< "scale=9 ; $NANOSECONDS / 1000000000")" - extra_seconds="$(printf '%3.9f\n' "$extra_seconds_without_leading_zero")" - - sdwdate_log "Made up random extra: ${NANOSECONDS_PLUS_OR_MINUS}${extra_seconds} second[s] [${NANOSECONDS_PLUS_OR_MINUS}${NANOSECONDS} nanosecond(s)]." -} - -sdwdate_log_time_before_setting_time() { - local SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING - SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING="$(date +%s.%N)" - local SDWDATE_NEW_DATE_BEFORE_SETTING - SDWDATE_NEW_DATE_BEFORE_SETTING="$(date)" - sdwdate_log "Time before setting: $SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING [$SDWDATE_NEW_DATE_BEFORE_SETTING]" -} - -sdwdate_log_time_after_setting_time() { - local SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING - SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING="$(date +%s.%N)" - local SDWDATE_NEW_DATE_AFTER_SETTING - SDWDATE_NEW_DATE_AFTER_SETTING="$(date)" - sdwdate_log "Time after setting using: $SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING [$SDWDATE_NEW_DATE_AFTER_SETTING]" -} - -sdwdate_set_time_using_date() { - ## sets: NANOSECONDS - ## sets: NANOSECONDS_PLUS_OR_MINUS - sdwdate_generate_nanoseconds - - sdwdate_log_time_before_setting_time - - ## sets: SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL - sdwdate_get_move_time_nanoseonds_total - - local NEW_UNIXTIME_WITH_NANOSECONDS - NEW_UNIXTIME_WITH_NANOSECONDS="${NEW_UNIXTIME}000000000" - - local SDWDATE_MOVE_TIME_UNIXTIME_WITH_NANOSECONDS - true "SDWDATE_MOVE_TIME_UNIXTIME_WITH_NANOSECONDS="\$\(\( $NEW_UNIXTIME_WITH_NANOSECONDS $NANOSECONDS_PLUS_OR_MINUS $NANOSECONDS \)\)"" - SDWDATE_MOVE_TIME_UNIXTIME_WITH_NANOSECONDS="$(( $NEW_UNIXTIME_WITH_NANOSECONDS $NANOSECONDS_PLUS_OR_MINUS $NANOSECONDS ))" - - ## {{ SDWDATE_NEW_TIME_FORMATTED_FOR_DATE - - local first_ten last_nine first_ten_string_length last_nine_string_length - - ## unixtime - first_ten="${SDWDATE_MOVE_TIME_UNIXTIME_WITH_NANOSECONDS:0:10}" - - ## nanoseconds - last_nine="${SDWDATE_MOVE_TIME_UNIXTIME_WITH_NANOSECONDS:10:19}" - - first_ten_string_length="${#first_ten}" - last_nine_string_length="${#last_nine}" - - if [ ! "$first_ten_string_length" = "10" ]; then - sdwdate_error_handler "$FUNCNAME: first_ten: $first_ten | first_ten_string_length: $first_ten_string_length" || true - fi - - if [ ! "$last_nine_string_length" = "9" ]; then - sdwdate_error_handler "$FUNCNAME: last_nine: $last_nine | last_nine_string_length: $last_nine_string_length" || true - fi - - local SDWDATE_NEW_TIME_FORMATTED_FOR_DATE - ## unixtime.nanoseconds - SDWDATE_NEW_TIME_FORMATTED_FOR_DATE="$first_ten.$last_nine" - - ## Example SDWDATE_NEW_TIME_FORMATTED_FOR_DATE: - ## 1405043428.773182656 - - ## }} SDWDATE_NEW_TIME_FORMATTED_FOR_DATE - - sdwdate_log "Setting time using \`date\` to $SDWDATE_NEW_TIME_FORMATTED_FOR_DATE..." - - ## Set new time. Syntax: date --set @1396733199.112834496 - ## There is an exception in /etc/sudoers.d/sdwdate. - sudo /bin/date --set "@${SDWDATE_NEW_TIME_FORMATTED_FOR_DATE}" > /dev/null - - sdwdate_log_time_after_setting_time - - if [ "$SYSTOHC" = "1" ]; then - sudo /sbin/hwclock --systohc - fi -} - -sdwdate_terminate_sclockadj() { - if [ "$SDWDATE_SUBSHELL_SCLOCKADJ_PID" = "" ]; then - return 0 - fi - - local pid_still_running SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE - SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE="0" - pid_still_running="0" - kill -0 "$SDWDATE_SUBSHELL_SCLOCKADJ_PID" || { pid_still_running="$?" ; true; }; - - if [ "$pid_still_running" = "0" ]; then - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID still running. Terminating..." - kill -sigterm "$SDWDATE_SUBSHELL_SCLOCKADJ_PID" || true - wait "$SDWDATE_SUBSHELL_SCLOCKADJ_PID" || { SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE="$?" ; true; }; - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID Terminated. Exit code: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - else - wait "$SDWDATE_SUBSHELL_SCLOCKADJ_PID" || { SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE="$?" ; true; }; - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID no longer running. Exit code: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - fi - - ## Not required. - ## Done by function sdwdate_subshell_time_end. - #sdwdate_log_time_after_setting_time - - if [ "$SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" = "0" ]; then - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID exited with expected zero exit code: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - elif [ "$SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" = "130" ]; then - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID exited with expected non-zero exit code (sigint): $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - elif [ "$SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" = "143" ]; then - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID exited with expected non-zero exit code (sigterm): $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - else - sdwdate_log "$FUNCNAME: subshell for sclockadj with pid $SDWDATE_SUBSHELL_SCLOCKADJ_PID exited with unexpected non-zero exit code: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - SDWDATE_SUBSHELL_SCLOCKADJ_PID="" - ## Not using sdwdate_error_handler to prevent and endless loop. - sdwdate_error "$FUNCNAME: subshell for sclockadj exited with unexpected non-zero exit code: $SDWDATE_SUBSHELL_SCLOCKADJ_EXIT_CODE" - fi - SDWDATE_SUBSHELL_SCLOCKADJ_PID="" -} - -sdwdate_get_move_time_nanoseonds_total() { - local SDWDATE_MOVE_TIME_SECONDS_TOTAL first_char - - true "SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL="\$\(\( $SDWDATE_MEDIAN_TIME_NANOSECONDS $NANOSECONDS_PLUS_OR_MINUS $NANOSECONDS \)\)"" - SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL="$(( $SDWDATE_MEDIAN_TIME_NANOSECONDS $NANOSECONDS_PLUS_OR_MINUS $NANOSECONDS ))" - - SDWDATE_MOVE_TIME_SECONDS_TOTAL="$(bc -l <<< "scale=2 ; $SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL / 1000000000")" - SDWDATE_MOVE_TIME_SECONDS_TOTAL="$(printf '%.2f\n' "$SDWDATE_MOVE_TIME_SECONDS_TOTAL")" - - first_char=${SDWDATE_MOVE_TIME_SECONDS_TOTAL::1} - if [ ! "$first_char" = "-" ]; then - SDWDATE_MOVE_TIME_SECONDS_TOTAL="+$SDWDATE_MOVE_TIME_SECONDS_TOTAL" - fi - - sdwdate_log "require time change: $SDWDATE_MOVE_TIME_SECONDS_TOTAL second(s) [$SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL nanosecond(s)]" -} - -sdwdate_set_time_using_sclockadj() { - ## sets: NANOSECONDS - ## sets: NANOSECONDS_PLUS_OR_MINUS - sdwdate_generate_nanoseconds - - ## Not required. - ## Done by function sdwdate_subshell_time_start. - #sdwdate_log_time_before_setting_time - - ## sets: SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL - sdwdate_get_move_time_nanoseonds_total - - local first_char - first_char=${SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL::1} - - local sclockadj_add_or_subtract - if [ "$first_char" = "-" ]; then - local SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL_WITHOUT_SIGN - SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL_WITHOUT_SIGN="${SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL#?}" - sclockadj_add_or_subtract="--subtract $SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL_WITHOUT_SIGN" - else - sclockadj_add_or_subtract="--add $SDWDATE_MOVE_TIME_NANOSECONDS_TOTAL" - fi - - ## Setting the INLINEDIR environment variable for ruby-inline, that requires - ## a non-group writeable cache dir. - - local SDWDATE_SCLOCKADJ_COMMAND - ## There is an exception in /etc/sudoers.d/. - SDWDATE_SCLOCKADJ_COMMAND="\ - sudo \ - INLINEDIR=$SDW_CACHE_DIR \ - /usr/lib/sdwdate/sclockadj \ - $SDWDATE_SCLOCKADJ_VERBOSE \ - $SDWDATE_SCLOCKADJ_CHANGE_DATE \ - $SDWDATE_SCLOCKADJ_FIRST_WAIT \ - $SDWDATE_SCLOCKADJ_SYSTOHC \ - --move-min $SDWDATE_SCLOCKADJ_MOVE_MIN \ - --move-max $SDWDATE_SCLOCKADJ_MOVE_MAX \ - --wait-min $SDWDATE_SCLOCKADJ_WAIT_MIN \ - --wait-max $SDWDATE_SCLOCKADJ_WAIT_MAX \ - $sclockadj_add_or_subtract \ - " - - sdwdate_log "Launching into background: $SDWDATE_SCLOCKADJ_COMMAND" - - sclockadj_subshell_fifo="$TEMP_DIR/sclockadj.sclockadj_subshell_fifo" - rm --force "$sclockadj_subshell_fifo" - mkfifo "$sclockadj_subshell_fifo" - test -r "$sclockadj_subshell_fifo" - - { - sdwdate_subshell_time_start() { - SDWDATE_SUBSHELL_SCLOCKADJ_TIME_START="$(date +%s)" - local SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING - SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING="$(date +%s.%N)" - local SDWDATE_NEW_DATE_BEFORE_SETTING - SDWDATE_NEW_DATE_BEFORE_SETTING="$(date)" - echo "$FUNCNAME: Time before running sclockadj: $SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_BEFORE_SETTING [$SDWDATE_NEW_DATE_BEFORE_SETTING]" - } - - sdwdate_subshell_time_end() { - SDWDATE_SUBSHELL_SCLOCKADJ_TIME_END="$(date +%s)" - true "SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_SECONDS="\$\(\( $SDWDATE_SUBSHELL_SCLOCKADJ_TIME_END - $SDWDATE_SUBSHELL_SCLOCKADJ_TIME_START \)\)"" - SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_SECONDS="$(( $SDWDATE_SUBSHELL_SCLOCKADJ_TIME_END - $SDWDATE_SUBSHELL_SCLOCKADJ_TIME_START ))" - SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES="$(bc -l <<< "scale=2 ; $SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_SECONDS / 60")" - SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_HOURS="$(bc -l <<< "scale=2 ; $SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES / 60")" - ## Add leading zero. - SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES="$(printf '%.2f\n' "$SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES")" - SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_HOURS="$(printf '%.2f\n' "$SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_HOURS")" - echo "$FUNCNAME: was running for $SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_SECONDS s [~ $SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES min] \ -[~ $SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_HOURS h]." - unset SDWDATE_SUBSHELL_SCLOCKADJ_TIME_START - unset SDWDATE_SUBSHELL_SCLOCKADJ_TIME_END - unset SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_SECONDS - unset SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_MINUTES - unset SDWDATE_SUBSHELL_SCLOCKADJ_TOOK_TIME_HOURS - local SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING - SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING="$(date +%s.%N)" - local SDWDATE_NEW_DATE_AFTER_SETTING - SDWDATE_NEW_DATE_AFTER_SETTING="$(date)" - echo "$FUNCNAME: Time after running sclockadj: $SDWDATE_NEW_UNIXTIME_AND_NANOSECONDS_AFTER_SETTING [$SDWDATE_NEW_DATE_AFTER_SETTING]" - } - - sdwdate_subshell_wait() { - echo "$FUNCNAME: Waiting for pid to finish SDWDATE_SCLOCKADJ_COMMAND_PID: $SDWDATE_SCLOCKADJ_COMMAND_PID" - SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE="0" - wait "$SDWDATE_SCLOCKADJ_COMMAND_PID" || { SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE="$?" ; true; }; - echo "$FUNCNAME: SDWDATE_SCLOCKADJ_COMMAND_PID: $SDWDATE_SCLOCKADJ_COMMAND_PID | SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE: $SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE" - } - - sdwdate_subshell_trap_sigterm() { - echo "$FUNCNAME: SIGTERM received." - ## Interrupted sdwdate_subshell_wait. - if [ "$SDWDATE_SCLOCKADJ_COMMAND_PID" = "" ]; then - return 0 - fi - ## There is a /etc/sudoers.d exception. - echo "$FUNCNAME: executing: sudo /usr/lib/sdwdate/sclockadj_kill_helper $SDWDATE_SCLOCKADJ_COMMAND_PID" - sclockadj_kill_helper_output="$(sudo /usr/lib/sdwdate/sclockadj_kill_helper "$SDWDATE_SCLOCKADJ_COMMAND_PID" 2>&1)" - echo "$FUNCNAME: sclockadj_kill_helper_output: $sclockadj_kill_helper_output" - unset sclockadj_kill_helper_output - } - - sdwdate_subshell_exit() { - echo "exit code: $SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE" - exit "$SDWDATE_SCLOCKADJ_COMMAND_EXIT_CODE" - } - - sdwdate_subshell_main() { - ## There is a /etc/sudoers.d exception. - sudo /usr/lib/sdwdate/sclockadj_debug_helper || true - sdwdate_subshell_time_start - $SDWDATE_SCLOCKADJ_COMMAND 2>&1 & - SDWDATE_SCLOCKADJ_COMMAND_PID="$!" - sdwdate_subshell_wait ## Might get interrupted by signal sigterm. - sdwdate_subshell_time_end - sdwdate_subshell_exit - } - - trap "sdwdate_subshell_trap_sigterm" SIGTERM - - sdwdate_subshell_main - } > "$sclockadj_subshell_fifo" 2>&1 & - - SDWDATE_SUBSHELL_SCLOCKADJ_PID="$!" - sdwdate_log "Started subshell for sclockadj with pid: $SDWDATE_SUBSHELL_SCLOCKADJ_PID" - - { - sdwdate_log "sdwdate_subshell_read: start" - while read -r sclockadj_line; do - sdwdate_log "sdwdate_subshell_read: sclockadj reports: $sclockadj_line" - done < "$sclockadj_subshell_fifo" - sdwdate_log "sdwdate_subshell_read: end" - } & -} - -sdwdate_maybe_set_new_time() { - sdwdate_terminate_sclockadj - - if [ "$DONT_SET_DATE" = "1" ]; then - return 0 - fi - - ## Do not needlessly try to change time, when there is no difference. - if [ "$SDWDATE_MEDIAN_TIME_SECONDS" = "0" ]; then - sdwdate_log "require time change: 0 second(s) [0 nanosecond(s)]" - sdwdate_log "No need to set clock." - return 0 - fi - - if [ "$NO_MOVE_FORWARD" = "1" ]; then - if [ "$SDWDATE_MEDIAN_TIME_SECONDS" -gt "0" ]; then - sdwdate_log "Not setting clock forward." - return 0 - fi - fi - - if [ "$NO_MOVE_BACKWARDS" = "1" ]; then - if [ "$SDWDATE_MEDIAN_TIME_SECONDS" -lt "0" ]; then - sdwdate_log "Not setting clock backwards." - return 0 - fi - fi - - if [ "$SDW_MODE" = "startup" ]; then - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP" = "1" ]; then - sdwdate_set_time_using_sclockadj - else - sdwdate_set_time_using_date - fi - elif [ "$SDW_MODE" = "restartup" ]; then - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP" = "1" ]; then - sdwdate_set_time_using_sclockadj - else - sdwdate_set_time_using_date - fi - elif [ "$SDW_MODE" = "sighup" ]; then - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP" = "1" ]; then - sdwdate_set_time_using_sclockadj - else - sdwdate_set_time_using_date - fi - elif [ "$SDW_MODE" = "daemon" ]; then - if [ "$SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON" = "1" ]; then - sdwdate_set_time_using_sclockadj - else - sdwdate_set_time_using_date - fi - else - sdwdate_error_handler 'Invalid SDW_MODE.' - fi -} - -sdwdate_failed() { - rm --force "$SUCCESS_FILE" - touch "$DONE_FILE" -} - -sdwdate_success() { - touch "$FIRST_SUCCESS_FILE" - touch "$SUCCESS_FILE" - touch "$DONE_FILE" -} - -sdwdate_dispatcher() { - local dispatcher_variable_name dispatcher_variable_content - - dispatcher_variable_name="$1" - dispatcher_variable_content="$(eval echo \"\$"$dispatcher_variable_name"\")" - - if [ "$dispatcher_variable_content" = "" ]; then - sdwdate_log "$dispatcher_variable_name dispatcher_variable_content is empty, skipping." - return 0 - fi - - if [ "$dispatcher_variable_name" = "DISPATCH_PREREQUISITE" ]; then - while true; do - sdwdate_no_duplicate_log "dispatching $dispatcher_variable_name (SDW_MODE: $SDW_MODE) (LD_PRELOAD: $LD_PRELOAD): $DISPATCH_PREREQUISITE" - - local prerequisite_exit_code - prerequisite_exit_code="0" - prerequisite_output="$(eval $dispatcher_variable_content 2>&1)" || { prerequisite_exit_code="${PIPESTATUS[0]}" ; true; }; - - true "prerequisite_exit_code: $prerequisite_exit_code" - true "prerequisite_output: $prerequisite_output" - - if [ "$prerequisite_exit_code" = "0" ]; then - ## Ok. - sdwdate_log "$dispatcher_variable_name exited $prerequisite_exit_code, continuing..." - break - elif [ "$prerequisite_exit_code" = "1" ]; then - ## Error. - sdwdate_error_handler "$dispatcher_variable_name exited $prerequisite_exit_code | $prerequisite_output | requested explicit exiting..." || true - elif [ "$prerequisite_exit_code" = "2" ]; then - ## Wait. - sdwdate_no_duplicate_log "$dispatcher_variable_name exited $prerequisite_exit_code | $prerequisite_output | waiting..." - sleep 10 & - sleep_pid="$!" - wait "$sleep_pid" || true - unset sleep_pid - else - ## Unexpected error. - sdwdate_error_handler "$dispatcher_variable_name exited $DISPATCH_PREREQUISITE, neither 0, 1, or 2. | $prerequisite_output | unexpected error, exiting..." || true - fi - done - return 0 - fi - - sdwdate_log "dispatching $dispatcher_variable_name (SDW_MODE: $SDW_MODE): $dispatcher_variable_content" - - if [ "$dispatcher_variable_name" = "DISPATCH_POST_ERROR" ]; then - sdwdate_log "SDW_EXIT_ON_ERROR: $SDW_EXIT_ON_ERROR" || true - eval_output="$(eval $dispatcher_variable_content 2>&1)" || true - #eval_output="$(eval bash -x $dispatcher_variable_content 2>&1)" || true - #sdwdate_write_log "eval_output: $eval_output" || true - sdwdate_log "dispatching $dispatcher_variable_name done." || true - else - eval_output="$(eval $dispatcher_variable_content 2>&1)" - #eval_output="$(eval bash -x $dispatcher_variable_content 2>&1)" - #sdwdate_write_log "eval_output: $eval_output" - sdwdate_log "dispatching $dispatcher_variable_name done." - fi -} - -sdwdate_error_test() { - if [ -f "/var/lib/sdw_error" ]; then - sdwdate_error_handler "Test file /var/lib/sdw_error to force test an error exists." || true - sleep 10 & - sleep_pid="$!" - wait "$sleep_pid" || true - unset sleep_pid - fi -} - -sdwdate_sleep() { - ## INTERVAL: in minutes - ## sleep: in seconds - - local minutes - if [ "$RANDOMIZE" = "1" ]; then - test -e "/dev/random" - minutes="$(( ($(od -An -N2 -i "/dev/random") )%($INTERVAL) ))" - if [ "$MIN_INTERVAL" -ge "$minutes" ]; then - true "minutes="\$\(\( $minutes + $MIN_INTERVAL \)\)"" - minutes="$(( $minutes + $MIN_INTERVAL ))" - fi - else - minutes="$INTERVAL" - fi - - local seconds - true "seconds="\$\(\( $minutes \* 60 \)\)"" - seconds="$(( $minutes * 60 ))" - - sdwdate_log "Sleeping for $minutes minutes. (RANDOMIZE: $RANDOMIZE)" - - sleep "$seconds" & - sleep_pid="$!" - wait "$sleep_pid" || true - unset sleep_pid -} - -sdwdate_maybe_read_config_folder() { - local grep_exit_code - grep_exit_code="$?" - echo "${1+"$@"}" | grep --quiet "\-\-noconfig" || { grep_exit_code="$?" ; true; }; - if [ "$grep_exit_code" = "0" ]; then - true "Found --noconfig, skip reading config folder." - else - ## Not found, let's read config folder. - sdwdate_read_config_folder - fi -} - -sdwdate_maybe_parse_cmd_options() { - if [ "$PARSE_CMD_ONLY_ON_STARTUP" = "1" ]; then - if [ "$ALREADY_PARSED_CDM_ON_STARTUP" = "1" ]; then - true "ALREADY_PARSED_CDM_ON_STARTUP is 1, not parsing cmd options again." - else - ALREADY_PARSED_CDM_ON_STARTUP="1" - sdwdate_parse_cmd_options - fi - else - sdwdate_parse_cmd_options ${1+"$@"} - fi -} - -sdwdate_maybe_sleep_or_exit() { - ## Check, if we are running in daemon mode. - if [ "$INTERVAL" = "" ] || [ "$INTERVAL" = "0" ]; then - ## No daemon mode. - exit 0 - else - ## Daemon mode. - SDW_MODE="daemon" - sdwdate_sleep - fi -} - -sdwdate_main() { - sdwdate_sanity_tests - - while true; do - sdwdate_maybe_read_config_folder ${1+"$@"} - sdwdate_maybe_parse_cmd_options ${1+"$@"} - sdwdate_defaults - sdwdate_log "Running sdwdate... pid: $$ | LD_PRELOAD: $LD_PRELOAD" - sdwdate_preparation - if [ "$DEBUG" = "1" ]; then - sdwdate_enable_debugging - fi - - sdwdate_error_test - - sdwdate_dispatcher "DISPATCH_PRE" - - sdwdate_pick - - ## Do not continue, if an error was caught. - if [ ! "$sdwdate_loop_return" = "0" ]; then - sdwdate_failed - sdwdate_dispatcher "DISPATCH_POST_FAILURE" - else - sdwdate_build_median - sdwdate_maybe_set_new_time - sdwdate_success - sdwdate_dispatcher "DISPATCH_POST_SUCCESS" - fi - - sdwdate_maybe_sleep_or_exit - done -} diff --git a/usr/lib/sdwdate/restart_fresh b/usr/lib/sdwdate/restart_fresh deleted file mode 100755 index c61b1795..00000000 --- a/usr/lib/sdwdate/restart_fresh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -set -e - -rm --force /var/run/sdwdate/first_success diff --git a/usr/lib/sdwdate/sclockadj b/usr/lib/sdwdate/sclockadj deleted file mode 100755 index e7ef35db..00000000 --- a/usr/lib/sdwdate/sclockadj +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env ruby - -module SneakyClockAdjusterCLI - require 'optparse' - require 'ostruct' - - def self.parse args - options = OpenStruct.new - options.add_or_substract = :add - options.amount = nil - options.move_min = nil - options.move_max = nil - options.wait_min = nil - options.wait_max = nil - options.first_wait = false - options.verbose = true - options.debug = false - options.systohc = false - - opt_parser = OptionParser.new do |opts| - opts.banner = "" - opts.separator "Specific options:" - - opts.on("--add num", Integer, "(Required) Nanoseconds to add") do |v| - options.add_or_substract = :add - options.amount = v - end - opts.on("--subtract num", Integer, "(Required) Nanoseconds to subtract") do |v| - options.add_or_substract = :subtract - options.amount = v - end - opts.on("--wait-min num", Integer, "(Required) Minimum random seconds to wait") do |v| - options.wait_min = v - end - opts.on("--wait-max num", Integer, "(Required) Maximum random seconds to wait") do |v| - options.wait_max = v - end - opts.on("--move-min num", Integer, "(Required) Minimum random nanoseconds to change") do |v| - options.move_min = v - end - opts.on("--move-max num", Integer, "(Required) Maximum random nanoseconds to change") do |v| - options.move_max = v - end - opts.on("--[no-]first-wait", "Wait before first change?") do |v| - options.first_wait = v - end - opts.on("--[no-]verbose", "Run Verbosely?") do |v| - options.verbose = v - end - opts.on("--[no-]debug", "Debug messages. Don't change date.") do |v| - options.debug = v - end - opts.on("--[no-]systohc", "Update hardware clock.") do |v| - options.systohc = v - end - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit - end - - end - - opt_parser.parse! - if options.amount == nil then puts opt_parser; exit 1 end - if options.move_min == nil then puts opt_parser; exit 1 end - if options.move_max == nil then puts opt_parser; exit 1 end - if options.wait_min == nil then puts opt_parser; exit 1 end - if options.wait_max == nil then puts opt_parser; exit 1 end - if options.amount == nil then puts opt_parser; exit 1 end - if options.amount <= 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end - if options.wait_min < 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end - if options.wait_max < options.wait_min then raise OptionParser::InvalidArgument, "Max must > min!" end - if options.move_min < 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end - if options.move_max < options.move_min then raise OptionParser::InvalidArgument, "Max must > min!" end - - return options - end # self.parse -end # module SneakyClockAdjusterCLI - -module SneakyClockAdjuster - require 'openssl' - require 'securerandom' - require 'bigdecimal' - require 'bigdecimal/util' - require 'inline' - - def self.execute add_or_substract, amount, move_min, move_max, wait_min, wait_max, first_wait = false, verbose = true, debug = false, systohc = false - - puts "Running with PID: #{Process.pid}" if verbose - Kernel.trap("TERM") do - puts "Exiting..." - exit 143 - end - Kernel.trap("INT") do - puts "Exiting..." - exit 130 - end - - jumps = make_jumps amount, move_min, move_max - jumps_c = jumps.count - intervals = make_intervals jumps_c, wait_min, wait_max - if not first_wait then intervals[0] = 0 end - if debug - puts "DEBUG: Using these sleep intervals: #{intervals}" - puts "DEBUG: with these time jumps: #{jumps}" - end - - c = Cinline.new - - jumps.each_index do |index| - - if verbose - puts "---" - puts "Iteration: #{index+1} of #{jumps_c}" - end - - wait_ns = intervals[index] - wait_s = wait_ns.to_f / 1000000000 - jump_ns = jumps[index] - jump_s = jump_ns.to_f / 1000000000 - - if wait_ns > 0 then - puts "Waiting #{wait_ns} nanoseconds [#{wait_s} seconds]" if verbose - sleep(wait_s) - end - - if verbose - print "Aproximate system date with nanoseconds: " - puts %x[echo "$(date) | $(date +%N)"] - end - - if add_or_substract == :add then - puts "Adding #{jump_ns} nanoseconds [#{jump_s} seconds]" if verbose - else - puts "Subtracting #{jump_ns} nanoseconds [#{jump_s} seconds]" if verbose - jump_ns = -jump_ns - end - - if not debug - c_return_code = c.getAndSet(jump_ns) - puts "c_return_code: #{c_return_code}" if verbose - if c_return_code != 0 then - warn "ERROR: c_return_code was: #{c_return_code}" - exit c_return_code - end - end - - if systohc then - cmd = "hwclock --systohc" - puts "Set hwclock with: #{cmd}" if verbose - system cmd unless debug - end - - if verbose - print "Aproximate system date with nanoseconds: " - puts %x[echo "$(date) | $(date +%N)"] - end - - end # jumps.each_index - - if verbose - puts "---" - puts "Done! Exiting..." - end - end - - def self.make_intervals count, min, max, intervals = [] - count.times do - intervals << (SecureRandom.random_number max-min+1) + min - end - return intervals - end - - def self.make_jumps amount, min, max, jumps = [] - loop do - if amount < 1 then return jumps end - if amount < min then return jumps << amount end - - if amount - max < 1 then max = amount end - random_jump = (SecureRandom.random_number max-min+1) + min - jumps << random_jump - amount -= random_jump - end - end -end # module SneakyClockAdjuster - -class Cinline - - inline :C do |builder| - builder.include '' - builder.include '' - builder.include '' - builder.c ' - int getAndSet(long long addNsec) { - /* receive time adjustment, negative or positive, in nanoseconds */ - - /* get current time in seconds since epoch + nanoseconds offset */ - struct timespec tps; /* tv_sec; tv_nsec */ - if( clock_gettime(0, &tps) == -1 ) { - perror( "getclock" ); - return EXIT_FAILURE; - } - - /* combine seconds and nanoseconds offset to manipulate */ - long long nanosecondsSinceEpoch = - (long long)(tps.tv_sec) * 1000000000 + /* convert seconds to nanoseconds */ - (long long)(tps.tv_nsec); /* add offset */ - - long long newNanosecondsSinceEpoch = nanosecondsSinceEpoch += addNsec; - - /* separate adjusted nanoseconds since epoch into seconds + nanoseconds offset */ - long newNS = newNanosecondsSinceEpoch % 1000000000; /* pulls out nanoseconds */ - long newS = newNanosecondsSinceEpoch / 1000000000; /* truncates into seconds */ - - /* set struct with new values; set time */ - tps.tv_sec = newS; /* reusing old struct */ - tps.tv_nsec = newNS; - if( clock_settime(0, &tps) == -1 ) { - perror( "setclock" ); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; - } - ' - end - -end - -options = SneakyClockAdjusterCLI.parse(ARGV) -SneakyClockAdjuster.execute options.add_or_substract, options.amount, options.move_min, options.move_max, options.wait_min, options.wait_max, options.first_wait, options.verbose, options.debug diff --git a/usr/lib/sdwdate/sclockadj_debug_helper b/usr/lib/sdwdate/sclockadj_debug_helper deleted file mode 100755 index 936d697d..00000000 --- a/usr/lib/sdwdate/sclockadj_debug_helper +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -set -x - -find /var/cache/sdwdate -printf "%p %u %g %m\n" -true "\$?: $?" diff --git a/usr/lib/sdwdate/sclockadj_kill_helper b/usr/lib/sdwdate/sclockadj_kill_helper deleted file mode 100755 index f288df19..00000000 --- a/usr/lib/sdwdate/sclockadj_kill_helper +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -pid="$1" - -if [[ "$pid" != *[!0-9]* ]]; then - true "pid '$pid' is strictly numeric." -else - echo "pid '$pid' is NOT strictly numeric!" - exit 1 -fi - -ps_output="$(ps h --format command "$pid")" - -## Example ps_output: -## sudo INLINEDIR=/var/cache/sdwdate/sclockadj /usr/lib/sdwdate/sclockadj --no-verbose --no-debug --no-first-wait --move-min 500000 --move-max 500000 --wait-min 1000000000 --wait-max 1000000000 --add 172807620812 - -read -t 2 first second third _ <<< "$ps_output" - -## Example first: -## sudo - -## Example third: -## /usr/lib/sdwdate/sclockadj - -if [ "$first" = "sudo" ]; then - if [ "$third" = "/usr/lib/sdwdate/sclockadj" ]; then - echo "kill -sigterm $pid" - kill -sigterm "$pid" - exit 0 - fi -fi - -echo "Nothing killed. ps_output: $ps_output" -exit 0 diff --git a/usr/lib/sdwdate/url_to_unixtime b/usr/lib/sdwdate/url_to_unixtime deleted file mode 100755 index bb95dabb..00000000 --- a/usr/lib/sdwdate/url_to_unixtime +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/python - -## Copyright (C) 2015 troubadour -## Copyright (C) 2015 Patrick Schleizer -## See the file COPYING for copying conditions. - -## Usage: -## /usr/lib/sdwdate/url_to_unixtime socket_ip socket_port url remote_port verbosity - -## Example: -## /usr/lib/sdwdate/url_to_unixtime 127.0.0.1 9050 check.torproject.org 80 true - -import sys, socks -from dateutil.parser import parse - -def data_to_date_string_start_position(data): - date_string_start_position = data.find('Date:') - - if date_string_start_position == -1: - ## not found, check if lowercase. - date_string_start_position = data.find('date:') - - if date_string_start_position == -1: - ## "Date:" not found. - print >> sys.stderr, 'Parsing HTTP header date failed.' - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - sys.exit(3) - - date_string_start_position = date_string_start_position + 6 - return date_string_start_position - -def data_to_http_time(data, date_string_start_position): - http_time = '' - ## max accepted string length. - http_time = data[date_string_start_position:date_string_start_position + 29].strip() - - http_time_string_length = len(http_time) - - ## min string length = max string length. - if http_time_string_length < 29: - print >> sys.stderr, 'HTTP header date string too short.' - print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) - sys.exit(4) - - return http_time - -def unixtime_sanity_check(data, http_time, parsed_unixtime): - try: - unixtime_digit = int(parsed_unixtime) - - except ValueError as e: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'parsed_unixtime not numeric!' - sys.exit(6) - - unixtime_string_length_is = len(parsed_unixtime) - unixtime_string_length_max = 10 - - if unixtime_string_length_is > unixtime_string_length_max: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) - print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) - print >> sys.stderr, 'parsed_unixtime has excessive string length!' - sys.exit(7) - - return parsed_unixtime - -def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): - s = socks.socksocket() - s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) - - try: - s.connect((url, remote_port)) - - except Exception as e: - ## {{ wheezy compatibility - if str(e).startswith('__init__'): - print >> sys.stderr, 'connect error: URL "%s" not found.' % url - else: - ## }} - print >> sys.stderr, 'connect error: %s' % (e) - sys.exit(2) - - s.send('HEAD / HTTP/1.0\r\n\r\n') - - data = '' - buf = s.recv(1024) - while len(buf): - data += buf - buf = s.recv(1024) - s.close() - - return data - -def http_time_to_parsed_unixtime(data, http_time): - try: - ## Thanks to: - ## eumiro - ## http://stackoverflow.com/a/3894047/2605155 - parsed_unixtime = parse(http_time).strftime('%s') - - except ValueError as e: - print >> sys.stderr, 'Parsing http_time from server failed!' - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'dateutil ValueError: %s' % (e) - sys.exit(5) - - return(parsed_unixtime) - -def parse_command_line_parameters(): - try: - socket_ip = sys.argv[1] - socket_port = int(sys.argv[2]) - url = sys.argv[3] - remote_port = int(sys.argv[4]) - verbosity = sys.argv[5] - - except (IndexError) as e: - print >> sys.stderr, "Parsing command line parameter failed. | e: %s" % (e) - sys.exit(1) - - return(socket_ip, socket_port, url, remote_port, verbosity) - -def output_unixtime(data, http_time, parsed_unixtime, unixtime, verbosity): - if verbosity == "true": - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print "%s" % unixtime - -def main(): - socket_ip, socket_port, url, remote_port, verbosity = parse_command_line_parameters() - data = request_data_from_remote_server(socket_ip, socket_port, url, remote_port) - date_string_start_position = data_to_date_string_start_position(data) - http_time = data_to_http_time(data, date_string_start_position) - parsed_unixtime = http_time_to_parsed_unixtime(data, http_time) - unixtime = unixtime_sanity_check(data, http_time, parsed_unixtime) - output_unixtime(data, http_time, parsed_unixtime, unixtime, verbosity) - -main() diff --git a/usr/lib/tmpfiles.d/sdwdate.conf b/usr/lib/tmpfiles.d/sdwdate.conf deleted file mode 100644 index 31832d73..00000000 --- a/usr/lib/tmpfiles.d/sdwdate.conf +++ /dev/null @@ -1,6 +0,0 @@ -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -d /var/run/sdwdate 0775 sdwdate sdwdate -f /var/log/sdwdate.log 0775 sdwdate sdwdate diff --git a/usr/share/sdwdate/unit_test b/usr/share/sdwdate/unit_test deleted file mode 100755 index b2ecf702..00000000 --- a/usr/share/sdwdate/unit_test +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash - -#set -x - -echo "-------------------- Start Unit Test --------------------" - -set -e -set -o pipefail -set -o errtrace - -echo "-------------------- Begin Sourcing --------------------" - -for i in /usr/lib/sdwdate/modules.d/* /etc/sdwdate.d/*; do - if [ -f "$i" ]; then - ## If the last character is a ~, ignore that file, - ## because it was created by some editor, - ## which creates backup files. - if [ "${i: -1}" = "~" ]; then - continue - fi - ## Skipping files such as .dpkg-old and .dpkg-dist. - if ( echo "$i" | grep -q ".dpkg-" ); then - true "skip $i" - continue - fi - bash -n "$i" - source "$i" - fi -done - -echo "-------------------- End Sourcing --------------------" - -echo "-------------------- Begin Setup for Unit Test --------------------" - -sdwdate_error_handler() { - local exit_code="$?" - local error_cause error_text - error_text="$1" - if [ "$error_text" = "" ]; then - error_cause="$FUNCNAME signal ERR detected with BASH_COMMAND: -$BASH_COMMAND" - else - error_cause="$FUNCNAME called with error_text: -$error_text" - fi - exit 1 -} - -trap "sdwdate_error_handler" ERR - -sdwdate_log() { - true "$FUNCNAME (unit test): $@" -} - -sdwdate_no_duplicate_log() { - true "$FUNCNAME (unit test): $@" -} - -echo "-------------------- End Setup for Unit Test --------------------" - -echo "-------------------- Begin Unit Test 1 --------------------" - -download_unit_test() { - echo "-------------------- -------------------- --------------------" - - #sdwdate_defaults - - TEMP_DIR="/tmp/test" - SDWDATE_CURRENT_POOL="testpool" - mkdir --parents "$TEMP_DIR" - touch "$TEMP_DIR/$SDWDATE_CURRENT_POOL" - - declare -A -g sdwdate_download_tool_exit_code - declare -A -g SDWDATE_DOWNLOAD_TOOL_PID - declare -A -g SDWDATE_DOWNLOAD_TOOK_TIME - - for element in "${SDWDATE_POOL_ONE[@]}" "${SDWDATE_POOL_TWO[@]}" "${SDWDATE_POOL_THREE[@]}"; do - echo "element: $element" - number_lines_counter="0" - while read -r -d $'\n' line; do - line_without_spaces="${line// /}" - if [ "$line_without_spaces" = "" ]; then - ## Skipping empty or lines with leading/trailing spaces (such as '" '). - continue - fi - number_lines_counter="$(( $number_lines_counter + 1 ))" - - link_unprocessed="$(sdwdate_pick_single_link_from_maybe_multi_lined_mirror_list "$element")" - echo "link_unprocessed: $link_unprocessed" - - ## sets: link_url_part - ## sets: link_comment_part - ## sets: link_url - ## sets: link_port - sdwdate_link_with_comments_to_processed_link - - echo "\${link_url}:\${link_port}: ${link_url}:${link_port}" - sdwdate_get_time_from_remote - if [ ! "${sdwdate_download_tool_exit_code[$SDWDATE_CURRENT_POOL]}" = "0" ]; then - echo "FAILED : ${link_url}:${link_port}" - else - echo "SUCCESS: ${link_url}:${link_port}" - fi - echo "----------" - done < <( echo "$element" ) - done -} - -download_unit_test From 2c280089c141116fdd7fd50000e9e2e55e398074 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 23 Jun 2015 18:41:28 +0000 Subject: [PATCH 004/183] + usr/lib/python2.7/dist-packages/sdwdate --- .../dist-packages/sdwdate/sdwdate_python | 166 ++++++++++++++++++ .../dist-packages/sdwdate/url_to_unixtime.py | 164 +++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100755 usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python new file mode 100755 index 00000000..1a01e7b4 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python @@ -0,0 +1,166 @@ +#!/usr/bin/env python + +import sys +from url_to_unixtime import url_to_unixtime +import time, random + +#import config + +pool_one = ['dtsxnd3ykn32ywv6.onion', + 'znig4bc5rlwyj4mz.onion', + 'vtjkwwcq5osuo6uq.onion', + '33y6fjyhs3phzfjj.onion', + 'y6xjgkgwj47us5ca.onion', + 'strngbxhwyuu37a3.onion', + 'swdi5ymnwmrqhycl.onion', + 'dqeasamlf3jld2kz.onion', + 'pubdrop4dw6rk3aq.onion', + 'hkjpnjbvhrxjvikd.onion', + 'v6gdwmm7ed4oifvd.onion', + 'vbmwh445kf3fs2v4.onion', + 'poulsensqiv6ocq4.onion', + 'tigas3l7uusztiqu.onion', + 'w6csjytbrl273che.onion', + 'ak2uqfavwgmjrvtu.onion'] + +pool_two = ['yn6ocmvu4ok3k3al.onion', + 'acabtd4btrxjjrvr.onion', + '5r4bjnjug3apqdii.onion', + '2dermafialks7aai.onion', + 'ymi7h25hgp3bj63v.onion', + 'ppdz5djzpo3w5k2z.onion', + 'pltloztihmfrg2sw.onion', + 'ur5b2b4brz427ygh.onion', + 'abkjckdgoabr7bmm.onion', + 'bqs3dobnazs7h4u4.onion', + 'fkut2p37apcg6l7f.onion', + '6iolddfbfinntq2b.onion', + 'nzh3fv6jc6jskki3.onion'] + +pool_three = ['cwoiopiifrlzcuos.onion', + 'zsolxunfmbfuq7wf.onion', + 'yfm6sdhnfbulplsw.onion', + 'j6uhdvbhz74oefxf.onion', + '3g2upl4pq6kufc4m.onion', + 'dju2peblv7upfz3q.onion', + 'msydqstlz2kzerdg.onion', + 'uj3wazyk5u4hnvtk.onion', + 'wi7qkxyrdpu5cmvr.onion', + 'ic6au7wa3f6naxjq.onion', + 'timaq4ygg2iegci7.onion', + '344c6kbnjnljjzlz.onion', + 'fncuwbiisyh6ak3i.onion'] + +returned_unixtimes = 0 + +already_picked_index_pool_one = [] +already_picked_index_pool_two = [] +already_picked_index_pool_three = [] +already_picked_index_dummy_pool = [] + +pool_one_done = False +pool_two_done = False +pool_three_done = False +dummy_pool_done = False +url_random_pool_one = [] +url_random_pool_two = [] +url_random_pool_three = [] +url_random_dummy_pool = [] + +print 'Start %s' % (time.time()) + +while returned_unixtimes < 3: + urls = [] + unixtimes = [] + url_random = [] + + random.seed() + + url_index = [] + + if not pool_one_done: + while True: + ## Pick a random pool index. + url_index = random.sample(xrange(len(pool_one)), 1) + + ## Reset to prevent infinite loop, next condition would never be met, + ## probably raise an error before that happens. + if len(already_picked_index_pool_one) == len(pool_one): + already_picked_index_pool_one = [] + url_random_pool_one = [] + + ## Was this index used? + if url_index not in already_picked_index_pool_one: + ## No, add to used indexes. + already_picked_index_pool_one.append(url_index) + ## Add it to the pool url list. + url_random_pool_one.append(pool_one[url_index[0]]) + ## Add it to the url list to fetch. + url_random.append(pool_one[url_index[0]]) + break + + if not pool_two_done: + while True: + url_index = random.sample(xrange(len(pool_two)), 1) + + if len(url_random_pool_two) == len(pool_two): + already_picked_index_pool_two = [] + url_random_pool_two = [] + + if url_index not in already_picked_index_pool_two: + already_picked_index_pool_two.append(url_index) + url_random_pool_two.append(pool_two[url_index[0]]) + url_random.append(pool_two[url_index[0]]) + break + + if not pool_three_done: + while True: + url_index = random.sample(xrange(len(pool_three)), 1) + + if len(url_random_pool_three) == len(pool_three): + already_picked_index_pool_three = [] + url_random_pool_three = [] + + if url_index not in already_picked_index_pool_three: + already_picked_index_pool_three.append(url_index) + url_random_pool_three.append(pool_three[url_index[0]]) + url_random.append(pool_three[url_index[0]]) + break + + print 'pool 1 picked urls %s' % url_random_pool_one + print 'pool 2 picked urls %s' % url_random_pool_two + print 'pool 3 picked urls %s' % url_random_pool_three + print 'random urls %s' % url_random + + ## Fetch remotes. + if len(url_random) > 0: + urls, unix_times = url_to_unixtime(url_random) + else: + ## Add code here. + sys.exit(1) + + ## We can [safely?] infer that something is wrong with tor. + if (urls[0] == 'Connection closed unexpectedly' and + urls[1] == 'Connection closed unexpectedly' and + urls[2] == 'Connection closed unexpectedly'): + ## Raise error, log, user warning. + print urls[0] + sys.exit(1) + + if not pool_one_done: + ## Is last element in the pool in returned urls? + pool_one_done = url_random_pool_one[len(url_random_pool_one) - 1] in urls + + if not pool_two_done: + pool_two_done = url_random_pool_two[len(url_random_pool_two) - 1] in urls + + if not pool_three_done: + pool_three_done = url_random_pool_three[len(url_random_pool_three) - 1] in urls + + returned_unixtimes = len(urls) + print 'Returned unixtimes: %s' % (returned_unixtimes) + +for i in range(0, len(urls)): + print '"%s" %s' % (urls[i], unix_times[i]) + +print 'End %s' % (time.time()) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py new file mode 100644 index 00000000..ea7cf201 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +## Copyright (C) 2015 troubadour +## Copyright (C) 2015 Patrick Schleizer +## See the file COPYING for copying conditions. + +import sys +import gevent.monkey +gevent.monkey.patch_socket() +from gevent import Timeout +import socks +from dateutil.parser import parse + +import time + +urls = [] +unix_times = [] + +def unixtime_sanity_check(data, http_time, parsed_unixtime, url): + try: + unixtime_digit = int(parsed_unixtime) + + except ValueError as e: + print >> sys.stderr, 'parsed_unixtime conversion failed!' + print >> sys.stderr, 'data: %s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) + print >> sys.stderr, 'parsed_unixtime not numeric!' + return + #exit(6) + + unixtime_string_length_is = len(parsed_unixtime) + unixtime_string_length_max = 10 + + if unixtime_string_length_is > unixtime_string_length_max: + print >> sys.stderr, 'parsed_unixtime conversion failed!' + print >> sys.stderr, 'data: %s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) + print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) + print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) + print >> sys.stderr, 'parsed_unixtime has excessive string length!' + return + #sys.exit(7) + + urls.append(url) + unix_times.append(parsed_unixtime) + +def http_time_to_parsed_unixtime(data, http_time, url): + try: + ## Thanks to: + ## eumiro + ## http://stackoverflow.com/a/3894047/2605155 + parsed_unixtime = parse(http_time).strftime('%s') + + except ValueError as e: + print >> sys.stderr, 'Parsing http_time from server failed!' + print >> sys.stderr, 'HTTP header data:\n%s' % (data) + print >> sys.stderr, 'http_time: %s' % (http_time) + print >> sys.stderr, 'dateutil ValueError: %s' % (e) + return + #sys.exit(5) + + #print(parsed_unixtime) + unixtime_sanity_check(data, http_time, parsed_unixtime, url) + +def data_to_http_time(data, date_string_start_position, url): + http_time = '' + ## max accepted string length. + http_time = data[date_string_start_position:date_string_start_position + 29].strip() + + http_time_string_length = len(http_time) + + ## min string length = max string length. + if http_time_string_length < 29: + print >> sys.stderr, 'HTTP header date string too short.' + print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length + print >> sys.stderr, 'HTTP header data:\n%s' % (data) + print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) + return + #sys.exit(4) + + #print http_time + http_time_to_parsed_unixtime(data, http_time, url) + +def data_to_date_string_start_position(data, url): + date_string_start_position = data.find('Date:') + + if date_string_start_position == -1: + ## not found, check if lowercase. + date_string_start_position = data.find('date:') + + if date_string_start_position == -1: + ## "Date:" not found. + print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) + #print 'HTTP header data:\n%s' % (data) + return + #sys.exit(3) + + else: + date_string_start_position = date_string_start_position + 6 + data_to_http_time(data, date_string_start_position, url) + + +def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): + s = socks.socksocket() + s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) + #print 'THREAD STARTED "%s"' % url + + try: + s.connect((url, remote_port)) + print 'CONNECTED "%s"' % url + + ## Should occur only when tor is not running (stopped, crashed, + ## gateway shut down...) + except socks.GeneralProxyError as e: + error = '%s' % (e) + urls.append(error) + return urls + + except IOError as e: + ## {{ wheezy compatibility + if str(e).startswith('__init__'): + print >> sys.stderr, 'connect error: URL "%s" not found.' % url + else: + ## }} + ## Should return the errors to sdwdate for logging. + print >> sys.stderr, '"%s" %s ' % (url, e) + return + #sys.exit + + s.send('HEAD / HTTP/1.0\r\n\r\n') + data = '' + buf = s.recv(1024) + print 'SENDING "%s"' % url + while len(buf): + data += buf + buf = s.recv(1024) + s.close() + print 'RECEIVED "%s"' % url + + data_to_date_string_start_position(data, url) + + +def url_to_unixtime(remotes): + threads = [] + + timeout = gevent.Timeout() + timer = [] + seconds = 10 + + for i in range(0, len(remotes)): + timer.append(timeout.start_new(seconds)) + args = (request_data_from_remote_server, '127.0.0.1', '9050', remotes[i], 80) + threads.append(gevent.spawn(*args)) + + for i in range(0, len(remotes)): + try: + threads[i].join(timeout=timer[i]) + + except Timeout: + print >> sys.stderr, '"%s" Timeout' % (threads[i].args[2]) + + return urls, unix_times From e262416825096cb318a494b23663a896fc8d5360 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 23 Jun 2015 20:34:32 +0000 Subject: [PATCH 005/183] sdwdate class, check_tor() function --- .../dist-packages/sdwdate/sdwdate_python | 333 +++++++++--------- 1 file changed, 175 insertions(+), 158 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python index 1a01e7b4..d433418a 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python @@ -6,161 +6,178 @@ import time, random #import config -pool_one = ['dtsxnd3ykn32ywv6.onion', - 'znig4bc5rlwyj4mz.onion', - 'vtjkwwcq5osuo6uq.onion', - '33y6fjyhs3phzfjj.onion', - 'y6xjgkgwj47us5ca.onion', - 'strngbxhwyuu37a3.onion', - 'swdi5ymnwmrqhycl.onion', - 'dqeasamlf3jld2kz.onion', - 'pubdrop4dw6rk3aq.onion', - 'hkjpnjbvhrxjvikd.onion', - 'v6gdwmm7ed4oifvd.onion', - 'vbmwh445kf3fs2v4.onion', - 'poulsensqiv6ocq4.onion', - 'tigas3l7uusztiqu.onion', - 'w6csjytbrl273che.onion', - 'ak2uqfavwgmjrvtu.onion'] - -pool_two = ['yn6ocmvu4ok3k3al.onion', - 'acabtd4btrxjjrvr.onion', - '5r4bjnjug3apqdii.onion', - '2dermafialks7aai.onion', - 'ymi7h25hgp3bj63v.onion', - 'ppdz5djzpo3w5k2z.onion', - 'pltloztihmfrg2sw.onion', - 'ur5b2b4brz427ygh.onion', - 'abkjckdgoabr7bmm.onion', - 'bqs3dobnazs7h4u4.onion', - 'fkut2p37apcg6l7f.onion', - '6iolddfbfinntq2b.onion', - 'nzh3fv6jc6jskki3.onion'] - -pool_three = ['cwoiopiifrlzcuos.onion', - 'zsolxunfmbfuq7wf.onion', - 'yfm6sdhnfbulplsw.onion', - 'j6uhdvbhz74oefxf.onion', - '3g2upl4pq6kufc4m.onion', - 'dju2peblv7upfz3q.onion', - 'msydqstlz2kzerdg.onion', - 'uj3wazyk5u4hnvtk.onion', - 'wi7qkxyrdpu5cmvr.onion', - 'ic6au7wa3f6naxjq.onion', - 'timaq4ygg2iegci7.onion', - '344c6kbnjnljjzlz.onion', - 'fncuwbiisyh6ak3i.onion'] - -returned_unixtimes = 0 - -already_picked_index_pool_one = [] -already_picked_index_pool_two = [] -already_picked_index_pool_three = [] -already_picked_index_dummy_pool = [] - -pool_one_done = False -pool_two_done = False -pool_three_done = False -dummy_pool_done = False -url_random_pool_one = [] -url_random_pool_two = [] -url_random_pool_three = [] -url_random_dummy_pool = [] - -print 'Start %s' % (time.time()) - -while returned_unixtimes < 3: - urls = [] - unixtimes = [] - url_random = [] - - random.seed() - - url_index = [] - - if not pool_one_done: - while True: - ## Pick a random pool index. - url_index = random.sample(xrange(len(pool_one)), 1) - - ## Reset to prevent infinite loop, next condition would never be met, - ## probably raise an error before that happens. - if len(already_picked_index_pool_one) == len(pool_one): - already_picked_index_pool_one = [] - url_random_pool_one = [] - - ## Was this index used? - if url_index not in already_picked_index_pool_one: - ## No, add to used indexes. - already_picked_index_pool_one.append(url_index) - ## Add it to the pool url list. - url_random_pool_one.append(pool_one[url_index[0]]) - ## Add it to the url list to fetch. - url_random.append(pool_one[url_index[0]]) - break - - if not pool_two_done: - while True: - url_index = random.sample(xrange(len(pool_two)), 1) - - if len(url_random_pool_two) == len(pool_two): - already_picked_index_pool_two = [] - url_random_pool_two = [] - - if url_index not in already_picked_index_pool_two: - already_picked_index_pool_two.append(url_index) - url_random_pool_two.append(pool_two[url_index[0]]) - url_random.append(pool_two[url_index[0]]) - break - - if not pool_three_done: - while True: - url_index = random.sample(xrange(len(pool_three)), 1) - - if len(url_random_pool_three) == len(pool_three): - already_picked_index_pool_three = [] - url_random_pool_three = [] - - if url_index not in already_picked_index_pool_three: - already_picked_index_pool_three.append(url_index) - url_random_pool_three.append(pool_three[url_index[0]]) - url_random.append(pool_three[url_index[0]]) - break - - print 'pool 1 picked urls %s' % url_random_pool_one - print 'pool 2 picked urls %s' % url_random_pool_two - print 'pool 3 picked urls %s' % url_random_pool_three - print 'random urls %s' % url_random - - ## Fetch remotes. - if len(url_random) > 0: - urls, unix_times = url_to_unixtime(url_random) - else: - ## Add code here. - sys.exit(1) - - ## We can [safely?] infer that something is wrong with tor. - if (urls[0] == 'Connection closed unexpectedly' and - urls[1] == 'Connection closed unexpectedly' and - urls[2] == 'Connection closed unexpectedly'): - ## Raise error, log, user warning. - print urls[0] - sys.exit(1) - - if not pool_one_done: - ## Is last element in the pool in returned urls? - pool_one_done = url_random_pool_one[len(url_random_pool_one) - 1] in urls - - if not pool_two_done: - pool_two_done = url_random_pool_two[len(url_random_pool_two) - 1] in urls - - if not pool_three_done: - pool_three_done = url_random_pool_three[len(url_random_pool_three) - 1] in urls - - returned_unixtimes = len(urls) - print 'Returned unixtimes: %s' % (returned_unixtimes) - -for i in range(0, len(urls)): - print '"%s" %s' % (urls[i], unix_times[i]) - -print 'End %s' % (time.time()) + +class sdwdate(): + def __init__(self): + self.pool_one = ['dsafg', + 'vv', + 'gggg'] + #dtsxnd3ykn32ywv6.onion', + #'znig4bc5rlwyj4mz.onion', + #'vtjkwwcq5osuo6uq.onion', + #'33y6fjyhs3phzfjj.onion', + #'y6xjgkgwj47us5ca.onion', + #'strngbxhwyuu37a3.onion', + #'swdi5ymnwmrqhycl.onion', + #'dqeasamlf3jld2kz.onion', + #'pubdrop4dw6rk3aq.onion', + #'hkjpnjbvhrxjvikd.onion', + #'v6gdwmm7ed4oifvd.onion', + #'vbmwh445kf3fs2v4.onion', + #'poulsensqiv6ocq4.onion', + #'tigas3l7uusztiqu.onion', + #'w6csjytbrl273che.onion', + #'ak2uqfavwgmjrvtu.onion'] + + self.pool_two = ['yn6ocmvu4ok3k3al.onion', + 'acabtd4btrxjjrvr.onion', + '5r4bjnjug3apqdii.onion', + '2dermafialks7aai.onion', + 'ymi7h25hgp3bj63v.onion', + 'ppdz5djzpo3w5k2z.onion', + 'pltloztihmfrg2sw.onion', + 'ur5b2b4brz427ygh.onion', + 'abkjckdgoabr7bmm.onion', + 'bqs3dobnazs7h4u4.onion', + 'fkut2p37apcg6l7f.onion', + '6iolddfbfinntq2b.onion', + 'nzh3fv6jc6jskki3.onion'] + + self.pool_three = ['cwoiopiifrlzcuos.onion', + 'zsolxunfmbfuq7wf.onion', + 'yfm6sdhnfbulplsw.onion', + 'j6uhdvbhz74oefxf.onion', + '3g2upl4pq6kufc4m.onion', + 'dju2peblv7upfz3q.onion', + 'msydqstlz2kzerdg.onion', + 'uj3wazyk5u4hnvtk.onion', + 'wi7qkxyrdpu5cmvr.onion', + 'ic6au7wa3f6naxjq.onion', + 'timaq4ygg2iegci7.onion', + '344c6kbnjnljjzlz.onion', + 'fncuwbiisyh6ak3i.onion'] + + self.returned_unixtimes = 0 + + self.number_of_pools = 3 + + self.pool_one_done = False + self.pool_two_done = False + self.pool_three_done = False + + self.already_picked_index_pool_one = [] + self.already_picked_index_pool_two = [] + self.already_picked_index_pool_three = [] + + self.url_random_pool_one = [] + self.url_random_pool_two = [] + self.url_random_pool_three = [] + + print 'Start %s' % (time.time()) + + def check_tor(self, remotes): + ## We can [safely?] infer that something is wrong with tor. + if (remotes[0] == 'Connection closed unexpectedly' and + remotes[1] == 'Connection closed unexpectedly' and + remotes[2] == 'Connection closed unexpectedly'): + ## Raise error, log, user warning. + print self.urls[0] + sys.exit(1) + + def sdwdate_loop(self): + while self.returned_unixtimes < self.number_of_pools: + self.urls = [] + self.unixtimes = [] + self.url_random = [] + + random.seed() + + url_index = [] + + if not self.pool_one_done: + while True: + ## Pick a random pool index. + url_index = random.sample(xrange(len(self.pool_one)), 1) + + ## Reset to prevent infinite loop, next condition would never be met, + ## probably raise an error before that happens. + if len(self.already_picked_index_pool_one) == len(self.pool_one): + self.already_picked_index_pool_one = [] + self.url_random_pool_one = [] + + ## Was this index used? + if url_index not in self.already_picked_index_pool_one: + ## No, add to used indexes. + self.already_picked_index_pool_one.append(url_index) + ## Add it to the pool url list. + self.url_random_pool_one.append(self.pool_one[url_index[0]]) + ## Add it to the url list to fetch. + self.url_random.append(self.pool_one[url_index[0]]) + break + + if not self.pool_two_done: + while True: + url_index = random.sample(xrange(len(self.pool_two)), 1) + + if len(self.url_random_pool_two) == len(self.pool_two): + self.already_picked_index_pool_two = [] + self.url_random_pool_two = [] + + if url_index not in self.already_picked_index_pool_two: + self.already_picked_index_pool_two.append(url_index) + self.url_random_pool_two.append(self.pool_two[url_index[0]]) + self.url_random.append(self.pool_two[url_index[0]]) + break + + if not self.pool_three_done: + while True: + url_index = random.sample(xrange(len(self.pool_three)), 1) + + if len(self.url_random_pool_three) == len(self.pool_three): + self.already_picked_index_pool_three = [] + self.url_random_pool_three = [] + + if url_index not in self.already_picked_index_pool_three: + self.already_picked_index_pool_three.append(url_index) + self.url_random_pool_three.append(self.pool_three[url_index[0]]) + self.url_random.append(self.pool_three[url_index[0]]) + break + + print 'pool 1 picked urls %s' % self.url_random_pool_one + print 'pool 2 picked urls %s' % self.url_random_pool_two + print 'pool 3 picked urls %s' % self.url_random_pool_three + print 'random urls %s' % self.url_random + + ## Fetch remotes. + if len(self.url_random) > 0: + self.urls, self.unix_times = url_to_unixtime(self.url_random) + else: + ## Add code here. + sys.exit(1) + + self.check_tor(self.urls) + + if not self.pool_one_done: + ## Is last element in the pool in returned urls? + self.pool_one_done = self.url_random_pool_one[len(self.url_random_pool_one) - 1] in self.urls + + if not self.pool_two_done: + self.pool_two_done = self.url_random_pool_two[len(self.url_random_pool_two) - 1] in self.urls + + if not self.pool_three_done: + self.pool_three_done = self.url_random_pool_three[len(self.url_random_pool_three) - 1] in self.urls + + self.returned_unixtimes = len(self.urls) + print 'Returned unixtimes: %s' % (self.returned_unixtimes) + + for i in range(0, len(self.urls)): + print '"%s" %s' % (self.urls[i], self.unix_times[i]) + + print 'End %s' % (time.time()) + +def main(): + sdwdate_ = sdwdate() + sdwdate_.sdwdate_loop() + +if __name__ == "__main__": + main() From ebd72e07d8ba2ca170cdd670537bb960e0297db3 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 25 Jun 2015 20:38:19 +0000 Subject: [PATCH 006/183] cleaning --- debian/sdwdate.postinst | 58 ----------------------------------------- debian/sdwdate.postrm | 18 ------------- 2 files changed, 76 deletions(-) delete mode 100644 debian/sdwdate.postinst delete mode 100644 debian/sdwdate.postrm diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst deleted file mode 100644 index 672b244f..00000000 --- a/debian/sdwdate.postinst +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -if [ -f /usr/lib/pre.bsh ]; then - source /usr/lib/pre.bsh -fi - -set -e - -true " -##################################################################### -## INFO: BEGIN: $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ${1+"$@"} -##################################################################### -" - -if [ -x /usr/lib/anon-shared-helper-scripts/torsocks-remove-ld-preload ]; then - source /usr/lib/anon-shared-helper-scripts/torsocks-remove-ld-preload -fi - -case "$1" in - configure) - ## Not using --no-create-home, so sdwdate can write into /home/sdwdate. - adduser --quiet --system --group sdwdate || true - - mkdir --parents "/var/cache/sdwdate/sclockadj" - chown --recursive root:root "/var/cache/sdwdate/sclockadj" - chmod --recursive 700 "/var/cache/sdwdate/sclockadj" - - ## Clean up from previous version. - rm -f -r "/var/cache/sdwdate/.ruby_inline" - ;; - - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "$DPKG_MAINTSCRIPT_NAME called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -true "INFO: debhelper beginning here." - -#DEBHELPER# - -true "INFO: Done with debhelper." - -true " -##################################################################### -## INFO: END : $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ${1+"$@"} -##################################################################### -" - -## Explicitly "exit 0", so eventually trapped errors can be ignored. -exit 0 diff --git a/debian/sdwdate.postrm b/debian/sdwdate.postrm deleted file mode 100644 index 7b35eddf..00000000 --- a/debian/sdwdate.postrm +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -if [ -f /usr/lib/pre.bsh ]; then - source /usr/lib/pre.bsh -fi - -set -e - -if [ "$1" = "purge" ]; then - rm -f "/var/log/sdwdate.log" - rm -r -f "/var/cache/sdwdate" -fi - -#DEBHELPER# From 85e3aeb643bd576ef93925ea3389217a6a8325b5 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 25 Jun 2015 20:43:08 +0000 Subject: [PATCH 007/183] IO errors handling in sdwdate_loop --- .../dist-packages/sdwdate/sdwdate_python | 147 ++++++++++-------- .../dist-packages/sdwdate/url_to_unixtime.py | 40 +++-- 2 files changed, 104 insertions(+), 83 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python index d433418a..712b69fd 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python @@ -4,30 +4,30 @@ import sys from url_to_unixtime import url_to_unixtime import time, random +from numbers import Number +import re + #import config -class sdwdate(): +class Sdwdate(): def __init__(self): - self.pool_one = ['dsafg', - 'vv', - 'gggg'] - #dtsxnd3ykn32ywv6.onion', - #'znig4bc5rlwyj4mz.onion', - #'vtjkwwcq5osuo6uq.onion', - #'33y6fjyhs3phzfjj.onion', - #'y6xjgkgwj47us5ca.onion', - #'strngbxhwyuu37a3.onion', - #'swdi5ymnwmrqhycl.onion', - #'dqeasamlf3jld2kz.onion', - #'pubdrop4dw6rk3aq.onion', - #'hkjpnjbvhrxjvikd.onion', - #'v6gdwmm7ed4oifvd.onion', - #'vbmwh445kf3fs2v4.onion', - #'poulsensqiv6ocq4.onion', - #'tigas3l7uusztiqu.onion', - #'w6csjytbrl273che.onion', - #'ak2uqfavwgmjrvtu.onion'] + self.pool_one = ['dtsxnd3ykn32ywv6.onion', + 'znig4bc5rlwyj4mz.onion', + 'vtjkwwcq5osuo6uq.onion', + '33y6fjyhs3phzfjj.onion', + 'y6xjgkgwj47us5ca.onion', + 'strngbxhwyuu37a3.onion', + 'swdi5ymnwmrqhycl.onion', + 'dqeasamlf3jld2kz.onion', + 'pubdrop4dw6rk3aq.onion', + 'hkjpnjbvhrxjvikd.onion', + 'v6gdwmm7ed4oifvd.onion', + 'vbmwh445kf3fs2v4.onion', + 'poulsensqiv6ocq4.onion', + 'tigas3l7uusztiqu.onion', + 'w6csjytbrl273che.onion', + 'ak2uqfavwgmjrvtu.onion'] self.pool_two = ['yn6ocmvu4ok3k3al.onion', 'acabtd4btrxjjrvr.onion', @@ -57,8 +57,6 @@ class sdwdate(): '344c6kbnjnljjzlz.onion', 'fncuwbiisyh6ak3i.onion'] - self.returned_unixtimes = 0 - self.number_of_pools = 3 self.pool_one_done = False @@ -73,51 +71,66 @@ class sdwdate(): self.url_random_pool_two = [] self.url_random_pool_three = [] + self.valid_urls = [] + self.unixtimes = [] + + self.invalid_urls = [] + self.url_errors = [] + print 'Start %s' % (time.time()) - def check_tor(self, remotes): - ## We can [safely?] infer that something is wrong with tor. - if (remotes[0] == 'Connection closed unexpectedly' and - remotes[1] == 'Connection closed unexpectedly' and - remotes[2] == 'Connection closed unexpectedly'): + def general_proxy_error(self, pools): + #print pools[0] + #print pools[1] + #print pools[2] + if (pools[0] == 'Connection closed unexpectedly' and + pools[1] == 'Connection closed unexpectedly' and + pools[2] == 'Connection closed unexpectedly'): ## Raise error, log, user warning. - print self.urls[0] + print 'General Proxy Error' sys.exit(1) + return True + + def check_remote(self, remote, value): + try: + if True: + n = int(value) + print 'check_remote "%s" %s, True' % (remote, value) + return True + except ValueError: + print 'check_remote "%s" %s, False' % (remote, value) + return False + def sdwdate_loop(self): - while self.returned_unixtimes < self.number_of_pools: + while len(self.valid_urls) < self.number_of_pools: + print "MAIN LOOP" self.urls = [] - self.unixtimes = [] self.url_random = [] - random.seed() - - url_index = [] - if not self.pool_one_done: while True: - ## Pick a random pool index. - url_index = random.sample(xrange(len(self.pool_one)), 1) + url_index = [] + url_index = random.sample(range(len(self.pool_one)), 1) - ## Reset to prevent infinite loop, next condition would never be met, - ## probably raise an error before that happens. if len(self.already_picked_index_pool_one) == len(self.pool_one): self.already_picked_index_pool_one = [] self.url_random_pool_one = [] + ## Should stop here? Declare the pool invalid? + #self.number_of_pools = self.number_of_pools - 1 + #self.pool_one_done = True - ## Was this index used? if url_index not in self.already_picked_index_pool_one: - ## No, add to used indexes. self.already_picked_index_pool_one.append(url_index) - ## Add it to the pool url list. + print 'pool 1 added %s' % (self.pool_one[url_index[0]]) self.url_random_pool_one.append(self.pool_one[url_index[0]]) - ## Add it to the url list to fetch. self.url_random.append(self.pool_one[url_index[0]]) break if not self.pool_two_done: while True: - url_index = random.sample(xrange(len(self.pool_two)), 1) + url_index = [] + url_index = random.sample(range(len(self.pool_two)), 1) if len(self.url_random_pool_two) == len(self.pool_two): self.already_picked_index_pool_two = [] @@ -125,13 +138,15 @@ class sdwdate(): if url_index not in self.already_picked_index_pool_two: self.already_picked_index_pool_two.append(url_index) + print 'pool 2 added %s' % (self.pool_two[url_index[0]]) self.url_random_pool_two.append(self.pool_two[url_index[0]]) self.url_random.append(self.pool_two[url_index[0]]) break if not self.pool_three_done: while True: - url_index = random.sample(xrange(len(self.pool_three)), 1) + url_index = [] + url_index = random.sample(range(len(self.pool_three)), 1) if len(self.url_random_pool_three) == len(self.pool_three): self.already_picked_index_pool_three = [] @@ -139,44 +154,54 @@ class sdwdate(): if url_index not in self.already_picked_index_pool_three: self.already_picked_index_pool_three.append(url_index) + print 'pool 3 added %s' % (self.pool_three[url_index[0]]) self.url_random_pool_three.append(self.pool_three[url_index[0]]) self.url_random.append(self.pool_three[url_index[0]]) break - print 'pool 1 picked urls %s' % self.url_random_pool_one - print 'pool 2 picked urls %s' % self.url_random_pool_two - print 'pool 3 picked urls %s' % self.url_random_pool_three - print 'random urls %s' % self.url_random - ## Fetch remotes. if len(self.url_random) > 0: - self.urls, self.unix_times = url_to_unixtime(self.url_random) + print 'random urls %s' % (self.url_random) + self.urls, self.returned_values = url_to_unixtime(self.url_random) else: ## Add code here. sys.exit(1) - self.check_tor(self.urls) + if self.general_proxy_error(self.returned_values): + self.valid_urls = [] + self.unixtimes = [] + for i in range(len(self.urls)): + if self.check_remote(self.urls[i], self.returned_values[i]): + self.valid_urls.append(self.urls[i]) + self.unixtimes.append(self.returned_values[i]) + else: + self.invalid_urls.append(self.urls[i]) + self.url_errors.append(self.returned_values[i]) if not self.pool_one_done: - ## Is last element in the pool in returned urls? - self.pool_one_done = self.url_random_pool_one[len(self.url_random_pool_one) - 1] in self.urls + for i in range(len(self.url_random_pool_one)): + self.pool_one_done = self.url_random_pool_one[i] in self.valid_urls + print 'pool_one_done %s' % (self.pool_one_done) if not self.pool_two_done: - self.pool_two_done = self.url_random_pool_two[len(self.url_random_pool_two) - 1] in self.urls + for i in range(len(self.url_random_pool_two)): + self.pool_two_done = self.url_random_pool_two[i] in self.valid_urls + print 'pool_two_done %s' % (self.pool_two_done) if not self.pool_three_done: - self.pool_three_done = self.url_random_pool_three[len(self.url_random_pool_three) - 1] in self.urls - - self.returned_unixtimes = len(self.urls) - print 'Returned unixtimes: %s' % (self.returned_unixtimes) + for i in range(len(self.url_random_pool_three)): + self.pool_three_done = self.url_random_pool_three[i] in self.valid_urls + print 'pool_three_done %s' % (self.pool_three_done) - for i in range(0, len(self.urls)): - print '"%s" %s' % (self.urls[i], self.unix_times[i]) + print 'valid urls %s' % (self.valid_urls) + ## Duplicates in bad urls, same url appended because pool not done. + ## Remove duplicates + print 'bad urls %s' % (list(set(self.invalid_urls))) print 'End %s' % (time.time()) def main(): - sdwdate_ = sdwdate() + sdwdate_ = Sdwdate() sdwdate_.sdwdate_loop() if __name__ == "__main__": diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index ea7cf201..0af5bd56 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -27,7 +27,6 @@ def unixtime_sanity_check(data, http_time, parsed_unixtime, url): print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) print >> sys.stderr, 'parsed_unixtime not numeric!' return - #exit(6) unixtime_string_length_is = len(parsed_unixtime) unixtime_string_length_max = 10 @@ -41,7 +40,6 @@ def unixtime_sanity_check(data, http_time, parsed_unixtime, url): print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) print >> sys.stderr, 'parsed_unixtime has excessive string length!' return - #sys.exit(7) urls.append(url) unix_times.append(parsed_unixtime) @@ -59,7 +57,6 @@ def http_time_to_parsed_unixtime(data, http_time, url): print >> sys.stderr, 'http_time: %s' % (http_time) print >> sys.stderr, 'dateutil ValueError: %s' % (e) return - #sys.exit(5) #print(parsed_unixtime) unixtime_sanity_check(data, http_time, parsed_unixtime, url) @@ -78,7 +75,6 @@ def data_to_http_time(data, date_string_start_position, url): print >> sys.stderr, 'HTTP header data:\n%s' % (data) print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) return - #sys.exit(4) #print http_time http_time_to_parsed_unixtime(data, http_time, url) @@ -93,9 +89,7 @@ def data_to_date_string_start_position(data, url): if date_string_start_position == -1: ## "Date:" not found. print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) - #print 'HTTP header data:\n%s' % (data) return - #sys.exit(3) else: date_string_start_position = date_string_start_position + 6 @@ -105,29 +99,23 @@ def data_to_date_string_start_position(data, url): def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): s = socks.socksocket() s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) - #print 'THREAD STARTED "%s"' % url try: s.connect((url, remote_port)) print 'CONNECTED "%s"' % url - ## Should occur only when tor is not running (stopped, crashed, - ## gateway shut down...) - except socks.GeneralProxyError as e: - error = '%s' % (e) - urls.append(error) - return urls - except IOError as e: ## {{ wheezy compatibility if str(e).startswith('__init__'): - print >> sys.stderr, 'connect error: URL "%s" not found.' % url - else: + urls.append(url) + unix_times.append('URL not found') + #print >> sys.stderr, 'connect error: URL "%s" not found.' % url ## }} - ## Should return the errors to sdwdate for logging. - print >> sys.stderr, '"%s" %s ' % (url, e) + else: + error = '%s' % (e) + urls.append(url) + unix_times.append(error) return - #sys.exit s.send('HEAD / HTTP/1.0\r\n\r\n') data = '' @@ -143,22 +131,30 @@ def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): def url_to_unixtime(remotes): + #print remotes + + url = [] threads = [] timeout = gevent.Timeout() timer = [] seconds = 10 - for i in range(0, len(remotes)): + print 'GEVENT started' + + for i in range(len(remotes)): timer.append(timeout.start_new(seconds)) args = (request_data_from_remote_server, '127.0.0.1', '9050', remotes[i], 80) threads.append(gevent.spawn(*args)) - for i in range(0, len(remotes)): + for i in range(len(remotes)): try: threads[i].join(timeout=timer[i]) except Timeout: - print >> sys.stderr, '"%s" Timeout' % (threads[i].args[2]) + urls.append(threads[i].args[2]) + unix_times.append('Timeout') + + print 'GEVENT exiting' return urls, unix_times From 8da613e53408f07af2e4a4fe3e2f40fc433ad3bc Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 25 Jun 2015 21:23:27 +0000 Subject: [PATCH 008/183] if NOT self.general_proxy_error --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python index 712b69fd..231d5176 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python @@ -90,7 +90,7 @@ class Sdwdate(): print 'General Proxy Error' sys.exit(1) - return True + return False def check_remote(self, remote, value): try: @@ -167,7 +167,7 @@ class Sdwdate(): ## Add code here. sys.exit(1) - if self.general_proxy_error(self.returned_values): + if not self.general_proxy_error(self.returned_values): self.valid_urls = [] self.unixtimes = [] for i in range(len(self.urls)): From 48bb58ca5b283b82688f4c04cef495192edb45a5 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 27 Jun 2015 16:02:11 +0000 Subject: [PATCH 009/183] read pools configuration --- .../python2.7/dist-packages/sdwdate/config.py | 137 ++++++++++++++++++ .../sdwdate/{sdwdate_python => sdwdate.py} | 57 +------- 2 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/config.py rename usr/lib/python2.7/dist-packages/sdwdate/{sdwdate_python => sdwdate.py} (73%) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py new file mode 100644 index 00000000..45743812 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import os +import glob +import time +import re + +def read_pools(): + SDWDATE_POOL_ONE = False + SDWDATE_POOL_TWO = False + SDWDATE_POOL_THREE = False + + multi_line = False + pool_one = [] + pool_two = [] + pool_three = [] + + pool_one_single = [] + pool_one_multi = [] + + pool_two_single = [] + pool_two_multi = [] + + pool_three_single = [] + pool_three_multi = [] + + if os.path.exists('/etc/sdwdate.d/'): + files = sorted(glob.glob('/etc/sdwdate.d/*')) + + if files: + conf_found = False + for conf in files: + if not conf.endswith('~') and conf.count('.dpkg-') == 0: + conf_found = True + with open(conf) as c: + for line in c: + if line.startswith('SDWDATE_POOL_ONE'): + SDWDATE_POOL_ONE = True + + elif line.startswith('SDWDATE_POOL_TWO'): + SDWDATE_POOL_ONE = False + SDWDATE_POOL_TWO = True + + elif line.startswith('SDWDATE_POOL_THREE'): + SDWDATE_POOL_ONE = False + SDWDATE_POOL_TWO = False + SDWDATE_POOL_THREE = True + + elif SDWDATE_POOL_ONE and not line.startswith('##'): + pool_one.append(line.strip()) + + elif SDWDATE_POOL_TWO and not line.startswith('##'): + pool_two.append(line.strip()) + + elif SDWDATE_POOL_THREE and not line.startswith('##'): + pool_three.append(line.strip()) + + if not conf_found: + self.set_default() + print('No valid file found in user configuration folder "/etc/sdwdate.d".'\ + ' Running with default configuration.') + + else: + print('No file found in user configuration folder "/etc/cpfpy.d".'\ + ' Running with default configuration.') + + else: + print('User configuration folder "/etc/cpfpy.d" does not exist.'\ + ' Running with default configuration.') + + for i in range(len(pool_one)): + if multi_line and pool_one[i] == '"': + multi_line = False + elif multi_line: + url = pool_one[i][0:22] + pool_one_multi.append(url) + elif pool_one[i] == '"': + multi_line = True + elif pool_one[i].startswith('"'): + #url = re.search(r'"(.*)#', pool_one[i]) + #pool_one_single.append(url.group(1)) + url = pool_one[i][1:23] + pool_one_single.append(url) + + #print 'pool_one_multi' + #for i in xrange(len(pool_one_multi)): + #print pool_one_multi[i] + #print 'pool 1 singles' + #for i in range(len(pool_one_single)): + #print pool_one_single[i] + + for i in range(len(pool_two)): + if multi_line and pool_two[i] == '"': + multi_line = False + elif multi_line: + url = pool_two[i][0:22] + pool_two_multi.append(url) + elif pool_two[i] == '"': + multi_line = True + elif pool_two[i].startswith('"'): + #url = re.search(r'"(.*)#', pool_two[i]) + #pool_two_single.append(url.group(1)) + url = pool_two[i][1:23] + pool_two_single.append(url) + + #print 'pool_two_multi' + #for i in xrange(len(pool_two_multi)): + #print pool_two_multi[i] + #print 'pool 2 singles' + #for i in range(len(pool_two_single)): + #print pool_two_single[i] + + for i in range(len(pool_three)): + if multi_line and pool_three[i] == '"': + multi_line = False + elif multi_line: + url = pool_three[i][0:22] + pool_three_multi.append(url) + elif pool_three[i] == '"': + multi_line = True + elif pool_three[i].startswith('"'): + #url = re.search(r'"(.*)#', pool_three[i]) + #pool_three_single.append(url.group(1)) + url = pool_three[i][1:23] + pool_three_single.append(url) + + #print 'pool_three_multi' + #for i in xrange(len(pool_three_multi)): + #print pool_three_multi[i] + #print 'pool 2 singles' + #for i in range(len(pool_three_single)): + #print pool_three_single[i] + + return pool_one_single, pool_two_single, pool_three_single + +if __name__ == "__main__": + read_pools() diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py similarity index 73% rename from usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python rename to usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 231d5176..324bfd4e 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate_python +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -1,61 +1,19 @@ #!/usr/bin/env python import sys -from url_to_unixtime import url_to_unixtime import time, random +from url_to_unixtime import url_to_unixtime +from config import read_pools + from numbers import Number import re -#import config class Sdwdate(): def __init__(self): - self.pool_one = ['dtsxnd3ykn32ywv6.onion', - 'znig4bc5rlwyj4mz.onion', - 'vtjkwwcq5osuo6uq.onion', - '33y6fjyhs3phzfjj.onion', - 'y6xjgkgwj47us5ca.onion', - 'strngbxhwyuu37a3.onion', - 'swdi5ymnwmrqhycl.onion', - 'dqeasamlf3jld2kz.onion', - 'pubdrop4dw6rk3aq.onion', - 'hkjpnjbvhrxjvikd.onion', - 'v6gdwmm7ed4oifvd.onion', - 'vbmwh445kf3fs2v4.onion', - 'poulsensqiv6ocq4.onion', - 'tigas3l7uusztiqu.onion', - 'w6csjytbrl273che.onion', - 'ak2uqfavwgmjrvtu.onion'] - - self.pool_two = ['yn6ocmvu4ok3k3al.onion', - 'acabtd4btrxjjrvr.onion', - '5r4bjnjug3apqdii.onion', - '2dermafialks7aai.onion', - 'ymi7h25hgp3bj63v.onion', - 'ppdz5djzpo3w5k2z.onion', - 'pltloztihmfrg2sw.onion', - 'ur5b2b4brz427ygh.onion', - 'abkjckdgoabr7bmm.onion', - 'bqs3dobnazs7h4u4.onion', - 'fkut2p37apcg6l7f.onion', - '6iolddfbfinntq2b.onion', - 'nzh3fv6jc6jskki3.onion'] - - self.pool_three = ['cwoiopiifrlzcuos.onion', - 'zsolxunfmbfuq7wf.onion', - 'yfm6sdhnfbulplsw.onion', - 'j6uhdvbhz74oefxf.onion', - '3g2upl4pq6kufc4m.onion', - 'dju2peblv7upfz3q.onion', - 'msydqstlz2kzerdg.onion', - 'uj3wazyk5u4hnvtk.onion', - 'wi7qkxyrdpu5cmvr.onion', - 'ic6au7wa3f6naxjq.onion', - 'timaq4ygg2iegci7.onion', - '344c6kbnjnljjzlz.onion', - 'fncuwbiisyh6ak3i.onion'] + self.pool_one, self.pool_two, self.pool_three = read_pools() self.number_of_pools = 3 @@ -80,9 +38,6 @@ def __init__(self): print 'Start %s' % (time.time()) def general_proxy_error(self, pools): - #print pools[0] - #print pools[1] - #print pools[2] if (pools[0] == 'Connection closed unexpectedly' and pools[1] == 'Connection closed unexpectedly' and pools[2] == 'Connection closed unexpectedly'): @@ -116,9 +71,6 @@ def sdwdate_loop(self): if len(self.already_picked_index_pool_one) == len(self.pool_one): self.already_picked_index_pool_one = [] self.url_random_pool_one = [] - ## Should stop here? Declare the pool invalid? - #self.number_of_pools = self.number_of_pools - 1 - #self.pool_one_done = True if url_index not in self.already_picked_index_pool_one: self.already_picked_index_pool_one.append(url_index) @@ -163,6 +115,7 @@ def sdwdate_loop(self): if len(self.url_random) > 0: print 'random urls %s' % (self.url_random) self.urls, self.returned_values = url_to_unixtime(self.url_random) + print 'returned urls "%s"' % (self.urls) else: ## Add code here. sys.exit(1) From e481ecc1ca7d7d27333eaf2939b28ac2a2e6121d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 27 Jun 2015 20:03:37 +0000 Subject: [PATCH 010/183] pick_single_link_from_maybe_multi_lined_mirror_list --- .../python2.7/dist-packages/sdwdate/config.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 45743812..54ed25a6 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -82,12 +82,12 @@ def read_pools(): url = pool_one[i][1:23] pool_one_single.append(url) - #print 'pool_one_multi' - #for i in xrange(len(pool_one_multi)): - #print pool_one_multi[i] - #print 'pool 1 singles' - #for i in range(len(pool_one_single)): - #print pool_one_single[i] + print 'pool_one_multi' + for i in xrange(len(pool_one_multi)): + print pool_one_multi[i] + print 'pool 1 singles' + for i in range(len(pool_one_single)): + print pool_one_single[i] for i in range(len(pool_two)): if multi_line and pool_two[i] == '"': @@ -103,12 +103,12 @@ def read_pools(): url = pool_two[i][1:23] pool_two_single.append(url) - #print 'pool_two_multi' - #for i in xrange(len(pool_two_multi)): - #print pool_two_multi[i] - #print 'pool 2 singles' - #for i in range(len(pool_two_single)): - #print pool_two_single[i] + print 'pool_two_multi' + for i in xrange(len(pool_two_multi)): + print pool_two_multi[i] + print 'pool 2 singles' + for i in range(len(pool_two_single)): + print pool_two_single[i] for i in range(len(pool_three)): if multi_line and pool_three[i] == '"': @@ -124,14 +124,15 @@ def read_pools(): url = pool_three[i][1:23] pool_three_single.append(url) - #print 'pool_three_multi' - #for i in xrange(len(pool_three_multi)): - #print pool_three_multi[i] - #print 'pool 2 singles' - #for i in range(len(pool_three_single)): - #print pool_three_single[i] + print 'pool_three_multi' + for i in xrange(len(pool_three_multi)): + print pool_three_multi[i] + print 'pool_three_single' + for i in range(len(pool_three_single)): + print pool_three_single[i] - return pool_one_single, pool_two_single, pool_three_single + return (pool_one_single, pool_two_single, pool_three_single, + pool_one_multi, pool_two_multi, pool_three_multi) if __name__ == "__main__": read_pools() From fb3106de1e329d98ef75ba68c5b10b4984c11719 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 27 Jun 2015 20:06:21 +0000 Subject: [PATCH 011/183] pick_single_link_from_maybe_multi_lined_mirror_list (2) --- .../dist-packages/sdwdate/sdwdate.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 324bfd4e..c401d5ee 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -5,15 +5,27 @@ from url_to_unixtime import url_to_unixtime from config import read_pools - -from numbers import Number -import re - - +from error_handler import SdwdateError class Sdwdate(): def __init__(self): - self.pool_one, self.pool_two, self.pool_three = read_pools() + self.pool_one_single, self.pool_two_single, self.pool_three_single, \ + self.pool_one_multi, self.pool_two_multi, self.pool_three_multi = read_pools() + + if len(self.pool_one_multi) > 0: + self.pool_one = self.pool_one_multi + else: + self.pool_one = self.pool_one_single + + if len(self.pool_two_multi) > 0: + self.pool_two = self.pool_two_multi + else: + self.pool_two = self.pool_two_single + + if len(self.pool_one_multi) > 0: + self.pool_three = self.pool_three_multi + else: + self.pool_three = self.pool_three_multi self.number_of_pools = 3 From cb69ced8ef084b723eace651a3db04b080c4fab6 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 27 Jun 2015 22:51:55 +0000 Subject: [PATCH 012/183] fix: development bug --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index c401d5ee..12f8275f 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -5,7 +5,7 @@ from url_to_unixtime import url_to_unixtime from config import read_pools -from error_handler import SdwdateError +#from error_handler import SdwdateError class Sdwdate(): def __init__(self): From 3a91e982105dfbecccc037db3c5dc4b02fc9b67e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 28 Jun 2015 18:55:34 +0000 Subject: [PATCH 013/183] select multi-line in pool randomly, pick a random member in it. --- .../dist-packages/sdwdate/sdwdate.py | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 12f8275f..2aca8aa2 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -13,19 +13,21 @@ def __init__(self): self.pool_one_multi, self.pool_two_multi, self.pool_three_multi = read_pools() if len(self.pool_one_multi) > 0: - self.pool_one = self.pool_one_multi + ## Increment the range of random. + self.range_pool_one = len(self.pool_one_single) + 1 + print len(self.pool_one_multi) else: - self.pool_one = self.pool_one_single + self.range_pool_one = len(self.pool_one_single) if len(self.pool_two_multi) > 0: - self.pool_two = self.pool_two_multi + self.range_pool_two = len(self.pool_two_single) + 1 else: - self.pool_two = self.pool_two_single + self.range_pool_two = len(self.pool_two_single) - if len(self.pool_one_multi) > 0: - self.pool_three = self.pool_three_multi + if len(self.pool_three_multi) > 0: + self.range_pool_three = len(self.pool_three_single) + 1 else: - self.pool_three = self.pool_three_multi + self.range_pool_three = len(self.pool_three_single) self.number_of_pools = 3 @@ -78,7 +80,16 @@ def sdwdate_loop(self): if not self.pool_one_done: while True: url_index = [] - url_index = random.sample(range(len(self.pool_one)), 1) + url_index = random.sample(range(self.range_pool_one), 1) + index = url_index[0] + ## url_index is zero based. + if index > len(self.pool_one_single) - 1: + ## muti-line entry exists in pool, make it current, pick a random member, + self.pool_one = self.pool_one_multi + url_index = random.sample(range(len(self.pool_one_multi)), 1) + else: + ## otherwise, use a single member. + self.pool_one = self.pool_one_single if len(self.already_picked_index_pool_one) == len(self.pool_one): self.already_picked_index_pool_one = [] @@ -94,7 +105,13 @@ def sdwdate_loop(self): if not self.pool_two_done: while True: url_index = [] - url_index = random.sample(range(len(self.pool_two)), 1) + url_index = random.sample(range(self.range_pool_two), 1) + index = url_index[0] + if index > len(self.pool_two_single) - 1: + url_index = random.sample(range(len(self.pool_two_multi)), 1) + self.pool_two = self.pool_two_multi + else: + self.pool_two = self.pool_two_single if len(self.url_random_pool_two) == len(self.pool_two): self.already_picked_index_pool_two = [] @@ -110,7 +127,13 @@ def sdwdate_loop(self): if not self.pool_three_done: while True: url_index = [] - url_index = random.sample(range(len(self.pool_three)), 1) + url_index = random.sample(range(self.range_pool_three), 1) + index = url_index[0] + if index > len(self.pool_three_single) - 1: + url_index = random.sample(range(len(self.pool_three_multi)), 1) + self.pool_three = self.pool_three_multi + else: + self.pool_three = self.pool_three_single if len(self.url_random_pool_three) == len(self.pool_three): self.already_picked_index_pool_three = [] From b3520602181e368ae563182b8b1c7032dc044d08 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 13 Jul 2015 18:53:59 +0000 Subject: [PATCH 014/183] simplified 30_sdwdate_default --- etc/sdwdate.d/30_sdwdate_default | 48 +++++----- .../python2.7/dist-packages/sdwdate/config.py | 96 +++---------------- .../dist-packages/sdwdate/sdwdate.py | 44 ++------- 3 files changed, 44 insertions(+), 144 deletions(-) diff --git a/etc/sdwdate.d/30_sdwdate_default b/etc/sdwdate.d/30_sdwdate_default index c6e3b87e..a12575ee 100644 --- a/etc/sdwdate.d/30_sdwdate_default +++ b/etc/sdwdate.d/30_sdwdate_default @@ -266,13 +266,11 @@ SDWDATE_POOL_ONE=( ## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" ## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" SDWDATE_POOL_TWO=( - " - atlas777hhh7mcs7.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - compass6vpxj32p3.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - globe223ezvh6bps.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - bbbbbb6qtmqg65g6.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - pppppptkftqqnfsq.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - " + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" @@ -301,27 +299,25 @@ SDWDATE_POOL_TWO=( ## removed because no http: ## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" SDWDATE_POOL_THREE=( - "3g2upl4pq6kufc4m.onion:80#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" - "dju2peblv7upfz3q.onion:80#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" - "msydqstlz2kzerdg.onion:80#https://ahmia.fi/address/msydqstlz2kzerdg" - "uj3wazyk5u4hnvtk.onion:80#https://thepiratebay.se/blog/238" - "bitmailendavkbec.onion:80#https://bitmessage.org/forum/index.php?topic=1556.0" + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - " - nzh3fv6jc6jskki3#riseup.net: nzh3fv6jc6jskki3.onion (port 443) - nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) - cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) - zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) - yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) - xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) - zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) - 5jp7xtmox6jyoqd5:443#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) - zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) - zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) - j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) - 7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) - " + "nzh3fv6jc6jskki3.onion#riseup.net: nzh3fv6jc6jskki3.onion (port 443) + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 54ed25a6..f6eea56e 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import os +import os, sys import glob import time import re @@ -15,15 +15,6 @@ def read_pools(): pool_two = [] pool_three = [] - pool_one_single = [] - pool_one_multi = [] - - pool_two_single = [] - pool_two_multi = [] - - pool_three_single = [] - pool_three_multi = [] - if os.path.exists('/etc/sdwdate.d/'): files = sorted(glob.glob('/etc/sdwdate.d/*')) @@ -34,6 +25,7 @@ def read_pools(): conf_found = True with open(conf) as c: for line in c: + line = line.strip() if line.startswith('SDWDATE_POOL_ONE'): SDWDATE_POOL_ONE = True @@ -46,14 +38,20 @@ def read_pools(): SDWDATE_POOL_TWO = False SDWDATE_POOL_THREE = True - elif SDWDATE_POOL_ONE and not line.startswith('##'): - pool_one.append(line.strip()) + elif SDWDATE_POOL_ONE and line.startswith('"'): + url = re.search(r'"(.*)#', line) + print '%s' % (url.group(1)) + pool_one.append(url.group(1)) - elif SDWDATE_POOL_TWO and not line.startswith('##'): - pool_two.append(line.strip()) + elif SDWDATE_POOL_TWO and line.startswith('"'): + url = re.search(r'"(.*)#', line) + print '%s' % (url.group(1)) + pool_two.append(url.group(1)) - elif SDWDATE_POOL_THREE and not line.startswith('##'): - pool_three.append(line.strip()) + elif SDWDATE_POOL_THREE and line.startswith('"'): + url = re.search(r'"(.*)#', line) + print '%s' % (url.group(1)) + pool_three.append(url.group(1)) if not conf_found: self.set_default() @@ -68,71 +66,7 @@ def read_pools(): print('User configuration folder "/etc/cpfpy.d" does not exist.'\ ' Running with default configuration.') - for i in range(len(pool_one)): - if multi_line and pool_one[i] == '"': - multi_line = False - elif multi_line: - url = pool_one[i][0:22] - pool_one_multi.append(url) - elif pool_one[i] == '"': - multi_line = True - elif pool_one[i].startswith('"'): - #url = re.search(r'"(.*)#', pool_one[i]) - #pool_one_single.append(url.group(1)) - url = pool_one[i][1:23] - pool_one_single.append(url) - - print 'pool_one_multi' - for i in xrange(len(pool_one_multi)): - print pool_one_multi[i] - print 'pool 1 singles' - for i in range(len(pool_one_single)): - print pool_one_single[i] - - for i in range(len(pool_two)): - if multi_line and pool_two[i] == '"': - multi_line = False - elif multi_line: - url = pool_two[i][0:22] - pool_two_multi.append(url) - elif pool_two[i] == '"': - multi_line = True - elif pool_two[i].startswith('"'): - #url = re.search(r'"(.*)#', pool_two[i]) - #pool_two_single.append(url.group(1)) - url = pool_two[i][1:23] - pool_two_single.append(url) - - print 'pool_two_multi' - for i in xrange(len(pool_two_multi)): - print pool_two_multi[i] - print 'pool 2 singles' - for i in range(len(pool_two_single)): - print pool_two_single[i] - - for i in range(len(pool_three)): - if multi_line and pool_three[i] == '"': - multi_line = False - elif multi_line: - url = pool_three[i][0:22] - pool_three_multi.append(url) - elif pool_three[i] == '"': - multi_line = True - elif pool_three[i].startswith('"'): - #url = re.search(r'"(.*)#', pool_three[i]) - #pool_three_single.append(url.group(1)) - url = pool_three[i][1:23] - pool_three_single.append(url) - - print 'pool_three_multi' - for i in xrange(len(pool_three_multi)): - print pool_three_multi[i] - print 'pool_three_single' - for i in range(len(pool_three_single)): - print pool_three_single[i] - - return (pool_one_single, pool_two_single, pool_three_single, - pool_one_multi, pool_two_multi, pool_three_multi) + return (pool_one, pool_two, pool_three) if __name__ == "__main__": read_pools() diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 2aca8aa2..03e8abc4 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -9,25 +9,13 @@ class Sdwdate(): def __init__(self): - self.pool_one_single, self.pool_two_single, self.pool_three_single, \ - self.pool_one_multi, self.pool_two_multi, self.pool_three_multi = read_pools() - - if len(self.pool_one_multi) > 0: - ## Increment the range of random. - self.range_pool_one = len(self.pool_one_single) + 1 - print len(self.pool_one_multi) - else: - self.range_pool_one = len(self.pool_one_single) - - if len(self.pool_two_multi) > 0: - self.range_pool_two = len(self.pool_two_single) + 1 - else: - self.range_pool_two = len(self.pool_two_single) - - if len(self.pool_three_multi) > 0: - self.range_pool_three = len(self.pool_three_single) + 1 - else: - self.range_pool_three = len(self.pool_three_single) + self.pool_one, self.pool_two, self.pool_three = read_pools() + + self.range_pool_one = len(self.pool_one) + + self.range_pool_two = len(self.pool_two) + + self.range_pool_three = len(self.pool_three) self.number_of_pools = 3 @@ -82,14 +70,6 @@ def sdwdate_loop(self): url_index = [] url_index = random.sample(range(self.range_pool_one), 1) index = url_index[0] - ## url_index is zero based. - if index > len(self.pool_one_single) - 1: - ## muti-line entry exists in pool, make it current, pick a random member, - self.pool_one = self.pool_one_multi - url_index = random.sample(range(len(self.pool_one_multi)), 1) - else: - ## otherwise, use a single member. - self.pool_one = self.pool_one_single if len(self.already_picked_index_pool_one) == len(self.pool_one): self.already_picked_index_pool_one = [] @@ -107,11 +87,6 @@ def sdwdate_loop(self): url_index = [] url_index = random.sample(range(self.range_pool_two), 1) index = url_index[0] - if index > len(self.pool_two_single) - 1: - url_index = random.sample(range(len(self.pool_two_multi)), 1) - self.pool_two = self.pool_two_multi - else: - self.pool_two = self.pool_two_single if len(self.url_random_pool_two) == len(self.pool_two): self.already_picked_index_pool_two = [] @@ -129,11 +104,6 @@ def sdwdate_loop(self): url_index = [] url_index = random.sample(range(self.range_pool_three), 1) index = url_index[0] - if index > len(self.pool_three_single) - 1: - url_index = random.sample(range(len(self.pool_three_multi)), 1) - self.pool_three = self.pool_three_multi - else: - self.pool_three = self.pool_three_single if len(self.url_random_pool_three) == len(self.pool_three): self.already_picked_index_pool_three = [] From ac521d7db97df8ea0ce5e11ab30d5c1ed5e2ce6f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 14 Jul 2015 19:35:00 +0000 Subject: [PATCH 015/183] temporary: create etc/sdwdate-python.d/30_sdwdate_default, leave original 30_sdwdate_default untouched --- etc/sdwdate-python.d/30_sdwdate_default | 324 ++++++++++++++++++ etc/sdwdate.d/30_sdwdate_default | 48 +-- .../python2.7/dist-packages/sdwdate/config.py | 7 +- 3 files changed, 352 insertions(+), 27 deletions(-) create mode 100644 etc/sdwdate-python.d/30_sdwdate_default diff --git a/etc/sdwdate-python.d/30_sdwdate_default b/etc/sdwdate-python.d/30_sdwdate_default new file mode 100644 index 00000000..a12575ee --- /dev/null +++ b/etc/sdwdate-python.d/30_sdwdate_default @@ -0,0 +1,324 @@ +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +## Please use "/etc/sdwdate.d/50_sdwdate_user" for your custom +## configuration, which will override the defaults found here. +## When sdwdate is updated, this file may be overwritten. + +## Bash Fragment. + +## Enable/disable debugging. +## 1 enabled. +## 2 disabled. +DEBUG=0 + +## Run as the following user name. +## Not implemented. Has no effect. +USER="" + +## Do or do not actually change the date/time after successfully fetching it. +## 0 set date. +## 1 do not set date. +DONT_SET_DATE=0 + +## do not move the time forward +## 0 disabled +## 1 enabled +NO_MOVE_FORWARD=0 + +## do not move the time backwards +## 0 disabled +## 1 enabled +NO_MOVE_BACKWARDS=0 + +## update hardware clock +## 0 disabled +## 1 enabled +SYSTOHC=0 + +## Log file. +LOG_FILE=/var/log/sdwdate.log + +## Done file. Will be created after run no matter if failure or success. +DONE_FILE=/var/run/sdwdate/done + +## Success file. Will only be created after a success. +SUCCESS_FILE=/var/run/sdwdate/success + +## First success file. Will be created after the first success. +FIRST_SUCCESS_FILE=/var/run/sdwdate/first_success + +## How many members per pool are allowed to fail. +## If too many members are not reachable, time will not be adjusted. +ALLOWED_PER_POOL_FAILURE_RATIO=0.34 + +## Temporary directory for file downloads. +## When not set, default to: TEMP_DIR="$(mktemp --directory)" +#TEMP_DIR="" + +## Cache dir. Must not include spaces. +SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" + +## proxy IP +PROXY_IP="127.0.0.1" + +## proxy port +PROXY_PORT="9050" + +## How often sdwdate should run in minutes. +## 0 disables it and sdwdate exits after run. +INTERVAL="180" + +## How many minutes should be waited before running sdwdate again. +## Only has an effect when RANDOMIZE is set to 1 as well. +MIN_INTERVAL="60" + +## Randomize the interval above. +## Minimum 60 minutes. +## Maximum $INTERVAL minutes. +## 0 disabled. +## 1 enabled. +RANDOMIZE="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## starting up. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 0 +SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## re-starting up when sdwdate succeeded at least once after boot. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## in daemon mode. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" + +## sclockadj verbose logging or not +## --no-verbose +## --verbose +## defaults to: --no-verbose +SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" + +## sclockadj change date or not +## --no-debug +## --debug +## defaults to: --no-debug (change date) +SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" + +## If sclockadj should wait before its first iteration. +## --no-first-wait +## --first-wait +## default to: --no-first-wait +SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" + +## Move clock minimum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MIN="500000" + +## Move clock maximum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MAX="500000" + +## Wait nanoseconds minimum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" + +## Wait nanoseconds maximum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" + +## This command will be `eval`uated before DISPATCH_PREREQUISITE and before running url to unixtime tool. +## sdwdate provides the $SDW_MODE variable, which is either set to +## - startup (when the sdwdate process is started) +## - daemon (when the sdwdate process finished one loop and will continue) +## When set to "", it will be skipped. +DISPATCH_PRE="" + +## Prerequisite before trying to connect to servers. +## This is supposed to be a command to be `eval`uated and to exit with code +## - 0, if sdwdate should continue. +## - 1, if sdwdate should terminate itself due to an expected error. +## - 2, if sdwdate should wait 10 seconds and then run the command again. +## - Anything else, if sdwdate should terminate itself due to an unexpected error. +## It may be useful to check if the network is already reachable. +## When set to "", it will be skipped. +DISPATCH_PREREQUISITE="" + +## This command will be `eval`uated when an unexpected error (bug) in sdwdate has been caught. +## sdwdate will provide the $error_message and $DONE_FILE variable. +## Remember to escape variables either using \$ or '$variable'. +## When set to "", it will be skipped. +DISPATCH_POST_ERROR="" + +## Create $DONE_FILE on error. +## 1 enabled. +## 0 disabled. +SDW_TOUCH_DONE_FILE_ON_ERROR="1" + +## Exit 1 on error. This will stop the daemon from running. +## 1 enabled. +## 0 disabled. +SDW_EXIT_ON_ERROR="1" + +## echo remote unix time even when using --quiet +## true - enabled. +## false - disabled. +ECHO_UNIX_TIME="false" + +## This command will be `eval`uated when sdwdate succeeded. +## When set to "", it will be skipped. +DISPATCH_POST_SUCCESS="" + +## This command will be `eval`uated when sdwdate failed. +## When set to "", it will be skipped. +DISPATCH_POST_FAILURE="" + +## This command will be `eval`uated before trying to connect to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before after connecting to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before trying to connect to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before after connecting to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before trying to connect to pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="" + +## This command will be `eval`uated before trying after connecting to the pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" + +## pool syntax +## "url.onion[:port]#comment" +## " +## url.onion[:port]#comment +## [url.onion[:port]#comment] +## [url.onion[:port]#comment] +## [...] +## " +## "url.onion[:port]#comment" +## ... + +## pool one. +## SecureDrop List +## info: +## Last updated Thu Oct 23 16:15:00 PDT 2014 +## Organization Landing Page Tor Hidden Service Address +## in use: +## https://freedom.press/securedrop/directory +## https://freedom.press/sites/default/files/securedrop_list.txt +## https://freedom.press/sites/default/files/securedrop_list.txt.asc +## https://freedom.press/sites/default/files/securedrop.asc +## removed because down: +## "bczjr6ciiblco5ti.onion#Forbes https://safesource.forbes.com bczjr6ciiblco5ti.onion" +## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" +## individual websites +SDWDATE_POOL_ONE=( + "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" + "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" + "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" + "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" + "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" + "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" + "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" + "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" + "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" + "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" + "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" + "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" + "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" + "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" +) + +## pool two. +## Hosted by Thomas White List +## +## GlobalLeaks List +## info: +## https://en.wikipedia.org/wiki/GlobaLeaks#Implementations +## http://www.webcitation.org/6WBrtPlrq +## Name of organization Implementation date Category Tor Url Tor2web Url Country +## removed because down: +## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia +## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" +## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" +## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" +SDWDATE_POOL_TWO=( + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" + "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" + "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" + "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" + "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" + "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" + "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" + "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" + "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" + "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" + "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" + "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" + "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" + "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" +) + +## pool three. +## info: +## individual websites +## riseup.net List +## https://help.riseup.net/en/tor#riseups-tor-hidden-services +## removed because down: +## "suw74isz7wqzpmgu.onion:80#https://www.wikileaks.org/wiki/WikiLeaks:Tor" +## removed because no http: +## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" +SDWDATE_POOL_THREE=( + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" + "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" + "nzh3fv6jc6jskki3.onion#riseup.net: nzh3fv6jc6jskki3.onion (port 443) + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) + "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" + "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" + "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" +) diff --git a/etc/sdwdate.d/30_sdwdate_default b/etc/sdwdate.d/30_sdwdate_default index a12575ee..c6e3b87e 100644 --- a/etc/sdwdate.d/30_sdwdate_default +++ b/etc/sdwdate.d/30_sdwdate_default @@ -266,11 +266,13 @@ SDWDATE_POOL_ONE=( ## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" ## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" SDWDATE_POOL_TWO=( - "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + " + atlas777hhh7mcs7.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + compass6vpxj32p3.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + globe223ezvh6bps.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + bbbbbb6qtmqg65g6.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + pppppptkftqqnfsq.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + " "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" @@ -299,25 +301,27 @@ SDWDATE_POOL_TWO=( ## removed because no http: ## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" SDWDATE_POOL_THREE=( - "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" - "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" - "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" - "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" - "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "3g2upl4pq6kufc4m.onion:80#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion:80#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion:80#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion:80#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion:80#https://bitmessage.org/forum/index.php?topic=1556.0" "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - "nzh3fv6jc6jskki3.onion#riseup.net: nzh3fv6jc6jskki3.onion (port 443) - "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) - "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) - "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) - "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) - "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) - "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) - "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) - "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) - "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) - "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) - "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) + " + nzh3fv6jc6jskki3#riseup.net: nzh3fv6jc6jskki3.onion (port 443) + nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) + cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) + zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) + yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) + xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) + zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) + 5jp7xtmox6jyoqd5:443#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) + zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) + zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) + j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) + 7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) + " "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index f6eea56e..397d2af5 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -15,8 +15,8 @@ def read_pools(): pool_two = [] pool_three = [] - if os.path.exists('/etc/sdwdate.d/'): - files = sorted(glob.glob('/etc/sdwdate.d/*')) + if os.path.exists('/etc/sdwdate-python.d/'): + files = sorted(glob.glob('/etc/sdwdate-python.d/*')) if files: conf_found = False @@ -40,17 +40,14 @@ def read_pools(): elif SDWDATE_POOL_ONE and line.startswith('"'): url = re.search(r'"(.*)#', line) - print '%s' % (url.group(1)) pool_one.append(url.group(1)) elif SDWDATE_POOL_TWO and line.startswith('"'): url = re.search(r'"(.*)#', line) - print '%s' % (url.group(1)) pool_two.append(url.group(1)) elif SDWDATE_POOL_THREE and line.startswith('"'): url = re.search(r'"(.*)#', line) - print '%s' % (url.group(1)) pool_three.append(url.group(1)) if not conf_found: From 92c52e247b8a24e04665f228aadb0edef4a6c32d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 15 Jul 2015 18:11:54 +0000 Subject: [PATCH 016/183] pools time difference --- .../dist-packages/sdwdate/sdwdate.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 03e8abc4..f7b7e86d 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -12,9 +12,7 @@ def __init__(self): self.pool_one, self.pool_two, self.pool_three = read_pools() self.range_pool_one = len(self.pool_one) - self.range_pool_two = len(self.pool_two) - self.range_pool_three = len(self.pool_three) self.number_of_pools = 3 @@ -33,6 +31,7 @@ def __init__(self): self.valid_urls = [] self.unixtimes = [] + self.pools_diff = [] self.invalid_urls = [] self.url_errors = [] @@ -132,29 +131,54 @@ def sdwdate_loop(self): if self.check_remote(self.urls[i], self.returned_values[i]): self.valid_urls.append(self.urls[i]) self.unixtimes.append(self.returned_values[i]) + else: self.invalid_urls.append(self.urls[i]) self.url_errors.append(self.returned_values[i]) + old_unixtime = (time.time()) + print 'old_unixtime %s' % old_unixtime + if not self.pool_one_done: for i in range(len(self.url_random_pool_one)): self.pool_one_done = self.url_random_pool_one[i] in self.valid_urls + if self.pool_one_done: + valid_url = self.url_random_pool_one[i] + ## Values are teturned randomly. Get the index of the url. + index = self.valid_urls.index(valid_url) + ## Pool matching web time. + web_time = self.unixtimes[index] + self.pools_diff.append(int(web_time) - old_unixtime) + print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_one_done %s' % (self.pool_one_done) if not self.pool_two_done: for i in range(len(self.url_random_pool_two)): self.pool_two_done = self.url_random_pool_two[i] in self.valid_urls + if self.pool_two_done: + valid_url = self.url_random_pool_two[i] + index = self.valid_urls.index(valid_url) + web_time = self.unixtimes[index] + self.pools_diff.append(int(web_time) - old_unixtime) + print 'pool_two: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_two_done %s' % (self.pool_two_done) if not self.pool_three_done: for i in range(len(self.url_random_pool_three)): self.pool_three_done = self.url_random_pool_three[i] in self.valid_urls + if self.pool_three_done: + valid_url = self.url_random_pool_three[i] + index = self.valid_urls.index(valid_url) + web_time = self.unixtimes[index] + self.pools_diff.append(int(web_time) - old_unixtime) + print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_three_done %s' % (self.pool_three_done) print 'valid urls %s' % (self.valid_urls) ## Duplicates in bad urls, same url appended because pool not done. ## Remove duplicates print 'bad urls %s' % (list(set(self.invalid_urls))) + print 'pools diff %s' % self.pools_diff print 'End %s' % (time.time()) From 0cfdb32bf4736350821f0743e7f80a15d6803ad4 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 15 Jul 2015 20:04:20 +0000 Subject: [PATCH 017/183] build_median --- .../dist-packages/sdwdate/sdwdate.py | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index f7b7e86d..c49c435a 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -50,10 +50,9 @@ def general_proxy_error(self, pools): def check_remote(self, remote, value): try: - if True: - n = int(value) - print 'check_remote "%s" %s, True' % (remote, value) - return True + n = int(value) + print 'check_remote "%s" %s, True' % (remote, value) + return True except ValueError: print 'check_remote "%s" %s, False' % (remote, value) return False @@ -148,7 +147,7 @@ def sdwdate_loop(self): index = self.valid_urls.index(valid_url) ## Pool matching web time. web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - old_unixtime) + self.pools_diff.append(int(web_time) - int(old_unixtime)) print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_one_done %s' % (self.pool_one_done) @@ -159,7 +158,7 @@ def sdwdate_loop(self): valid_url = self.url_random_pool_two[i] index = self.valid_urls.index(valid_url) web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - old_unixtime) + self.pools_diff.append(int(web_time) - int(old_unixtime)) print 'pool_two: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_two_done %s' % (self.pool_two_done) @@ -170,21 +169,35 @@ def sdwdate_loop(self): valid_url = self.url_random_pool_three[i] index = self.valid_urls.index(valid_url) web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - old_unixtime) + self.pools_diff.append(int(web_time) - int(old_unixtime)) print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_three_done %s' % (self.pool_three_done) print 'valid urls %s' % (self.valid_urls) + print 'pools diff %s' % self.pools_diff ## Duplicates in bad urls, same url appended because pool not done. ## Remove duplicates print 'bad urls %s' % (list(set(self.invalid_urls))) - print 'pools diff %s' % self.pools_diff print 'End %s' % (time.time()) + def build_median(self): + diffs = sorted(self.pools_diff) + print 'sorted %s' % (diffs) + ## We can have more than three values in valid_urls / pools_diff. + if len(diffs) % 2 == 0: + ## Even number of values. Median = sum(2 middle values) / 2 + median = (diffs[(len(diffs) / 2) - 1] + diffs[len(diffs) / 2]) / 2 + else: + ## Odd number of values. Median = middle value. + median = diffs[(len(diffs) / 2)] + print 'median %s' % median + def main(): sdwdate_ = Sdwdate() + sdwdate_.sdwdate_loop() + sdwdate_.build_median() if __name__ == "__main__": main() From d8d12b4dedc91c26dcaf14d5ec1a106844658d43 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 16 Jul 2015 07:22:14 +0000 Subject: [PATCH 018/183] remove duplicate pool members --- usr/lib/python2.7/dist-packages/sdwdate/config.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 397d2af5..b7e0f407 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -52,16 +52,18 @@ def read_pools(): if not conf_found: self.set_default() - print('No valid file found in user configuration folder "/etc/sdwdate.d".'\ - ' Running with default configuration.') + print('No valid file found in user configuration folder "/etc/sdwdate.d".') else: - print('No file found in user configuration folder "/etc/cpfpy.d".'\ - ' Running with default configuration.') + print('No file found in user configuration folder "/etc/cpfpy.d".') else: - print('User configuration folder "/etc/cpfpy.d" does not exist.'\ - ' Running with default configuration.') + print('User configuration folder "/etc/cpfpy.d" does not exist.') + + ## Remove duplicates. + pool_one = list(set(pool_one)) + pool_two = list(set(pool_two)) + pool_three = list(set(pool_three)) return (pool_one, pool_two, pool_three) From 77f4b1d37a2d2b748593aeddad2f7bee889a295f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 16 Jul 2015 11:35:20 +0000 Subject: [PATCH 019/183] no url returned -> no internet connection --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index c49c435a..9cdd96a5 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -1,12 +1,14 @@ #!/usr/bin/env python import sys -import time, random +import time +import random from url_to_unixtime import url_to_unixtime from config import read_pools #from error_handler import SdwdateError + class Sdwdate(): def __init__(self): self.pool_one, self.pool_two, self.pool_three = read_pools() @@ -118,6 +120,11 @@ def sdwdate_loop(self): if len(self.url_random) > 0: print 'random urls %s' % (self.url_random) self.urls, self.returned_values = url_to_unixtime(self.url_random) + if len(self.urls) == 0: + ## Most likely, internet connection is down. + ## Raise eror, log. + print('No values returned from url_to_unixtime') + sys.exit() print 'returned urls "%s"' % (self.urls) else: ## Add code here. From dbe3a4ed2d559f9dfe482af7518e1af143a8df92 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 16 Jul 2015 22:07:02 +0000 Subject: [PATCH 020/183] add_subtract_nanoseconds --- .../dist-packages/sdwdate/sdwdate.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 9cdd96a5..cb3b10bf 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -3,6 +3,7 @@ import sys import time import random +from random import randint from url_to_unixtime import url_to_unixtime from config import read_pools @@ -38,6 +39,8 @@ def __init__(self): self.invalid_urls = [] self.url_errors = [] + self.range_nanoseconds = 999999999 + print 'Start %s' % (time.time()) def general_proxy_error(self, pools): @@ -198,13 +201,36 @@ def build_median(self): else: ## Odd number of values. Median = middle value. median = diffs[(len(diffs) / 2)] + return median + + def add_subtract_nanoseconds(self): + median = self.build_median() + + if median == 0: + print('Time difference = 0. Not setting time') + return + + sign = randint(0, 1) + + nanoseconds = randint(0, self.range_nanoseconds) + seconds = float(nanoseconds) / 1000000000 + + if sign == 0: + newdiff = median + seconds + else: + newdiff = median - seconds + + print 'nanoseconds %s' % nanoseconds + print 'seconds %s' % seconds + print 'sign %s' % sign print 'median %s' % median + print 'newdiff %s' % newdiff def main(): sdwdate_ = Sdwdate() sdwdate_.sdwdate_loop() - sdwdate_.build_median() + sdwdate_.add_subtract_nanoseconds() if __name__ == "__main__": main() From 513f69e885ed7e9103012acb94e2f862ae220935 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 16 Jul 2015 22:14:47 +0000 Subject: [PATCH 021/183] build_median called from main --- .../python2.7/dist-packages/sdwdate/sdwdate.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index cb3b10bf..585fdd43 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -39,6 +39,7 @@ def __init__(self): self.invalid_urls = [] self.url_errors = [] + self.median = 0 self.range_nanoseconds = 999999999 print 'Start %s' % (time.time()) @@ -197,16 +198,15 @@ def build_median(self): ## We can have more than three values in valid_urls / pools_diff. if len(diffs) % 2 == 0: ## Even number of values. Median = sum(2 middle values) / 2 - median = (diffs[(len(diffs) / 2) - 1] + diffs[len(diffs) / 2]) / 2 + self.median = (diffs[(len(diffs) / 2) - 1] + diffs[len(diffs) / 2]) / 2 else: ## Odd number of values. Median = middle value. - median = diffs[(len(diffs) / 2)] - return median + self.median = diffs[(len(diffs) / 2)] def add_subtract_nanoseconds(self): median = self.build_median() - if median == 0: + if self.median == 0: print('Time difference = 0. Not setting time') return @@ -216,20 +216,21 @@ def add_subtract_nanoseconds(self): seconds = float(nanoseconds) / 1000000000 if sign == 0: - newdiff = median + seconds + newdiff = self.median + seconds else: - newdiff = median - seconds + newdiff = self.median - seconds print 'nanoseconds %s' % nanoseconds print 'seconds %s' % seconds print 'sign %s' % sign - print 'median %s' % median + print 'median %s' % self.median print 'newdiff %s' % newdiff def main(): sdwdate_ = Sdwdate() sdwdate_.sdwdate_loop() + sdwdate_.build_median() sdwdate_.add_subtract_nanoseconds() if __name__ == "__main__": From 166fcd9f9f5137a53e3f7899d4d09c9b13e0505c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 20 Jul 2015 19:28:29 +0000 Subject: [PATCH 022/183] run_sclockadj, kill_sclockadj functions --- .../dist-packages/sdwdate/sdwdate.py | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 585fdd43..6b13868b 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -4,6 +4,7 @@ import time import random from random import randint +from subprocess import Popen, call, PIPE from url_to_unixtime import url_to_unixtime from config import read_pools @@ -41,6 +42,9 @@ def __init__(self): self.median = 0 self.range_nanoseconds = 999999999 + self.newdiff_nanoseconds = 0 + + self.sclockadj_pid = 0 print 'Start %s' % (time.time()) @@ -208,7 +212,7 @@ def add_subtract_nanoseconds(self): if self.median == 0: print('Time difference = 0. Not setting time') - return + return False sign = randint(0, 1) @@ -216,22 +220,67 @@ def add_subtract_nanoseconds(self): seconds = float(nanoseconds) / 1000000000 if sign == 0: - newdiff = self.median + seconds + new_diff = self.median + seconds else: - newdiff = self.median - seconds + new_diff = self.median - seconds + + self.newdiff_nanoseconds = int(new_diff * 1000000000) print 'nanoseconds %s' % nanoseconds print 'seconds %s' % seconds print 'sign %s' % sign print 'median %s' % self.median - print 'newdiff %s' % newdiff + print 'new_diff %s' % new_diff + + return True + + def run_sclockadj(self): + if self.newdiff_nanoseconds > 0: + add_subtract = "--add" + else: + add_subtract = "--subtract" + cmd = [ + "sudo", + "INLINEDIR=/var/cache/sdwdate/sclockadj", + "/usr/lib/sdwdate/sclockadj", + "--debug", + "--verbose", + "--no-systohc", + "--no-first-wait", + "--move-min", "5000000", + "--move-max", "5000000", + "--wait-min", "1000000000", + "--wait-max", "1000000000", + add_subtract, str(abs(self.newdiff_nanoseconds))] + + ## Run sclockadj in a subshell. + sclockadj = Popen(cmd) + + self.sclockadj_pid = sclockadj.pid + + ## Running sclockadj_debug_helper, in case... + cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] + ## Pipe stdout in subprocess. + helper = Popen(cmd, stdout=PIPE) + ## Read the output. + line = helper.stdout.read() + print line + + def kill_sclockadj(self): + cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) + call(cmd, shell=True) + def main(): sdwdate_ = Sdwdate() sdwdate_.sdwdate_loop() sdwdate_.build_median() - sdwdate_.add_subtract_nanoseconds() + if sdwdate_.add_subtract_nanoseconds() == True: + sdwdate_.run_sclockadj() + + time.sleep(10) + sdwdate_.kill_sclockadj() if __name__ == "__main__": main() From eb899d3a509b3cbbec6e0fbdda5fa37ad69d7808 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 22 Jul 2015 16:25:34 +0000 Subject: [PATCH 023/183] + set_time_using_date() --- .../dist-packages/sdwdate/sdwdate.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 6b13868b..f870a5fa 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -42,6 +42,7 @@ def __init__(self): self.median = 0 self.range_nanoseconds = 999999999 + self.new_diff = 0 self.newdiff_nanoseconds = 0 self.sclockadj_pid = 0 @@ -220,17 +221,17 @@ def add_subtract_nanoseconds(self): seconds = float(nanoseconds) / 1000000000 if sign == 0: - new_diff = self.median + seconds + self.new_diff = self.median + seconds else: - new_diff = self.median - seconds + self.new_diff = self.median - seconds - self.newdiff_nanoseconds = int(new_diff * 1000000000) + self.newdiff_nanoseconds = int(self.new_diff * 1000000000) print 'nanoseconds %s' % nanoseconds print 'seconds %s' % seconds print 'sign %s' % sign print 'median %s' % self.median - print 'new_diff %s' % new_diff + print 'new_diff %s' % self.new_diff return True @@ -270,6 +271,19 @@ def kill_sclockadj(self): cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) call(cmd, shell=True) + def set_time_using_date(self): + old_unixtime = time.time() + print 'Old time %.9f' % (old_unixtime) + new_unixtime = '%.9f' % (old_unixtime + self.new_diff) + print 'New time %s' % str(new_unixtime) + ## Debug: print old date. + cmd = '/bin/date' + call(cmd, shell=True) + cmd = '/bin/date --set @' + str(new_unixtime) + print cmd + ## Set and print new date. + call(cmd, shell=True) + def main(): sdwdate_ = Sdwdate() @@ -277,10 +291,11 @@ def main(): sdwdate_.sdwdate_loop() sdwdate_.build_median() if sdwdate_.add_subtract_nanoseconds() == True: - sdwdate_.run_sclockadj() + sdwdate_.set_time_using_date() + #sdwdate_.run_sclockadj() - time.sleep(10) - sdwdate_.kill_sclockadj() + #time.sleep(10) + #sdwdate_.kill_sclockadj() if __name__ == "__main__": main() From 560d3a115f76b378ef39629d4e2120017e9a2b3c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 08:46:03 +0000 Subject: [PATCH 024/183] main loop, debugging sdwate_loop / url_to_unixtime --- .../dist-packages/sdwdate/sdwdate.py | 74 ++++++++++--------- .../dist-packages/sdwdate/url_to_unixtime.py | 5 +- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index f870a5fa..3be92325 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -29,6 +29,9 @@ def __init__(self): self.already_picked_index_pool_two = [] self.already_picked_index_pool_three = [] + self.urls = [] + self.url_random = [] + self.url_random_pool_one = [] self.url_random_pool_two = [] self.url_random_pool_three = [] @@ -71,8 +74,11 @@ def check_remote(self, remote, value): def sdwdate_loop(self): while len(self.valid_urls) < self.number_of_pools: print "MAIN LOOP" - self.urls = [] - self.url_random = [] + print 'valid_urls %s' % len(self.valid_urls) + print 'number_of_pools %s' % self.number_of_pools + ## Clear the list. + self.urls[:] = [] + self.url_random[:] = [] if not self.pool_one_done: while True: @@ -132,7 +138,7 @@ def sdwdate_loop(self): if len(self.urls) == 0: ## Most likely, internet connection is down. ## Raise eror, log. - print('No values returned from url_to_unixtime') + print('No values returned from url_to_unixtime.') sys.exit() print 'returned urls "%s"' % (self.urls) else: @@ -140,8 +146,8 @@ def sdwdate_loop(self): sys.exit(1) if not self.general_proxy_error(self.returned_values): - self.valid_urls = [] - self.unixtimes = [] + #self.valid_urls = [] + #self.unixtimes = [] for i in range(len(self.urls)): if self.check_remote(self.urls[i], self.returned_values[i]): self.valid_urls.append(self.urls[i]) @@ -186,14 +192,14 @@ def sdwdate_loop(self): index = self.valid_urls.index(valid_url) web_time = self.unixtimes[index] self.pools_diff.append(int(web_time) - int(old_unixtime)) - print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) + print 'pool_three: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_three_done %s' % (self.pool_three_done) - print 'valid urls %s' % (self.valid_urls) - print 'pools diff %s' % self.pools_diff - ## Duplicates in bad urls, same url appended because pool not done. - ## Remove duplicates - print 'bad urls %s' % (list(set(self.invalid_urls))) + print 'valid urls %s' % (self.valid_urls) + #print 'pools diff %s' % self.pools_diff + ### Duplicates in bad urls, same url appended because pool not done. + ### Remove duplicates + #print 'bad urls %s' % (list(set(self.invalid_urls))) print 'End %s' % (time.time()) @@ -208,12 +214,15 @@ def build_median(self): ## Odd number of values. Median = middle value. self.median = diffs[(len(diffs) / 2)] - def add_subtract_nanoseconds(self): - median = self.build_median() - + def maybe_set_new_time(self): if self.median == 0: print('Time difference = 0. Not setting time') return False + else: + return True + + def add_subtract_nanoseconds(self): + median = self.build_median() sign = randint(0, 1) @@ -244,8 +253,8 @@ def run_sclockadj(self): "sudo", "INLINEDIR=/var/cache/sdwdate/sclockadj", "/usr/lib/sdwdate/sclockadj", - "--debug", - "--verbose", + "--no-debug", + "--no-verbose", "--no-systohc", "--no-first-wait", "--move-min", "5000000", @@ -256,16 +265,16 @@ def run_sclockadj(self): ## Run sclockadj in a subshell. sclockadj = Popen(cmd) - self.sclockadj_pid = sclockadj.pid ## Running sclockadj_debug_helper, in case... - cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] - ## Pipe stdout in subprocess. - helper = Popen(cmd, stdout=PIPE) - ## Read the output. - line = helper.stdout.read() - print line + ## May be read the last line to ensure sclockadj is running. + #cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] + ### Pipe stdout in subprocess. + #helper = Popen(cmd, stdout=PIPE) + ### Read the output. + #line = helper.stdout.read() + #print line def kill_sclockadj(self): cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) @@ -279,23 +288,22 @@ def set_time_using_date(self): ## Debug: print old date. cmd = '/bin/date' call(cmd, shell=True) + ## Set new time. cmd = '/bin/date --set @' + str(new_unixtime) print cmd - ## Set and print new date. call(cmd, shell=True) - def main(): - sdwdate_ = Sdwdate() + while True: + sdwdate_ = Sdwdate() + sdwdate_.sdwdate_loop() + sdwdate_.build_median() - sdwdate_.sdwdate_loop() - sdwdate_.build_median() - if sdwdate_.add_subtract_nanoseconds() == True: - sdwdate_.set_time_using_date() - #sdwdate_.run_sclockadj() + if sdwdate_.maybe_set_new_time(): + sdwdate_.set_time_using_date() - #time.sleep(10) - #sdwdate_.kill_sclockadj() + print 'sleeping..\n\n\n' + time.sleep(30) if __name__ == "__main__": main() diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index 0af5bd56..9208c4a1 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -133,13 +133,16 @@ def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): def url_to_unixtime(remotes): #print remotes - url = [] threads = [] timeout = gevent.Timeout() timer = [] seconds = 10 + ## Clear lists. + del urls[:] + del unix_times[:] + print 'GEVENT started' for i in range(len(remotes)): From ea8766a15bdd6312b09db8e6387bc5901d7c2dac Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 09:02:41 +0000 Subject: [PATCH 025/183] debugging, minor --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 3be92325..5a77f7e3 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -222,10 +222,7 @@ def maybe_set_new_time(self): return True def add_subtract_nanoseconds(self): - median = self.build_median() - sign = randint(0, 1) - nanoseconds = randint(0, self.range_nanoseconds) seconds = float(nanoseconds) / 1000000000 @@ -300,6 +297,7 @@ def main(): sdwdate_.build_median() if sdwdate_.maybe_set_new_time(): + sdwdate_.add_subtract_nanoseconds() sdwdate_.set_time_using_date() print 'sleeping..\n\n\n' From 5dd8997583aa0219cbebfa8bb00c7140604b2d05 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 12:31:33 +0000 Subject: [PATCH 026/183] kill timed out threads --- usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index 9208c4a1..71574ba4 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -157,7 +157,7 @@ def url_to_unixtime(remotes): except Timeout: urls.append(threads[i].args[2]) unix_times.append('Timeout') + gevent.kill(threads[i]) print 'GEVENT exiting' - return urls, unix_times From 2bc715d5ba16ef0876c3d6d77f2d26e33c881ed2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 12:43:50 +0000 Subject: [PATCH 027/183] after gevent.kill, only three possible valid urls (one per pool) -> simplified buil_median --- .../python2.7/dist-packages/sdwdate/sdwdate.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 5a77f7e3..39bb2a7f 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -195,24 +195,16 @@ def sdwdate_loop(self): print 'pool_three: last_url %s, web_time %s' % (valid_url, web_time) print 'pool_three_done %s' % (self.pool_three_done) - print 'valid urls %s' % (self.valid_urls) - #print 'pools diff %s' % self.pools_diff - ### Duplicates in bad urls, same url appended because pool not done. - ### Remove duplicates - #print 'bad urls %s' % (list(set(self.invalid_urls))) + print 'valid urls %s' % (self.valid_urls) + print 'pools diff %s' % self.pools_diff + print 'bad urls %s' % (self.invalid_urls) print 'End %s' % (time.time()) def build_median(self): diffs = sorted(self.pools_diff) print 'sorted %s' % (diffs) - ## We can have more than three values in valid_urls / pools_diff. - if len(diffs) % 2 == 0: - ## Even number of values. Median = sum(2 middle values) / 2 - self.median = (diffs[(len(diffs) / 2) - 1] + diffs[len(diffs) / 2]) / 2 - else: - ## Odd number of values. Median = middle value. - self.median = diffs[(len(diffs) / 2)] + self.median = diffs[(len(diffs) / 2)] def maybe_set_new_time(self): if self.median == 0: From d51b96639ea26212e381860d3d3f1936ff07e230 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 15:04:42 +0000 Subject: [PATCH 028/183] check sclockadf pid. docstrings. confog cleanup. --- .../python2.7/dist-packages/sdwdate/config.py | 6 +-- .../dist-packages/sdwdate/sdwdate.py | 38 ++++++++++++++++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index b7e0f407..c86616fe 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -10,7 +10,6 @@ def read_pools(): SDWDATE_POOL_TWO = False SDWDATE_POOL_THREE = False - multi_line = False pool_one = [] pool_two = [] pool_three = [] @@ -51,14 +50,13 @@ def read_pools(): pool_three.append(url.group(1)) if not conf_found: - self.set_default() print('No valid file found in user configuration folder "/etc/sdwdate.d".') else: - print('No file found in user configuration folder "/etc/cpfpy.d".') + print('No file found in user configuration folder "/etc/sdwdate.d".') else: - print('User configuration folder "/etc/cpfpy.d" does not exist.') + print('User configuration folder "/etc/sdwdate.d" does not exist.') ## Remove duplicates. pool_one = list(set(pool_one)) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 39bb2a7f..210e9613 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -53,6 +53,9 @@ def __init__(self): print 'Start %s' % (time.time()) def general_proxy_error(self, pools): + ''' + This error occurs (at least) when Tor is not running. + ''' if (pools[0] == 'Connection closed unexpectedly' and pools[1] == 'Connection closed unexpectedly' and pools[2] == 'Connection closed unexpectedly'): @@ -63,6 +66,9 @@ def general_proxy_error(self, pools): return False def check_remote(self, remote, value): + ''' + Check returned value. True if numeric. + ''' try: n = int(value) print 'check_remote "%s" %s, True' % (remote, value) @@ -72,6 +78,12 @@ def check_remote(self, remote, value): return False def sdwdate_loop(self): + ''' + Check remotes. + Pick a random url in eaxh pool, check the returned value. + Append valid urls if time is returned, otherwise restart a cycle + with a new random url, until every pool has a time value. + ''' while len(self.valid_urls) < self.number_of_pools: print "MAIN LOOP" print 'valid_urls %s' % len(self.valid_urls) @@ -146,8 +158,6 @@ def sdwdate_loop(self): sys.exit(1) if not self.general_proxy_error(self.returned_values): - #self.valid_urls = [] - #self.unixtimes = [] for i in range(len(self.urls)): if self.check_remote(self.urls[i], self.returned_values[i]): self.valid_urls.append(self.urls[i]) @@ -202,11 +212,17 @@ def sdwdate_loop(self): print 'End %s' % (time.time()) def build_median(self): + ''' + Get the median (not average) from the list of values. + ''' diffs = sorted(self.pools_diff) print 'sorted %s' % (diffs) self.median = diffs[(len(diffs) / 2)] def maybe_set_new_time(self): + ''' + Do not set time if diff = 0. + ''' if self.median == 0: print('Time difference = 0. Not setting time') return False @@ -214,6 +230,10 @@ def maybe_set_new_time(self): return True def add_subtract_nanoseconds(self): + ''' + Could we replace this in sdwdate_loop pool_diff calcuations? + -> int(web_time) - old_unixtime + ''' sign = randint(0, 1) nanoseconds = randint(0, self.range_nanoseconds) seconds = float(nanoseconds) / 1000000000 @@ -234,6 +254,10 @@ def add_subtract_nanoseconds(self): return True def run_sclockadj(self): + ''' + Set time with sneaky_clock_adjuster. + Should we use sclockadj_debug_helper? + ''' if self.newdiff_nanoseconds > 0: add_subtract = "--add" else: @@ -266,6 +290,8 @@ def run_sclockadj(self): #print line def kill_sclockadj(self): + ''' + ''' cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) call(cmd, shell=True) @@ -274,12 +300,11 @@ def set_time_using_date(self): print 'Old time %.9f' % (old_unixtime) new_unixtime = '%.9f' % (old_unixtime + self.new_diff) print 'New time %s' % str(new_unixtime) - ## Debug: print old date. + ## Debug: print old time. cmd = '/bin/date' call(cmd, shell=True) ## Set new time. cmd = '/bin/date --set @' + str(new_unixtime) - print cmd call(cmd, shell=True) def main(): @@ -290,10 +315,13 @@ def main(): if sdwdate_.maybe_set_new_time(): sdwdate_.add_subtract_nanoseconds() + #sdwdate_.run_sclockadj() sdwdate_.set_time_using_date() print 'sleeping..\n\n\n' - time.sleep(30) + time.sleep(10) + if sdwdate_.sclockadj_pid != 0: + sdwdate_.kill_sclockadj() if __name__ == "__main__": main() From 359c4621298618165a031e98f0ecde4ec2b9a70b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 18:39:02 +0000 Subject: [PATCH 029/183] first_success in /tmp/sdwdate --- .../dist-packages/sdwdate/sdwdate.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 210e9613..c6f683a1 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys +import os import time import random from random import randint @@ -308,6 +309,12 @@ def set_time_using_date(self): call(cmd, shell=True) def main(): + first_success_dir = '/tmp/sdwdate/' + if not os.path.exists(first_success_dir): + os.mkdir(first_success_dir) + + first_success_file = first_success_dir + 'first_success' + while True: sdwdate_ = Sdwdate() sdwdate_.sdwdate_loop() @@ -315,11 +322,15 @@ def main(): if sdwdate_.maybe_set_new_time(): sdwdate_.add_subtract_nanoseconds() - #sdwdate_.run_sclockadj() - sdwdate_.set_time_using_date() + if os.path.exists(first_success_file): + sdwdate_.run_sclockadj() + else: + sdwdate_.set_time_using_date() + f = open(first_success_file, 'w') + f.close() - print 'sleeping..\n\n\n' - time.sleep(10) + print 'sleeping...\n\n\n' + time.sleep(30) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() From ad0af53f504e21165d302b2d489c32b4030c66de Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 19:25:20 +0000 Subject: [PATCH 030/183] catch SIGTERM -> kill sclockadj, exit 143 --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index c6f683a1..35e28b0c 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys +import gevent import os import time import random @@ -308,7 +309,14 @@ def set_time_using_date(self): cmd = '/bin/date --set @' + str(new_unixtime) call(cmd, shell=True) +def signal_sigterm_handler(): + if sdwdate_.sclockadj_pid != 0: + sdwdate_.kill_sclockadj() + sys.exit(143) + def main(): + gevent.signal(signal.SIGTERM, signal_sigterm_handler) + first_success_dir = '/tmp/sdwdate/' if not os.path.exists(first_success_dir): os.mkdir(first_success_dir) From 61a2db97e5632b8205d19cac36c2839d9dbde839 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 19:36:52 +0000 Subject: [PATCH 031/183] import signal --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 35e28b0c..7d90a0e7 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys +import signal import gevent import os import time From 1bf950d4acaba71c966580b87f46ca6091e3e391 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 24 Jul 2015 19:51:36 +0000 Subject: [PATCH 032/183] [sudo /bin/date --set] in set_time_using_date --- usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py index 7d90a0e7..0926f5e0 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py @@ -307,7 +307,8 @@ def set_time_using_date(self): cmd = '/bin/date' call(cmd, shell=True) ## Set new time. - cmd = '/bin/date --set @' + str(new_unixtime) + ## There is a /etc/sudoers.d exception. + cmd = 'sudo /bin/date --set @' + str(new_unixtime) call(cmd, shell=True) def signal_sigterm_handler(): From c8ebd54bd8a0348d726fb6cfa63ec4a864c3affe Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 26 Jul 2015 22:08:56 +0000 Subject: [PATCH 033/183] /usr/sbin/sdwdate. logging. --- .../python2.7/dist-packages/sdwdate/init.py | 1 + .../sdwdate/sdwdate.py => sbin/sdwdate} | 170 ++++++++++++------ 2 files changed, 116 insertions(+), 55 deletions(-) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/init.py rename usr/{lib/python2.7/dist-packages/sdwdate/sdwdate.py => sbin/sdwdate} (69%) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/init.py b/usr/lib/python2.7/dist-packages/sdwdate/init.py new file mode 100644 index 00000000..8d1c8b69 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/init.py @@ -0,0 +1 @@ + diff --git a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py b/usr/sbin/sdwdate similarity index 69% rename from usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py rename to usr/sbin/sdwdate index 0926f5e0..6667b4c2 100755 --- a/usr/lib/python2.7/dist-packages/sdwdate/sdwdate.py +++ b/usr/sbin/sdwdate @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys +import logging import signal import gevent import os @@ -9,14 +10,16 @@ from random import randint from subprocess import Popen, call, PIPE -from url_to_unixtime import url_to_unixtime -from config import read_pools +from sdwdate import url_to_unixtime +from sdwdate import config #from error_handler import SdwdateError class Sdwdate(): def __init__(self): - self.pool_one, self.pool_two, self.pool_three = read_pools() + self.pool_one, self.pool_two, self.pool_three = config.read_pools() + + self.iteration = 0 self.range_pool_one = len(self.pool_one) self.range_pool_two = len(self.pool_two) @@ -53,7 +56,9 @@ def __init__(self): self.sclockadj_pid = 0 - print 'Start %s' % (time.time()) + message = 'Fetching remote times, start %s' % (time.time()) + print(message) + logger.debug(message) def general_proxy_error(self, pools): ''' @@ -63,7 +68,7 @@ def general_proxy_error(self, pools): pools[1] == 'Connection closed unexpectedly' and pools[2] == 'Connection closed unexpectedly'): ## Raise error, log, user warning. - print 'General Proxy Error' + logger.critical('General Proxy Error') sys.exit(1) return False @@ -74,10 +79,14 @@ def check_remote(self, remote, value): ''' try: n = int(value) - print 'check_remote "%s" %s, True' % (remote, value) + message = 'Remote status "%s", True' % (remote) + print(message) + logger.debug(message) return True except ValueError: - print 'check_remote "%s" %s, False' % (remote, value) + message = 'Remote status "%s", False: %s' % (remote, value) + print(message) + logger.debug(message) return False def sdwdate_loop(self): @@ -88,10 +97,12 @@ def sdwdate_loop(self): with a new random url, until every pool has a time value. ''' while len(self.valid_urls) < self.number_of_pools: - print "MAIN LOOP" - print 'valid_urls %s' % len(self.valid_urls) - print 'number_of_pools %s' % self.number_of_pools - ## Clear the list. + self.iteration = self.iteration + 1 + message = 'Main loop, iteration %s' % (self.iteration) + print(message) + logger.debug(message) + + ## Clear the lists. self.urls[:] = [] self.url_random[:] = [] @@ -107,7 +118,7 @@ def sdwdate_loop(self): if url_index not in self.already_picked_index_pool_one: self.already_picked_index_pool_one.append(url_index) - print 'pool 1 added %s' % (self.pool_one[url_index[0]]) + #print 'pool 1 added %s' % (self.pool_one[url_index[0]]) self.url_random_pool_one.append(self.pool_one[url_index[0]]) self.url_random.append(self.pool_one[url_index[0]]) break @@ -124,7 +135,7 @@ def sdwdate_loop(self): if url_index not in self.already_picked_index_pool_two: self.already_picked_index_pool_two.append(url_index) - print 'pool 2 added %s' % (self.pool_two[url_index[0]]) + #print 'pool 2 added %s' % (self.pool_two[url_index[0]]) self.url_random_pool_two.append(self.pool_two[url_index[0]]) self.url_random.append(self.pool_two[url_index[0]]) break @@ -141,21 +152,28 @@ def sdwdate_loop(self): if url_index not in self.already_picked_index_pool_three: self.already_picked_index_pool_three.append(url_index) - print 'pool 3 added %s' % (self.pool_three[url_index[0]]) + #print 'pool 3 added %s' % (self.pool_three[url_index[0]]) self.url_random_pool_three.append(self.pool_three[url_index[0]]) self.url_random.append(self.pool_three[url_index[0]]) break ## Fetch remotes. if len(self.url_random) > 0: - print 'random urls %s' % (self.url_random) - self.urls, self.returned_values = url_to_unixtime(self.url_random) + message = 'Random urls %s' % (self.url_random) + print(message) + logger.debug(message) + + self.urls, self.returned_values = url_to_unixtime.url_to_unixtime(self.url_random) if len(self.urls) == 0: ## Most likely, internet connection is down. ## Raise eror, log. - print('No values returned from url_to_unixtime.') + message = ('No values returned from url_to_unixtime. Internet connection might be down.') + print(message) + logger.critical(message) sys.exit() - print 'returned urls "%s"' % (self.urls) + message = 'returned urls "%s"' % (self.urls) + print(message) + logger.debug(message) else: ## Add code here. sys.exit(1) @@ -171,7 +189,7 @@ def sdwdate_loop(self): self.url_errors.append(self.returned_values[i]) old_unixtime = (time.time()) - print 'old_unixtime %s' % old_unixtime + #print 'old_unixtime %s' % old_unixtime if not self.pool_one_done: for i in range(len(self.url_random_pool_one)): @@ -183,8 +201,10 @@ def sdwdate_loop(self): ## Pool matching web time. web_time = self.unixtimes[index] self.pools_diff.append(int(web_time) - int(old_unixtime)) - print 'pool_one: last_url %s, web_time %s' % (valid_url, web_time) - print 'pool_one_done %s' % (self.pool_one_done) + message = 'Pool one: last_url %s, web_time %s' % (valid_url, web_time) + print(message) + logger.debug(message) + #print 'pool_one_done %s' % (self.pool_one_done) if not self.pool_two_done: for i in range(len(self.url_random_pool_two)): @@ -194,8 +214,10 @@ def sdwdate_loop(self): index = self.valid_urls.index(valid_url) web_time = self.unixtimes[index] self.pools_diff.append(int(web_time) - int(old_unixtime)) - print 'pool_two: last_url %s, web_time %s' % (valid_url, web_time) - print 'pool_two_done %s' % (self.pool_two_done) + message = 'Pool two: last_url %s, web_time %s' % (valid_url, web_time) + print(message) + logger.debug(message) + #print 'pool_two_done %s' % (self.pool_two_done) if not self.pool_three_done: for i in range(len(self.url_random_pool_three)): @@ -205,29 +227,43 @@ def sdwdate_loop(self): index = self.valid_urls.index(valid_url) web_time = self.unixtimes[index] self.pools_diff.append(int(web_time) - int(old_unixtime)) - print 'pool_three: last_url %s, web_time %s' % (valid_url, web_time) - print 'pool_three_done %s' % (self.pool_three_done) - - print 'valid urls %s' % (self.valid_urls) - print 'pools diff %s' % self.pools_diff - print 'bad urls %s' % (self.invalid_urls) - - print 'End %s' % (time.time()) + message = 'Pool three: last_url %s, web_time %s' % (valid_url, web_time) + print(message) + logger.debug(message) + #print 'pool_three_done %s' % (self.pool_three_done) + + message = 'Valid urls %s' % (self.valid_urls) + print(message) + logger.debug(message) + #message = 'Pool diff %s' % self.pools_diff + #print(message) + #logger.debug(message) + message = 'bad urls %s' % (self.invalid_urls) + print(message) + logger.debug(message) + + message = 'Fetching remote times, end %s' % (time.time()) + print(message) + logger.debug(message) def build_median(self): ''' Get the median (not average) from the list of values. ''' diffs = sorted(self.pools_diff) - print 'sorted %s' % (diffs) + message = 'Pool differences, sorted: %s' % diffs + print(message) + logger.debug(message) self.median = diffs[(len(diffs) / 2)] - def maybe_set_new_time(self): + def set_new_time(self): ''' Do not set time if diff = 0. ''' if self.median == 0: - print('Time difference = 0. Not setting time') + message = 'Time difference = 0. Not setting time' + print(message) + logger.debug(message) return False else: return True @@ -237,6 +273,7 @@ def add_subtract_nanoseconds(self): Could we replace this in sdwdate_loop pool_diff calcuations? -> int(web_time) - old_unixtime ''' + signs = ['+', '-'] sign = randint(0, 1) nanoseconds = randint(0, self.range_nanoseconds) seconds = float(nanoseconds) / 1000000000 @@ -248,11 +285,16 @@ def add_subtract_nanoseconds(self): self.newdiff_nanoseconds = int(self.new_diff * 1000000000) - print 'nanoseconds %s' % nanoseconds - print 'seconds %s' % seconds - print 'sign %s' % sign - print 'median %s' % self.median - print 'new_diff %s' % self.new_diff + #print 'nanoseconds %s' % nanoseconds + message = 'Median time difference: %s' % self.median + print(message) + logger.debug(message) + message = 'Seconds to add: %s %s' % (signs[sign], seconds) + print(message) + logger.debug(message) + message = 'New time difference: %s' % self.new_diff + print(message) + logger.debug(message) return True @@ -283,6 +325,10 @@ def run_sclockadj(self): sclockadj = Popen(cmd) self.sclockadj_pid = sclockadj.pid + message = 'Running sclockaj, PID=%s' % (self.sclockadj_pid) + print(message) + logger.debug(message) + ## Running sclockadj_debug_helper, in case... ## May be read the last line to ensure sclockadj is running. #cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] @@ -299,27 +345,41 @@ def kill_sclockadj(self): call(cmd, shell=True) def set_time_using_date(self): - old_unixtime = time.time() - print 'Old time %.9f' % (old_unixtime) - new_unixtime = '%.9f' % (old_unixtime + self.new_diff) - print 'New time %s' % str(new_unixtime) - ## Debug: print old time. - cmd = '/bin/date' - call(cmd, shell=True) + message = 'setting time using date.' + print(message) + logger.debug(message) + + old_unixtime = float('%.9f' % (time.time())) + message = 'Old unixttime: %s' % (str(old_unixtime)) + print(message) + logger.debug(message) + + new_unixtime = float('%.9f' % (old_unixtime + self.new_diff)) + message = 'New unixtime %s' % (str(new_unixtime)) + print(message) + logger.debug(message) + ## Set new time. - ## There is a /etc/sudoers.d exception. cmd = 'sudo /bin/date --set @' + str(new_unixtime) call(cmd, shell=True) def signal_sigterm_handler(): if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() + logger.warning('Signal SIGTERM received. Exiting.') sys.exit(143) -def main(): +if __name__ == "__main__": gevent.signal(signal.SIGTERM, signal_sigterm_handler) - first_success_dir = '/tmp/sdwdate/' + logger = logging.getLogger('sdwdate_log') + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + handler = logging.FileHandler('/var/log/sdwdate-python.log') + handler.setFormatter(formatter) + logger.addHandler(handler) + + first_success_dir = '/run/sdwdate/' if not os.path.exists(first_success_dir): os.mkdir(first_success_dir) @@ -330,7 +390,7 @@ def main(): sdwdate_.sdwdate_loop() sdwdate_.build_median() - if sdwdate_.maybe_set_new_time(): + if sdwdate_.set_new_time(): sdwdate_.add_subtract_nanoseconds() if os.path.exists(first_success_file): sdwdate_.run_sclockadj() @@ -339,10 +399,10 @@ def main(): f = open(first_success_file, 'w') f.close() - print 'sleeping...\n\n\n' - time.sleep(30) + message = 'Sleeping...' + print(message + '\n\n') + logger.debug(message) + + time.sleep(20) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() - -if __name__ == "__main__": - main() From c0dff2b9e9967f69ac4fb5e71762c41f7532a18b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 27 Jul 2015 19:20:50 +0000 Subject: [PATCH 034/183] __init__.py --- usr/lib/python2.7/dist-packages/sdwdate/__init__.py | 1 + usr/lib/python2.7/dist-packages/sdwdate/init.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/__init__.py delete mode 100644 usr/lib/python2.7/dist-packages/sdwdate/init.py diff --git a/usr/lib/python2.7/dist-packages/sdwdate/__init__.py b/usr/lib/python2.7/dist-packages/sdwdate/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/__init__.py @@ -0,0 +1 @@ + diff --git a/usr/lib/python2.7/dist-packages/sdwdate/init.py b/usr/lib/python2.7/dist-packages/sdwdate/init.py deleted file mode 100644 index 8d1c8b69..00000000 --- a/usr/lib/python2.7/dist-packages/sdwdate/init.py +++ /dev/null @@ -1 +0,0 @@ - From 103bc74240632d1c4c0e4c42bf11db3cab7570a2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 28 Jul 2015 19:46:40 +0000 Subject: [PATCH 035/183] /usr/sbin/sdwdate in lib/systemd/system/sdwdate.service --- lib/systemd/system/sdwdate.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/systemd/system/sdwdate.service b/lib/systemd/system/sdwdate.service index ae3e936f..0107e61d 100644 --- a/lib/systemd/system/sdwdate.service +++ b/lib/systemd/system/sdwdate.service @@ -14,7 +14,7 @@ Wants=tor.service Type=simple User=sdwdate Group=sdwdate -ExecStart=/usr/bin/sdwdate +ExecStart=/usr/sbin/sdwdate SuccessExitStatus=143 KillMode=process TimeoutSec=30 From cc44be7b7983a24c67a82fabfd549f9f6f03f277 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 28 Jul 2015 19:47:39 +0000 Subject: [PATCH 036/183] usr/lib/tmpfiles.d/sdwdate.conf --- usr/lib/tmpfiles.d/sdwdate.conf | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 usr/lib/tmpfiles.d/sdwdate.conf diff --git a/usr/lib/tmpfiles.d/sdwdate.conf b/usr/lib/tmpfiles.d/sdwdate.conf new file mode 100644 index 00000000..31832d73 --- /dev/null +++ b/usr/lib/tmpfiles.d/sdwdate.conf @@ -0,0 +1,6 @@ +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +d /var/run/sdwdate 0775 sdwdate sdwdate +f /var/log/sdwdate.log 0775 sdwdate sdwdate From 09ec978cdfe487d1203da1e7f29b2ad5a3ca5aa8 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 28 Jul 2015 19:49:31 +0000 Subject: [PATCH 037/183] log file --- usr/sbin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/sbin/sdwdate b/usr/sbin/sdwdate index 6667b4c2..dc08ea63 100755 --- a/usr/sbin/sdwdate +++ b/usr/sbin/sdwdate @@ -375,15 +375,15 @@ if __name__ == "__main__": logger = logging.getLogger('sdwdate_log') logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - handler = logging.FileHandler('/var/log/sdwdate-python.log') + handler = logging.FileHandler('/var/log/sdwdate.log') handler.setFormatter(formatter) logger.addHandler(handler) - first_success_dir = '/run/sdwdate/' + first_success_dir = '/run/sdwdate' if not os.path.exists(first_success_dir): os.mkdir(first_success_dir) - first_success_file = first_success_dir + 'first_success' + first_success_file = first_success_dir + '/first_success' while True: sdwdate_ = Sdwdate() From 907cd7cd561c6046de3d80d5b4c5da218c9b4ceb Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 29 Jul 2015 11:01:51 +0000 Subject: [PATCH 038/183] missing files --- usr/lib/sdwdate/restart_fresh | 5 + usr/lib/sdwdate/sclockadj | 230 +++++++++++++++++++++++++ usr/lib/sdwdate/sclockadj_debug_helper | 10 ++ usr/lib/sdwdate/sclockadj_kill_helper | 38 ++++ 4 files changed, 283 insertions(+) create mode 100755 usr/lib/sdwdate/restart_fresh create mode 100755 usr/lib/sdwdate/sclockadj create mode 100755 usr/lib/sdwdate/sclockadj_debug_helper create mode 100755 usr/lib/sdwdate/sclockadj_kill_helper diff --git a/usr/lib/sdwdate/restart_fresh b/usr/lib/sdwdate/restart_fresh new file mode 100755 index 00000000..c61b1795 --- /dev/null +++ b/usr/lib/sdwdate/restart_fresh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +rm --force /var/run/sdwdate/first_success diff --git a/usr/lib/sdwdate/sclockadj b/usr/lib/sdwdate/sclockadj new file mode 100755 index 00000000..e7ef35db --- /dev/null +++ b/usr/lib/sdwdate/sclockadj @@ -0,0 +1,230 @@ +#!/usr/bin/env ruby + +module SneakyClockAdjusterCLI + require 'optparse' + require 'ostruct' + + def self.parse args + options = OpenStruct.new + options.add_or_substract = :add + options.amount = nil + options.move_min = nil + options.move_max = nil + options.wait_min = nil + options.wait_max = nil + options.first_wait = false + options.verbose = true + options.debug = false + options.systohc = false + + opt_parser = OptionParser.new do |opts| + opts.banner = "" + opts.separator "Specific options:" + + opts.on("--add num", Integer, "(Required) Nanoseconds to add") do |v| + options.add_or_substract = :add + options.amount = v + end + opts.on("--subtract num", Integer, "(Required) Nanoseconds to subtract") do |v| + options.add_or_substract = :subtract + options.amount = v + end + opts.on("--wait-min num", Integer, "(Required) Minimum random seconds to wait") do |v| + options.wait_min = v + end + opts.on("--wait-max num", Integer, "(Required) Maximum random seconds to wait") do |v| + options.wait_max = v + end + opts.on("--move-min num", Integer, "(Required) Minimum random nanoseconds to change") do |v| + options.move_min = v + end + opts.on("--move-max num", Integer, "(Required) Maximum random nanoseconds to change") do |v| + options.move_max = v + end + opts.on("--[no-]first-wait", "Wait before first change?") do |v| + options.first_wait = v + end + opts.on("--[no-]verbose", "Run Verbosely?") do |v| + options.verbose = v + end + opts.on("--[no-]debug", "Debug messages. Don't change date.") do |v| + options.debug = v + end + opts.on("--[no-]systohc", "Update hardware clock.") do |v| + options.systohc = v + end + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + + end + + opt_parser.parse! + if options.amount == nil then puts opt_parser; exit 1 end + if options.move_min == nil then puts opt_parser; exit 1 end + if options.move_max == nil then puts opt_parser; exit 1 end + if options.wait_min == nil then puts opt_parser; exit 1 end + if options.wait_max == nil then puts opt_parser; exit 1 end + if options.amount == nil then puts opt_parser; exit 1 end + if options.amount <= 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end + if options.wait_min < 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end + if options.wait_max < options.wait_min then raise OptionParser::InvalidArgument, "Max must > min!" end + if options.move_min < 0 then raise OptionParser::InvalidArgument, "Input must be positive!" end + if options.move_max < options.move_min then raise OptionParser::InvalidArgument, "Max must > min!" end + + return options + end # self.parse +end # module SneakyClockAdjusterCLI + +module SneakyClockAdjuster + require 'openssl' + require 'securerandom' + require 'bigdecimal' + require 'bigdecimal/util' + require 'inline' + + def self.execute add_or_substract, amount, move_min, move_max, wait_min, wait_max, first_wait = false, verbose = true, debug = false, systohc = false + + puts "Running with PID: #{Process.pid}" if verbose + Kernel.trap("TERM") do + puts "Exiting..." + exit 143 + end + Kernel.trap("INT") do + puts "Exiting..." + exit 130 + end + + jumps = make_jumps amount, move_min, move_max + jumps_c = jumps.count + intervals = make_intervals jumps_c, wait_min, wait_max + if not first_wait then intervals[0] = 0 end + if debug + puts "DEBUG: Using these sleep intervals: #{intervals}" + puts "DEBUG: with these time jumps: #{jumps}" + end + + c = Cinline.new + + jumps.each_index do |index| + + if verbose + puts "---" + puts "Iteration: #{index+1} of #{jumps_c}" + end + + wait_ns = intervals[index] + wait_s = wait_ns.to_f / 1000000000 + jump_ns = jumps[index] + jump_s = jump_ns.to_f / 1000000000 + + if wait_ns > 0 then + puts "Waiting #{wait_ns} nanoseconds [#{wait_s} seconds]" if verbose + sleep(wait_s) + end + + if verbose + print "Aproximate system date with nanoseconds: " + puts %x[echo "$(date) | $(date +%N)"] + end + + if add_or_substract == :add then + puts "Adding #{jump_ns} nanoseconds [#{jump_s} seconds]" if verbose + else + puts "Subtracting #{jump_ns} nanoseconds [#{jump_s} seconds]" if verbose + jump_ns = -jump_ns + end + + if not debug + c_return_code = c.getAndSet(jump_ns) + puts "c_return_code: #{c_return_code}" if verbose + if c_return_code != 0 then + warn "ERROR: c_return_code was: #{c_return_code}" + exit c_return_code + end + end + + if systohc then + cmd = "hwclock --systohc" + puts "Set hwclock with: #{cmd}" if verbose + system cmd unless debug + end + + if verbose + print "Aproximate system date with nanoseconds: " + puts %x[echo "$(date) | $(date +%N)"] + end + + end # jumps.each_index + + if verbose + puts "---" + puts "Done! Exiting..." + end + end + + def self.make_intervals count, min, max, intervals = [] + count.times do + intervals << (SecureRandom.random_number max-min+1) + min + end + return intervals + end + + def self.make_jumps amount, min, max, jumps = [] + loop do + if amount < 1 then return jumps end + if amount < min then return jumps << amount end + + if amount - max < 1 then max = amount end + random_jump = (SecureRandom.random_number max-min+1) + min + jumps << random_jump + amount -= random_jump + end + end +end # module SneakyClockAdjuster + +class Cinline + + inline :C do |builder| + builder.include '' + builder.include '' + builder.include '' + builder.c ' + int getAndSet(long long addNsec) { + /* receive time adjustment, negative or positive, in nanoseconds */ + + /* get current time in seconds since epoch + nanoseconds offset */ + struct timespec tps; /* tv_sec; tv_nsec */ + if( clock_gettime(0, &tps) == -1 ) { + perror( "getclock" ); + return EXIT_FAILURE; + } + + /* combine seconds and nanoseconds offset to manipulate */ + long long nanosecondsSinceEpoch = + (long long)(tps.tv_sec) * 1000000000 + /* convert seconds to nanoseconds */ + (long long)(tps.tv_nsec); /* add offset */ + + long long newNanosecondsSinceEpoch = nanosecondsSinceEpoch += addNsec; + + /* separate adjusted nanoseconds since epoch into seconds + nanoseconds offset */ + long newNS = newNanosecondsSinceEpoch % 1000000000; /* pulls out nanoseconds */ + long newS = newNanosecondsSinceEpoch / 1000000000; /* truncates into seconds */ + + /* set struct with new values; set time */ + tps.tv_sec = newS; /* reusing old struct */ + tps.tv_nsec = newNS; + if( clock_settime(0, &tps) == -1 ) { + perror( "setclock" ); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + ' + end + +end + +options = SneakyClockAdjusterCLI.parse(ARGV) +SneakyClockAdjuster.execute options.add_or_substract, options.amount, options.move_min, options.move_max, options.wait_min, options.wait_max, options.first_wait, options.verbose, options.debug diff --git a/usr/lib/sdwdate/sclockadj_debug_helper b/usr/lib/sdwdate/sclockadj_debug_helper new file mode 100755 index 00000000..936d697d --- /dev/null +++ b/usr/lib/sdwdate/sclockadj_debug_helper @@ -0,0 +1,10 @@ +#!/bin/bash + +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +set -x + +find /var/cache/sdwdate -printf "%p %u %g %m\n" +true "\$?: $?" diff --git a/usr/lib/sdwdate/sclockadj_kill_helper b/usr/lib/sdwdate/sclockadj_kill_helper new file mode 100755 index 00000000..f288df19 --- /dev/null +++ b/usr/lib/sdwdate/sclockadj_kill_helper @@ -0,0 +1,38 @@ +#!/bin/bash + +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +pid="$1" + +if [[ "$pid" != *[!0-9]* ]]; then + true "pid '$pid' is strictly numeric." +else + echo "pid '$pid' is NOT strictly numeric!" + exit 1 +fi + +ps_output="$(ps h --format command "$pid")" + +## Example ps_output: +## sudo INLINEDIR=/var/cache/sdwdate/sclockadj /usr/lib/sdwdate/sclockadj --no-verbose --no-debug --no-first-wait --move-min 500000 --move-max 500000 --wait-min 1000000000 --wait-max 1000000000 --add 172807620812 + +read -t 2 first second third _ <<< "$ps_output" + +## Example first: +## sudo + +## Example third: +## /usr/lib/sdwdate/sclockadj + +if [ "$first" = "sudo" ]; then + if [ "$third" = "/usr/lib/sdwdate/sclockadj" ]; then + echo "kill -sigterm $pid" + kill -sigterm "$pid" + exit 0 + fi +fi + +echo "Nothing killed. ps_output: $ps_output" +exit 0 From a7a7889709dbdb7cd512527cf25960197cb96bee Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 29 Jul 2015 19:58:25 +0000 Subject: [PATCH 039/183] debian/sdwdate.postinst, debian/sdwdate.postrm --- debian/sdwdate.postinst | 58 +++++++++++++++++++++++++++++++++++++++++ debian/sdwdate.postrm | 18 +++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 debian/sdwdate.postinst create mode 100644 debian/sdwdate.postrm diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst new file mode 100644 index 00000000..672b244f --- /dev/null +++ b/debian/sdwdate.postinst @@ -0,0 +1,58 @@ +#!/bin/bash + +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +if [ -f /usr/lib/pre.bsh ]; then + source /usr/lib/pre.bsh +fi + +set -e + +true " +##################################################################### +## INFO: BEGIN: $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ${1+"$@"} +##################################################################### +" + +if [ -x /usr/lib/anon-shared-helper-scripts/torsocks-remove-ld-preload ]; then + source /usr/lib/anon-shared-helper-scripts/torsocks-remove-ld-preload +fi + +case "$1" in + configure) + ## Not using --no-create-home, so sdwdate can write into /home/sdwdate. + adduser --quiet --system --group sdwdate || true + + mkdir --parents "/var/cache/sdwdate/sclockadj" + chown --recursive root:root "/var/cache/sdwdate/sclockadj" + chmod --recursive 700 "/var/cache/sdwdate/sclockadj" + + ## Clean up from previous version. + rm -f -r "/var/cache/sdwdate/.ruby_inline" + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "$DPKG_MAINTSCRIPT_NAME called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +true "INFO: debhelper beginning here." + +#DEBHELPER# + +true "INFO: Done with debhelper." + +true " +##################################################################### +## INFO: END : $DPKG_MAINTSCRIPT_PACKAGE $DPKG_MAINTSCRIPT_NAME ${1+"$@"} +##################################################################### +" + +## Explicitly "exit 0", so eventually trapped errors can be ignored. +exit 0 diff --git a/debian/sdwdate.postrm b/debian/sdwdate.postrm new file mode 100644 index 00000000..7b35eddf --- /dev/null +++ b/debian/sdwdate.postrm @@ -0,0 +1,18 @@ +#!/bin/bash + +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +if [ -f /usr/lib/pre.bsh ]; then + source /usr/lib/pre.bsh +fi + +set -e + +if [ "$1" = "purge" ]; then + rm -f "/var/log/sdwdate.log" + rm -r -f "/var/cache/sdwdate" +fi + +#DEBHELPER# From 2142fd9a22cf4e857090d13c617465b205d083f6 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 29 Jul 2015 20:28:59 +0000 Subject: [PATCH 040/183] usr/sbin/sdwdate -> usr/bin/sdwdate --- lib/systemd/system/sdwdate.service | 2 +- usr/{sbin => bin}/sdwdate | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) rename usr/{sbin => bin}/sdwdate (97%) diff --git a/lib/systemd/system/sdwdate.service b/lib/systemd/system/sdwdate.service index 0107e61d..ae3e936f 100644 --- a/lib/systemd/system/sdwdate.service +++ b/lib/systemd/system/sdwdate.service @@ -14,7 +14,7 @@ Wants=tor.service Type=simple User=sdwdate Group=sdwdate -ExecStart=/usr/sbin/sdwdate +ExecStart=/usr/bin/sdwdate SuccessExitStatus=143 KillMode=process TimeoutSec=30 diff --git a/usr/sbin/sdwdate b/usr/bin/sdwdate similarity index 97% rename from usr/sbin/sdwdate rename to usr/bin/sdwdate index dc08ea63..4cee1591 100755 --- a/usr/sbin/sdwdate +++ b/usr/bin/sdwdate @@ -10,14 +10,14 @@ import random from random import randint from subprocess import Popen, call, PIPE -from sdwdate import url_to_unixtime -from sdwdate import config +from sdwdate.url_to_unixtime import url_to_unixtime +from sdwdate.config import read_pools #from error_handler import SdwdateError class Sdwdate(): def __init__(self): - self.pool_one, self.pool_two, self.pool_three = config.read_pools() + self.pool_one, self.pool_two, self.pool_three = read_pools() self.iteration = 0 @@ -98,7 +98,7 @@ class Sdwdate(): ''' while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 - message = 'Main loop, iteration %s' % (self.iteration) + message = 'Running sdwdate loop, iteration %s' % (self.iteration) print(message) logger.debug(message) @@ -163,7 +163,7 @@ class Sdwdate(): print(message) logger.debug(message) - self.urls, self.returned_values = url_to_unixtime.url_to_unixtime(self.url_random) + self.urls, self.returned_values = url_to_unixtime(self.url_random) if len(self.urls) == 0: ## Most likely, internet connection is down. ## Raise eror, log. @@ -171,7 +171,7 @@ class Sdwdate(): print(message) logger.critical(message) sys.exit() - message = 'returned urls "%s"' % (self.urls) + message = 'Returned urls "%s"' % (self.urls) print(message) logger.debug(message) else: @@ -238,7 +238,7 @@ class Sdwdate(): #message = 'Pool diff %s' % self.pools_diff #print(message) #logger.debug(message) - message = 'bad urls %s' % (self.invalid_urls) + message = 'Bad urls %s' % (self.invalid_urls) print(message) logger.debug(message) @@ -345,7 +345,7 @@ class Sdwdate(): call(cmd, shell=True) def set_time_using_date(self): - message = 'setting time using date.' + message = 'Setting time using date.' print(message) logger.debug(message) @@ -403,6 +403,6 @@ if __name__ == "__main__": print(message + '\n\n') logger.debug(message) - time.sleep(20) + time.sleep(200) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() From 9fd86a920950ea3f5bd5903710893406c658ba7f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 29 Jul 2015 20:36:43 +0000 Subject: [PATCH 041/183] config reads multi-line entries, append random url to pool --- etc/sdwdate-python.d/30_sdwdate_default | 117 +++++++++--------- .../python2.7/dist-packages/sdwdate/config.py | 72 ++++++++--- 2 files changed, 116 insertions(+), 73 deletions(-) diff --git a/etc/sdwdate-python.d/30_sdwdate_default b/etc/sdwdate-python.d/30_sdwdate_default index a12575ee..cbe3e9a6 100644 --- a/etc/sdwdate-python.d/30_sdwdate_default +++ b/etc/sdwdate-python.d/30_sdwdate_default @@ -236,20 +236,20 @@ SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" ## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" ## individual websites SDWDATE_POOL_ONE=( - "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" - "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" - "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" - "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" - "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" - "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" - "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" - "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" - "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" - "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" - "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" - "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" - "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" - "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" + "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" + "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" + "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" + "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" + "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" + "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" + "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" + "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" + "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" + "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" + "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" + "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" + "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" + "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" ) ## pool two. @@ -264,29 +264,31 @@ SDWDATE_POOL_ONE=( ## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia ## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" ## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" -## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" +## "3qnry3qqjvc2u3c4.onion"#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" SDWDATE_POOL_TWO=( - "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" - "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" - "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" - "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" - "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" - "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" - "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" - "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" - "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" - "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" - "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" - "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" - "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" - "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" + " + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + " + "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" + "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" + "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" + "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" + "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" + "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" + "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" + "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" + "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" + "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" + "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" + "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" + "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" + "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" ) ## pool three. @@ -299,26 +301,27 @@ SDWDATE_POOL_TWO=( ## removed because no http: ## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" SDWDATE_POOL_THREE=( - "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" - "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" - "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" - "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" - "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" - "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" - "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - "nzh3fv6jc6jskki3.onion#riseup.net: nzh3fv6jc6jskki3.onion (port 443) - "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) - "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) - "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) - "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) - "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) - "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) - "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) - "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) - "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) - "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) - "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) - "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" - "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" + "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" + " + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) + " + "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" + "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" ) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index c86616fe..1f3b36b1 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -1,9 +1,45 @@ #!/usr/bin/env python -import os, sys +import os import glob -import time import re +import random + +def sort_pool(pool): + ## Check number of multi-line pool. + double_quotes = 0 + for i in range(len(pool)): + if pool[i] == ('"'): + double_quotes = double_quotes + 1 + number_of_pool_multi = double_quotes / 2 + + ## Dynamically create multi-line lists. + multi_list = [[] for i in range(number_of_pool_multi)] + + ## Sort... + multi_line = False + multi_index = 0 + pool_single = [] + for i in range(len(pool)): + if multi_line and pool[i] == '"': + multi_line = False + multi_index = multi_index + 1 + elif multi_line: + url = re.search(r'"(.*)#', pool[i]) + multi_list[multi_index].append(url.group(1)) + elif pool[i] == '"': + multi_line = True + elif pool[i].startswith('"'): + url = re.search(r'"(.*)#', pool[i]) + pool_single.append(url.group(1)) + + ## Pick a random url in each multi-line pool, + ## append it to single url pool. + for i in range(number_of_pool_multi): + single_url = multi_list[i][random.sample(range(len(multi_list[i])), 1)[0]] + pool_single.append(single_url) + + return(pool_single) def read_pools(): SDWDATE_POOL_ONE = False @@ -14,6 +50,10 @@ def read_pools(): pool_two = [] pool_three = [] + pool_one_sorted = [] + pool_two_sorted = [] + pool_three_sorted = [] + if os.path.exists('/etc/sdwdate-python.d/'): files = sorted(glob.glob('/etc/sdwdate-python.d/*')) @@ -37,17 +77,14 @@ def read_pools(): SDWDATE_POOL_TWO = False SDWDATE_POOL_THREE = True - elif SDWDATE_POOL_ONE and line.startswith('"'): - url = re.search(r'"(.*)#', line) - pool_one.append(url.group(1)) + elif SDWDATE_POOL_ONE and not line.startswith('##'): + pool_one.append(line) - elif SDWDATE_POOL_TWO and line.startswith('"'): - url = re.search(r'"(.*)#', line) - pool_two.append(url.group(1)) + elif SDWDATE_POOL_TWO and not line.startswith('##'): + pool_two.append(line) - elif SDWDATE_POOL_THREE and line.startswith('"'): - url = re.search(r'"(.*)#', line) - pool_three.append(url.group(1)) + elif SDWDATE_POOL_THREE and not line.startswith('##'): + pool_three.append(line) if not conf_found: print('No valid file found in user configuration folder "/etc/sdwdate.d".') @@ -58,12 +95,15 @@ def read_pools(): else: print('User configuration folder "/etc/sdwdate.d" does not exist.') - ## Remove duplicates. - pool_one = list(set(pool_one)) - pool_two = list(set(pool_two)) - pool_three = list(set(pool_three)) + pool_one_sorted = sort_pool(pool_one) + pool_two_sorted = sort_pool(pool_two) + pool_three_sorted = sort_pool(pool_three) + + print pool_one_sorted + print pool_two_sorted + print pool_three_sorted - return (pool_one, pool_two, pool_three) + return(pool_one_sorted, pool_two_sorted, pool_three_sorted) if __name__ == "__main__": read_pools() From daaa521490c9a1fbe73bd3a22d4eeaa905b472b5 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 30 Jul 2015 18:19:35 +0000 Subject: [PATCH 042/183] etc/sdwdate-python.d/30_sdwdate_default --- etc/sdwdate-python.d/30_sdwdate_default | 98 ++++++++++++------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/etc/sdwdate-python.d/30_sdwdate_default b/etc/sdwdate-python.d/30_sdwdate_default index cbe3e9a6..84aaff40 100644 --- a/etc/sdwdate-python.d/30_sdwdate_default +++ b/etc/sdwdate-python.d/30_sdwdate_default @@ -236,20 +236,20 @@ SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" ## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" ## individual websites SDWDATE_POOL_ONE=( - "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" - "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" - "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" - "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" - "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" - "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" - "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" - "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" - "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" - "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" - "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" - "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" - "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" - "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" + "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" + "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" + "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" + "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" + "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" + "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" + "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" + "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" + "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" + "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" + "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" + "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" + "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" + "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" ) ## pool two. @@ -264,31 +264,31 @@ SDWDATE_POOL_ONE=( ## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia ## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" ## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" -## "3qnry3qqjvc2u3c4.onion"#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" +## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" SDWDATE_POOL_TWO=( - " - "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" - " - "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" - "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" - "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" - "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" - "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" - "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" - "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" - "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" - "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" - "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" - "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" - "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" - "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" - "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" + " + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html + " + "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" + "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" + "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" + "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" + "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" + "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" + "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" + "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" + "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" + "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" + "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" + "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" + "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" + "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" ) ## pool three. @@ -301,14 +301,14 @@ SDWDATE_POOL_TWO=( ## removed because no http: ## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" SDWDATE_POOL_THREE=( - "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" - "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" - "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" - "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" - "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" - "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" - "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - " + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" + "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" + " "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) @@ -320,8 +320,8 @@ SDWDATE_POOL_THREE=( "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) - " - "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" - "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" + " + "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" + "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" ) From a5c6801ee5229b8a92a2fbb8f7fa3dab07fe7907 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 30 Jul 2015 19:26:04 +0000 Subject: [PATCH 043/183] etc/sdwdate-python.d/30_sdwdate_default square brackets, missing quotes --- etc/sdwdate-python.d/30_sdwdate_default | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/etc/sdwdate-python.d/30_sdwdate_default b/etc/sdwdate-python.d/30_sdwdate_default index 84aaff40..e1b413cb 100644 --- a/etc/sdwdate-python.d/30_sdwdate_default +++ b/etc/sdwdate-python.d/30_sdwdate_default @@ -266,13 +266,13 @@ SDWDATE_POOL_ONE=( ## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" ## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" SDWDATE_POOL_TWO=( - " - "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - " + [ + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + ] "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" @@ -308,19 +308,19 @@ SDWDATE_POOL_THREE=( "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - " - "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) - "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) - "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) - "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) - "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) - "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) - "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) - "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) - "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) - "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) - "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) - " + [ + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443)" + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443)" + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993)" + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443)" + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443)" + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587)" + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion)" + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995)" + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587)" + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443)" + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443)" + ] "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" From 5067b606e6207afe333813b4f0f79856d368729f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 30 Jul 2015 19:37:09 +0000 Subject: [PATCH 044/183] config.py, changes in /etc/sdwdate-python.d/30_sdwdate_default --- usr/lib/python2.7/dist-packages/sdwdate/config.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 1f3b36b1..54ff0e21 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -7,11 +7,10 @@ def sort_pool(pool): ## Check number of multi-line pool. - double_quotes = 0 + number_of_pool_multi = 0 for i in range(len(pool)): - if pool[i] == ('"'): - double_quotes = double_quotes + 1 - number_of_pool_multi = double_quotes / 2 + if pool[i] == ('['): + number_of_pool_multi = number_of_pool_multi + 1 ## Dynamically create multi-line lists. multi_list = [[] for i in range(number_of_pool_multi)] @@ -21,13 +20,13 @@ def sort_pool(pool): multi_index = 0 pool_single = [] for i in range(len(pool)): - if multi_line and pool[i] == '"': + if multi_line and pool[i] == ']': multi_line = False multi_index = multi_index + 1 elif multi_line: url = re.search(r'"(.*)#', pool[i]) multi_list[multi_index].append(url.group(1)) - elif pool[i] == '"': + elif pool[i] == '[': multi_line = True elif pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) From 9bf3499b273222cdb9a099a856a7fdf5acb4c550 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 30 Jul 2015 21:20:47 +0000 Subject: [PATCH 045/183] bad input check / resilience --- .../python2.7/dist-packages/sdwdate/config.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 54ff0e21..00f69151 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -4,6 +4,14 @@ import glob import re import random +import logging + +logger = logging.getLogger('sdwdate_log') +logger.setLevel(logging.DEBUG) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +handler = logging.FileHandler('/var/log/sdwdate.log') +handler.setFormatter(formatter) +logger.addHandler(handler) def sort_pool(pool): ## Check number of multi-line pool. @@ -23,14 +31,26 @@ def sort_pool(pool): if multi_line and pool[i] == ']': multi_line = False multi_index = multi_index + 1 - elif multi_line: + + elif multi_line and pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) - multi_list[multi_index].append(url.group(1)) + if url != None: + multi_list[multi_index].append(url.group(1)) + ## Most likely missing '#' at end of url. + ## We have to catch it because Python raise an exception and stops. + else: + ## Log twice the error. To be checked. + logger.warning('Malformed line in config file: %s' % (pool[i])) + elif pool[i] == '[': multi_line = True + elif pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) - pool_single.append(url.group(1)) + if url != None: + pool_single.append(url.group(1)) + else: + logger.warning('Malformed line in config file: %s' % (pool[i])) ## Pick a random url in each multi-line pool, ## append it to single url pool. @@ -103,6 +123,3 @@ def read_pools(): print pool_three_sorted return(pool_one_sorted, pool_two_sorted, pool_three_sorted) - -if __name__ == "__main__": - read_pools() From b7a7ef5b8ae3b4068e17abe8556c605867c6f2e2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 31 Jul 2015 18:23:34 +0000 Subject: [PATCH 046/183] remove logging from config.py --- .../python2.7/dist-packages/sdwdate/config.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 00f69151..134eb928 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -4,14 +4,6 @@ import glob import re import random -import logging - -logger = logging.getLogger('sdwdate_log') -logger.setLevel(logging.DEBUG) -formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -handler = logging.FileHandler('/var/log/sdwdate.log') -handler.setFormatter(formatter) -logger.addHandler(handler) def sort_pool(pool): ## Check number of multi-line pool. @@ -36,11 +28,6 @@ def sort_pool(pool): url = re.search(r'"(.*)#', pool[i]) if url != None: multi_list[multi_index].append(url.group(1)) - ## Most likely missing '#' at end of url. - ## We have to catch it because Python raise an exception and stops. - else: - ## Log twice the error. To be checked. - logger.warning('Malformed line in config file: %s' % (pool[i])) elif pool[i] == '[': multi_line = True @@ -49,8 +36,6 @@ def sort_pool(pool): url = re.search(r'"(.*)#', pool[i]) if url != None: pool_single.append(url.group(1)) - else: - logger.warning('Malformed line in config file: %s' % (pool[i])) ## Pick a random url in each multi-line pool, ## append it to single url pool. @@ -118,6 +103,10 @@ def read_pools(): pool_two_sorted = sort_pool(pool_two) pool_three_sorted = sort_pool(pool_three) + pool_one_sorted = list(set(pool_one_sorted)) + pool_two_sorted = list(set(pool_two_sorted)) + pool_three_sorted = list(set(pool_three_sorted)) + print pool_one_sorted print pool_two_sorted print pool_three_sorted From bce15048509c8f40cb280fcb2c69fca18eb9b917 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 31 Jul 2015 21:40:50 +0000 Subject: [PATCH 047/183] error handling, logging only. cleanup. --- usr/bin/sdwdate | 86 +++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 4cee1591..c05ec450 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -1,6 +1,5 @@ #!/usr/bin/env python -import sys import logging import signal import gevent @@ -12,7 +11,6 @@ from subprocess import Popen, call, PIPE from sdwdate.url_to_unixtime import url_to_unixtime from sdwdate.config import read_pools -#from error_handler import SdwdateError class Sdwdate(): @@ -67,10 +65,7 @@ class Sdwdate(): if (pools[0] == 'Connection closed unexpectedly' and pools[1] == 'Connection closed unexpectedly' and pools[2] == 'Connection closed unexpectedly'): - ## Raise error, log, user warning. - logger.critical('General Proxy Error') - sys.exit(1) - + return True return False def check_remote(self, remote, value): @@ -96,6 +91,7 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' + print len(self.pool_one) while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 message = 'Running sdwdate loop, iteration %s' % (self.iteration) @@ -106,19 +102,23 @@ class Sdwdate(): self.urls[:] = [] self.url_random[:] = [] + pool_invalid = False + message = '' + if not self.pool_one_done: while True: url_index = [] url_index = random.sample(range(self.range_pool_one), 1) index = url_index[0] + print self.already_picked_index_pool_one if len(self.already_picked_index_pool_one) == len(self.pool_one): - self.already_picked_index_pool_one = [] - self.url_random_pool_one = [] + pool_invalid = True + message = ' Time will not be set: no valid time returned from pool one..' + break if url_index not in self.already_picked_index_pool_one: self.already_picked_index_pool_one.append(url_index) - #print 'pool 1 added %s' % (self.pool_one[url_index[0]]) self.url_random_pool_one.append(self.pool_one[url_index[0]]) self.url_random.append(self.pool_one[url_index[0]]) break @@ -129,13 +129,13 @@ class Sdwdate(): url_index = random.sample(range(self.range_pool_two), 1) index = url_index[0] - if len(self.url_random_pool_two) == len(self.pool_two): - self.already_picked_index_pool_two = [] - self.url_random_pool_two = [] + if len(self.already_picked_index_pool_two) == len(self.pool_two): + pool_invalid = True + message = ' Time will not be set: no valid time returned from pool two.' + break if url_index not in self.already_picked_index_pool_two: self.already_picked_index_pool_two.append(url_index) - #print 'pool 2 added %s' % (self.pool_two[url_index[0]]) self.url_random_pool_two.append(self.pool_two[url_index[0]]) self.url_random.append(self.pool_two[url_index[0]]) break @@ -146,17 +146,21 @@ class Sdwdate(): url_index = random.sample(range(self.range_pool_three), 1) index = url_index[0] - if len(self.url_random_pool_three) == len(self.pool_three): - self.already_picked_index_pool_three = [] - self.url_random_pool_three = [] + if len(self.already_picked_index_pool_three) == len(self.pool_three): + pool_invalid = True + message = 'Time will not be set: no valid time returned from pool three' + break if url_index not in self.already_picked_index_pool_three: self.already_picked_index_pool_three.append(url_index) - #print 'pool 3 added %s' % (self.pool_three[url_index[0]]) self.url_random_pool_three.append(self.pool_three[url_index[0]]) self.url_random.append(self.pool_three[url_index[0]]) break + if pool_invalid: + print(message) + return(message) + ## Fetch remotes. if len(self.url_random) > 0: message = 'Random urls %s' % (self.url_random) @@ -169,14 +173,14 @@ class Sdwdate(): ## Raise eror, log. message = ('No values returned from url_to_unixtime. Internet connection might be down.') print(message) - logger.critical(message) - sys.exit() + return(message) message = 'Returned urls "%s"' % (self.urls) print(message) logger.debug(message) else: - ## Add code here. - sys.exit(1) + message = 'Something is wrong. sdwdate loop could not build a list or urls.' + print(message) + return(message) if not self.general_proxy_error(self.returned_values): for i in range(len(self.urls)): @@ -187,9 +191,12 @@ class Sdwdate(): else: self.invalid_urls.append(self.urls[i]) self.url_errors.append(self.returned_values[i]) + else: + message = 'General Proxy Error. Is Tor running?' + print(message) + return(message) old_unixtime = (time.time()) - #print 'old_unixtime %s' % old_unixtime if not self.pool_one_done: for i in range(len(self.url_random_pool_one)): @@ -204,7 +211,6 @@ class Sdwdate(): message = 'Pool one: last_url %s, web_time %s' % (valid_url, web_time) print(message) logger.debug(message) - #print 'pool_one_done %s' % (self.pool_one_done) if not self.pool_two_done: for i in range(len(self.url_random_pool_two)): @@ -217,7 +223,6 @@ class Sdwdate(): message = 'Pool two: last_url %s, web_time %s' % (valid_url, web_time) print(message) logger.debug(message) - #print 'pool_two_done %s' % (self.pool_two_done) if not self.pool_three_done: for i in range(len(self.url_random_pool_three)): @@ -230,14 +235,11 @@ class Sdwdate(): message = 'Pool three: last_url %s, web_time %s' % (valid_url, web_time) print(message) logger.debug(message) - #print 'pool_three_done %s' % (self.pool_three_done) message = 'Valid urls %s' % (self.valid_urls) print(message) logger.debug(message) - #message = 'Pool diff %s' % self.pools_diff - #print(message) - #logger.debug(message) + message = 'Bad urls %s' % (self.invalid_urls) print(message) logger.debug(message) @@ -246,6 +248,8 @@ class Sdwdate(): print(message) logger.debug(message) + return('Success') + def build_median(self): ''' Get the median (not average) from the list of values. @@ -387,22 +391,26 @@ if __name__ == "__main__": while True: sdwdate_ = Sdwdate() - sdwdate_.sdwdate_loop() - sdwdate_.build_median() + sdwdate_loop_result = sdwdate_.sdwdate_loop() + if sdwdate_loop_result == 'Success': + sdwdate_.build_median() + + if sdwdate_.set_new_time(): + sdwdate_.add_subtract_nanoseconds() + if os.path.exists(first_success_file): + sdwdate_.run_sclockadj() + else: + sdwdate_.set_time_using_date() + f = open(first_success_file, 'w') + f.close() - if sdwdate_.set_new_time(): - sdwdate_.add_subtract_nanoseconds() - if os.path.exists(first_success_file): - sdwdate_.run_sclockadj() - else: - sdwdate_.set_time_using_date() - f = open(first_success_file, 'w') - f.close() + else: + logger.warning(sdwdate_loop_result) message = 'Sleeping...' print(message + '\n\n') logger.debug(message) - time.sleep(200) + time.sleep(1800) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() From 986f87c1601c944da7c2902e6d8529104ca9e1c5 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 3 Aug 2015 21:05:33 +0000 Subject: [PATCH 048/183] test configuration files --- .../test_d_files/40_pool_one_invalid | 327 ++++++++++++++++++ .../test_d_files/50_pool_two_invalid | 327 ++++++++++++++++++ 2 files changed, 654 insertions(+) create mode 100644 etc/sdwdate-python.d/test_d_files/40_pool_one_invalid create mode 100644 etc/sdwdate-python.d/test_d_files/50_pool_two_invalid diff --git a/etc/sdwdate-python.d/test_d_files/40_pool_one_invalid b/etc/sdwdate-python.d/test_d_files/40_pool_one_invalid new file mode 100644 index 00000000..4ee676e0 --- /dev/null +++ b/etc/sdwdate-python.d/test_d_files/40_pool_one_invalid @@ -0,0 +1,327 @@ +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +## Please use "/etc/sdwdate.d/50_sdwdate_user" for your custom +## configuration, which will override the defaults found here. +## When sdwdate is updated, this file may be overwritten. + +## Bash Fragment. + +## Enable/disable debugging. +## 1 enabled. +## 2 disabled. +DEBUG=0 + +## Run as the following user name. +## Not implemented. Has no effect. +USER="" + +## Do or do not actually change the date/time after successfully fetching it. +## 0 set date. +## 1 do not set date. +DONT_SET_DATE=0 + +## do not move the time forward +## 0 disabled +## 1 enabled +NO_MOVE_FORWARD=0 + +## do not move the time backwards +## 0 disabled +## 1 enabled +NO_MOVE_BACKWARDS=0 + +## update hardware clock +## 0 disabled +## 1 enabled +SYSTOHC=0 + +## Log file. +LOG_FILE=/var/log/sdwdate.log + +## Done file. Will be created after run no matter if failure or success. +DONE_FILE=/var/run/sdwdate/done + +## Success file. Will only be created after a success. +SUCCESS_FILE=/var/run/sdwdate/success + +## First success file. Will be created after the first success. +FIRST_SUCCESS_FILE=/var/run/sdwdate/first_success + +## How many members per pool are allowed to fail. +## If too many members are not reachable, time will not be adjusted. +ALLOWED_PER_POOL_FAILURE_RATIO=0.34 + +## Temporary directory for file downloads. +## When not set, default to: TEMP_DIR="$(mktemp --directory)" +#TEMP_DIR="" + +## Cache dir. Must not include spaces. +SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" + +## proxy IP +PROXY_IP="127.0.0.1" + +## proxy port +PROXY_PORT="9050" + +## How often sdwdate should run in minutes. +## 0 disables it and sdwdate exits after run. +INTERVAL="180" + +## How many minutes should be waited before running sdwdate again. +## Only has an effect when RANDOMIZE is set to 1 as well. +MIN_INTERVAL="60" + +## Randomize the interval above. +## Minimum 60 minutes. +## Maximum $INTERVAL minutes. +## 0 disabled. +## 1 enabled. +RANDOMIZE="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## starting up. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 0 +SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## re-starting up when sdwdate succeeded at least once after boot. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## in daemon mode. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" + +## sclockadj verbose logging or not +## --no-verbose +## --verbose +## defaults to: --no-verbose +SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" + +## sclockadj change date or not +## --no-debug +## --debug +## defaults to: --no-debug (change date) +SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" + +## If sclockadj should wait before its first iteration. +## --no-first-wait +## --first-wait +## default to: --no-first-wait +SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" + +## Move clock minimum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MIN="500000" + +## Move clock maximum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MAX="500000" + +## Wait nanoseconds minimum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" + +## Wait nanoseconds maximum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" + +## This command will be `eval`uated before DISPATCH_PREREQUISITE and before running url to unixtime tool. +## sdwdate provides the $SDW_MODE variable, which is either set to +## - startup (when the sdwdate process is started) +## - daemon (when the sdwdate process finished one loop and will continue) +## When set to "", it will be skipped. +DISPATCH_PRE="" + +## Prerequisite before trying to connect to servers. +## This is supposed to be a command to be `eval`uated and to exit with code +## - 0, if sdwdate should continue. +## - 1, if sdwdate should terminate itself due to an expected error. +## - 2, if sdwdate should wait 10 seconds and then run the command again. +## - Anything else, if sdwdate should terminate itself due to an unexpected error. +## It may be useful to check if the network is already reachable. +## When set to "", it will be skipped. +DISPATCH_PREREQUISITE="" + +## This command will be `eval`uated when an unexpected error (bug) in sdwdate has been caught. +## sdwdate will provide the $error_message and $DONE_FILE variable. +## Remember to escape variables either using \$ or '$variable'. +## When set to "", it will be skipped. +DISPATCH_POST_ERROR="" + +## Create $DONE_FILE on error. +## 1 enabled. +## 0 disabled. +SDW_TOUCH_DONE_FILE_ON_ERROR="1" + +## Exit 1 on error. This will stop the daemon from running. +## 1 enabled. +## 0 disabled. +SDW_EXIT_ON_ERROR="1" + +## echo remote unix time even when using --quiet +## true - enabled. +## false - disabled. +ECHO_UNIX_TIME="false" + +## This command will be `eval`uated when sdwdate succeeded. +## When set to "", it will be skipped. +DISPATCH_POST_SUCCESS="" + +## This command will be `eval`uated when sdwdate failed. +## When set to "", it will be skipped. +DISPATCH_POST_FAILURE="" + +## This command will be `eval`uated before trying to connect to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before after connecting to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before trying to connect to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before after connecting to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before trying to connect to pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="" + +## This command will be `eval`uated before trying after connecting to the pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" + +## pool syntax +## "url.onion[:port]#comment" +## " +## url.onion[:port]#comment +## [url.onion[:port]#comment] +## [url.onion[:port]#comment] +## [...] +## " +## "url.onion[:port]#comment" +## ... + +## pool one. +## SecureDrop List +## info: +## Last updated Thu Oct 23 16:15:00 PDT 2014 +## Organization Landing Page Tor Hidden Service Address +## in use: +## https://freedom.press/securedrop/directory +## https://freedom.press/sites/default/files/securedrop_list.txt +## https://freedom.press/sites/default/files/securedrop_list.txt.asc +## https://freedom.press/sites/default/files/securedrop.asc +## removed because down: +## "bczjr6ciiblco5ti.onion#Forbes https://safesource.forbes.com bczjr6ciiblco5ti.onion" +## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" +## individual websites +SDWDATE_POOL_ONE=( + "atsxnd3ykn32ywv6.onion#d BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" + "anig4bc5rlwyj4mz.onion#z ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" + "atjkwwcq5osuo6uq.onion#v Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" + "a3y6fjyhs3phzfjj.onion#3 The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" + "a6xjgkgwj47us5ca.onion#y The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" +## "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" +## "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" +## "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" +## "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" +## "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" +## "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" +## "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" +## "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" +## "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" +) + +## pool two. +## Hosted by Thomas White List +## +## GlobalLeaks List +## info: +## https://en.wikipedia.org/wiki/GlobaLeaks#Implementations +## http://www.webcitation.org/6WBrtPlrq +## Name of organization Implementation date Category Tor Url Tor2web Url Country +## removed because down: +## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia +## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" +## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" +## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" +SDWDATE_POOL_TWO=( + [ + "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" + ] + "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" + "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" + "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" + "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" + "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "2dermafialks7aai.onionMafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" + "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" + "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" + "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" + "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" + "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" + "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" + "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" + "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" + "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" +) + +## pool three. +## info: +## individual websites +## riseup.net List +## https://help.riseup.net/en/tor#riseups-tor-hidden-services +## removed because down: +## "suw74isz7wqzpmgu.onion:80#https://www.wikileaks.org/wiki/WikiLeaks:Tor" +## removed because no http: +## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" +SDWDATE_POOL_THREE=( + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" + "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" + [ + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443)" + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443)" + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993)" + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443)" + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443)" + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587)" + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion)" + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995)" + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587)" + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443)" + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443)" + ] + "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" + "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" + "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" +) diff --git a/etc/sdwdate-python.d/test_d_files/50_pool_two_invalid b/etc/sdwdate-python.d/test_d_files/50_pool_two_invalid new file mode 100644 index 00000000..5a1fd4dc --- /dev/null +++ b/etc/sdwdate-python.d/test_d_files/50_pool_two_invalid @@ -0,0 +1,327 @@ +## This file is part of Whonix. +## Copyright (C) 2012 - 2014 Patrick Schleizer +## See the file COPYING for copying conditions. + +## Please use "/etc/sdwdate.d/50_sdwdate_user" for your custom +## configuration, which will override the defaults found here. +## When sdwdate is updated, this file may be overwritten. + +## Bash Fragment. + +## Enable/disable debugging. +## 1 enabled. +## 2 disabled. +DEBUG=0 + +## Run as the following user name. +## Not implemented. Has no effect. +USER="" + +## Do or do not actually change the date/time after successfully fetching it. +## 0 set date. +## 1 do not set date. +DONT_SET_DATE=0 + +## do not move the time forward +## 0 disabled +## 1 enabled +NO_MOVE_FORWARD=0 + +## do not move the time backwards +## 0 disabled +## 1 enabled +NO_MOVE_BACKWARDS=0 + +## update hardware clock +## 0 disabled +## 1 enabled +SYSTOHC=0 + +## Log file. +LOG_FILE=/var/log/sdwdate.log + +## Done file. Will be created after run no matter if failure or success. +DONE_FILE=/var/run/sdwdate/done + +## Success file. Will only be created after a success. +SUCCESS_FILE=/var/run/sdwdate/success + +## First success file. Will be created after the first success. +FIRST_SUCCESS_FILE=/var/run/sdwdate/first_success + +## How many members per pool are allowed to fail. +## If too many members are not reachable, time will not be adjusted. +ALLOWED_PER_POOL_FAILURE_RATIO=0.34 + +## Temporary directory for file downloads. +## When not set, default to: TEMP_DIR="$(mktemp --directory)" +#TEMP_DIR="" + +## Cache dir. Must not include spaces. +SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" + +## proxy IP +PROXY_IP="127.0.0.1" + +## proxy port +PROXY_PORT="9050" + +## How often sdwdate should run in minutes. +## 0 disables it and sdwdate exits after run. +INTERVAL="180" + +## How many minutes should be waited before running sdwdate again. +## Only has an effect when RANDOMIZE is set to 1 as well. +MIN_INTERVAL="60" + +## Randomize the interval above. +## Minimum 60 minutes. +## Maximum $INTERVAL minutes. +## 0 disabled. +## 1 enabled. +RANDOMIZE="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## starting up. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 0 +SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## re-starting up when sdwdate succeeded at least once after boot. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" + +## Use sclockadj instead of /bin/date (which would produce clock jumps) when +## in daemon mode. +## 0 sclockadj disabled +## 1 sclockadj enabled +## defaults to: 1 +SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" + +## sclockadj verbose logging or not +## --no-verbose +## --verbose +## defaults to: --no-verbose +SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" + +## sclockadj change date or not +## --no-debug +## --debug +## defaults to: --no-debug (change date) +SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" + +## If sclockadj should wait before its first iteration. +## --no-first-wait +## --first-wait +## default to: --no-first-wait +SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" + +## Move clock minimum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MIN="500000" + +## Move clock maximum nanoseconds (except rest). +## defaults to: 500000 +## (500000 ns = 0.5 ms = 0.0005 s) +SDWDATE_SCLOCKADJ_MOVE_MAX="500000" + +## Wait nanoseconds minimum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" + +## Wait nanoseconds maximum before next iteration. +## defaults to: 1000000000 +## (1000000000 ns = 1000 ms = 1 s) +SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" + +## This command will be `eval`uated before DISPATCH_PREREQUISITE and before running url to unixtime tool. +## sdwdate provides the $SDW_MODE variable, which is either set to +## - startup (when the sdwdate process is started) +## - daemon (when the sdwdate process finished one loop and will continue) +## When set to "", it will be skipped. +DISPATCH_PRE="" + +## Prerequisite before trying to connect to servers. +## This is supposed to be a command to be `eval`uated and to exit with code +## - 0, if sdwdate should continue. +## - 1, if sdwdate should terminate itself due to an expected error. +## - 2, if sdwdate should wait 10 seconds and then run the command again. +## - Anything else, if sdwdate should terminate itself due to an unexpected error. +## It may be useful to check if the network is already reachable. +## When set to "", it will be skipped. +DISPATCH_PREREQUISITE="" + +## This command will be `eval`uated when an unexpected error (bug) in sdwdate has been caught. +## sdwdate will provide the $error_message and $DONE_FILE variable. +## Remember to escape variables either using \$ or '$variable'. +## When set to "", it will be skipped. +DISPATCH_POST_ERROR="" + +## Create $DONE_FILE on error. +## 1 enabled. +## 0 disabled. +SDW_TOUCH_DONE_FILE_ON_ERROR="1" + +## Exit 1 on error. This will stop the daemon from running. +## 1 enabled. +## 0 disabled. +SDW_EXIT_ON_ERROR="1" + +## echo remote unix time even when using --quiet +## true - enabled. +## false - disabled. +ECHO_UNIX_TIME="false" + +## This command will be `eval`uated when sdwdate succeeded. +## When set to "", it will be skipped. +DISPATCH_POST_SUCCESS="" + +## This command will be `eval`uated when sdwdate failed. +## When set to "", it will be skipped. +DISPATCH_POST_FAILURE="" + +## This command will be `eval`uated before trying to connect to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before after connecting to the pool one. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="" + +## This command will be `eval`uated before trying to connect to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before after connecting to the pool two. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="" + +## This command will be `eval`uated before trying to connect to pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="" + +## This command will be `eval`uated before trying after connecting to the pool three. +## When set to "", it will be skipped. +SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" + +## pool syntax +## "url.onion[:port]#comment" +## " +## url.onion[:port]#comment +## [url.onion[:port]#comment] +## [url.onion[:port]#comment] +## [...] +## " +## "url.onion[:port]#comment" +## ... + +## pool one. +## SecureDrop List +## info: +## Last updated Thu Oct 23 16:15:00 PDT 2014 +## Organization Landing Page Tor Hidden Service Address +## in use: +## https://freedom.press/securedrop/directory +## https://freedom.press/sites/default/files/securedrop_list.txt +## https://freedom.press/sites/default/files/securedrop_list.txt.asc +## https://freedom.press/sites/default/files/securedrop.asc +## removed because down: +## "bczjr6ciiblco5ti.onion#Forbes https://safesource.forbes.com bczjr6ciiblco5ti.onion" +## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" +## individual websites +SDWDATE_POOL_ONE=( + "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" + "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" + "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" + "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" + "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" + "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" + "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" + "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" + "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" + "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" + "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" + "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" + "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" + "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" +) + +## pool two. +## Hosted by Thomas White List +## +## GlobalLeaks List +## info: +## https://en.wikipedia.org/wiki/GlobaLeaks#Implementations +## http://www.webcitation.org/6WBrtPlrq +## Name of organization Implementation date Category Tor Url Tor2web Url Country +## removed because down: +## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia +## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" +## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" +## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" +SDWDATE_POOL_TWO=( +## [ +## "atlas777hhh7mcs7.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" +## "compass6vpxj32p3.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" +## "globe223ezvh6bps.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" +## "bbbbbb6qtmqg65g6.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" +## "pppppptkftqqnfsq.onion#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html" +## ] +## "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" +## "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" + "an6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" + "bcabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" + "ar4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" + "adermafialks7aai.onionMafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" + "ami7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" +## "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" +## "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" +## "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" +## "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" +## "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" +## "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" +## "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" +## "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" +## "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" +) + +## pool three. +## info: +## individual websites +## riseup.net List +## https://help.riseup.net/en/tor#riseups-tor-hidden-services +## removed because down: +## "suw74isz7wqzpmgu.onion:80#https://www.wikileaks.org/wiki/WikiLeaks:Tor" +## removed because no http: +## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" +SDWDATE_POOL_THREE=( + "3g2upl4pq6kufc4m.onion#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" + "dju2peblv7upfz3q.onion#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" + "msydqstlz2kzerdg.onion#https://ahmia.fi/address/msydqstlz2kzerdg" + "uj3wazyk5u4hnvtk.onion#https://thepiratebay.se/blog/238" + "bitmailendavkbec.onion#https://bitmessage.org/forum/index.php?topic=1556.0" + "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" + "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" + [ + "nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443)" + "cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443)" + "zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993)" + "yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443)" + "xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443)" + "zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587)" + "5jp7xtmox6jyoqd5.onion#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion)" + "zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995)" + "zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587)" + "j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443)" + "7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443)" + ] + "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" + "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" + "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" +) From 2e8bf67e3d5d48db13f259f5703e3f02a3097fd7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 3 Aug 2015 21:31:38 +0000 Subject: [PATCH 049/183] move test .f files to /usr/share --- .../share}/test_d_files/40_pool_one_invalid | 0 .../share}/test_d_files/50_pool_two_invalid | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {etc/sdwdate-python.d => usr/share}/test_d_files/40_pool_one_invalid (100%) rename {etc/sdwdate-python.d => usr/share}/test_d_files/50_pool_two_invalid (100%) diff --git a/etc/sdwdate-python.d/test_d_files/40_pool_one_invalid b/usr/share/test_d_files/40_pool_one_invalid similarity index 100% rename from etc/sdwdate-python.d/test_d_files/40_pool_one_invalid rename to usr/share/test_d_files/40_pool_one_invalid diff --git a/etc/sdwdate-python.d/test_d_files/50_pool_two_invalid b/usr/share/test_d_files/50_pool_two_invalid similarity index 100% rename from etc/sdwdate-python.d/test_d_files/50_pool_two_invalid rename to usr/share/test_d_files/50_pool_two_invalid From 82e7ea2bd5713811cacaadc7bc572d9628e8526c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 7 Aug 2015 20:49:02 +0000 Subject: [PATCH 050/183] pickle -> wrting to status file --- usr/bin/sdwdate | 84 +++++++++++------- usr/lib/tmpfiles.d/sdwdate.conf | 2 + .../anon-icon-pack/212px-Timeblock.svg.png | Bin 0 -> 15843 bytes .../620px-Ambox_outdated.svg.png | Bin 0 -> 120421 bytes .../icons/anon-icon-pack/IconApproved.png | Bin 0 -> 125857 bytes 5 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png create mode 100644 usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png create mode 100644 usr/share/icons/anon-icon-pack/IconApproved.png diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index c05ec450..c7565ad8 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -7,7 +7,8 @@ import os import time import random from random import randint -from subprocess import Popen, call, PIPE +from subprocess import Popen, call, PIPE, check_output +import pickle from sdwdate.url_to_unixtime import url_to_unixtime from sdwdate.config import read_pools @@ -54,6 +55,17 @@ class Sdwdate(): self.sclockadj_pid = 0 + self.last_shell_date = '' + + self.first_success_path = '/var/run/sdwdate/first_success' + self.status_path = '/var/run/sdwdate/status' + + self.success_icon = '/usr/share/icons/anon-icon-pack/IconApproved.png' + self.busy_icon = '/usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png' + self.error_icon = '/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png' + + self.status = {'icon' : '', 'message' : ''} + message = 'Fetching remote times, start %s' % (time.time()) print(message) logger.debug(message) @@ -91,7 +103,8 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' - print len(self.pool_one) + self.write_status(self.busy_icon, 'Fetching remote times...') + while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 message = 'Running sdwdate loop, iteration %s' % (self.iteration) @@ -110,11 +123,10 @@ class Sdwdate(): url_index = [] url_index = random.sample(range(self.range_pool_one), 1) index = url_index[0] - print self.already_picked_index_pool_one if len(self.already_picked_index_pool_one) == len(self.pool_one): pool_invalid = True - message = ' Time will not be set: no valid time returned from pool one..' + message = ' Time is not set: no valid time returned from pool one' break if url_index not in self.already_picked_index_pool_one: @@ -131,7 +143,7 @@ class Sdwdate(): if len(self.already_picked_index_pool_two) == len(self.pool_two): pool_invalid = True - message = ' Time will not be set: no valid time returned from pool two.' + message = ' Time is not set: no valid time returned from pool two' break if url_index not in self.already_picked_index_pool_two: @@ -148,7 +160,7 @@ class Sdwdate(): if len(self.already_picked_index_pool_three) == len(self.pool_three): pool_invalid = True - message = 'Time will not be set: no valid time returned from pool three' + message = 'Time is not set: no valid time returned from pool three' break if url_index not in self.already_picked_index_pool_three: @@ -159,28 +171,30 @@ class Sdwdate(): if pool_invalid: print(message) - return(message) + return self.error_icon, message ## Fetch remotes. if len(self.url_random) > 0: - message = 'Random urls %s' % (self.url_random) + message = 'Requested urls %s' % (self.url_random) print(message) logger.debug(message) self.urls, self.returned_values = url_to_unixtime(self.url_random) + if len(self.urls) == 0: ## Most likely, internet connection is down. - ## Raise eror, log. message = ('No values returned from url_to_unixtime. Internet connection might be down.') print(message) - return(message) + return self.error_icon, message + message = 'Returned urls "%s"' % (self.urls) print(message) logger.debug(message) + else: message = 'Something is wrong. sdwdate loop could not build a list or urls.' print(message) - return(message) + return self.status_color[2], message + '\nPlease report this bug' if not self.general_proxy_error(self.returned_values): for i in range(len(self.urls)): @@ -194,7 +208,7 @@ class Sdwdate(): else: message = 'General Proxy Error. Is Tor running?' print(message) - return(message) + return self.error_icon, message old_unixtime = (time.time()) @@ -248,7 +262,8 @@ class Sdwdate(): print(message) logger.debug(message) - return('Success') + message = 'success' + return self.success_icon, message def build_median(self): ''' @@ -300,8 +315,6 @@ class Sdwdate(): print(message) logger.debug(message) - return True - def run_sclockadj(self): ''' Set time with sneaky_clock_adjuster. @@ -366,6 +379,16 @@ class Sdwdate(): ## Set new time. cmd = 'sudo /bin/date --set @' + str(new_unixtime) call(cmd, shell=True) + self.last_shell_date = check_output('date') + #print self.last_shell_date + + def write_status(self, *args): + self.status['icon'] = args[0] + self.status['message'] = args[1] + + with open(self.status_path, 'wb') as f: + pickle.dump(self.status, f) + def signal_sigterm_handler(): if sdwdate_.sclockadj_pid != 0: @@ -383,34 +406,33 @@ if __name__ == "__main__": handler.setFormatter(formatter) logger.addHandler(handler) - first_success_dir = '/run/sdwdate' - if not os.path.exists(first_success_dir): - os.mkdir(first_success_dir) - - first_success_file = first_success_dir + '/first_success' - while True: sdwdate_ = Sdwdate() - sdwdate_loop_result = sdwdate_.sdwdate_loop() - if sdwdate_loop_result == 'Success': + icon, message = sdwdate_.sdwdate_loop() + + if message == 'success': sdwdate_.build_median() if sdwdate_.set_new_time(): sdwdate_.add_subtract_nanoseconds() - if os.path.exists(first_success_file): + if os.path.exists(sdwdate_.first_success_path): sdwdate_.run_sclockadj() else: sdwdate_.set_time_using_date() - f = open(first_success_file, 'w') + f = open(sdwdate_.first_success_path, 'w') f.close() - else: - logger.warning(sdwdate_loop_result) + sleep_time = 1 + message = 'Sleeping for %s minutes' % sleep_time + print(message + '\n\n') + logger.debug(message) + sdwdate_.write_status(icon, 'Sleeping') - message = 'Sleeping...' - print(message + '\n\n') - logger.debug(message) + else: + logger.warning(message) + print(message) + sdwdate_.write_status(icon, message) - time.sleep(1800) + time.sleep(sleep_time * 60) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() diff --git a/usr/lib/tmpfiles.d/sdwdate.conf b/usr/lib/tmpfiles.d/sdwdate.conf index 31832d73..e2bc9fc0 100644 --- a/usr/lib/tmpfiles.d/sdwdate.conf +++ b/usr/lib/tmpfiles.d/sdwdate.conf @@ -3,4 +3,6 @@ ## See the file COPYING for copying conditions. d /var/run/sdwdate 0775 sdwdate sdwdate +f /var/run/sdwdate/status 0664 sdwdate sdwdate f /var/log/sdwdate.log 0775 sdwdate sdwdate + diff --git a/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png b/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ffa34d3447dcc35ecaa3da5e24d5342b9ff069 GIT binary patch literal 15843 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

    |szeP)>9Pi0?2Vza#}FU;)Hm5_{XR?q4XjY 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 Mpj2%!^V^#3*xWB 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 literal 0 HcmV?d00001 diff --git a/usr/share/icons/anon-icon-pack/IconApproved.png b/usr/share/icons/anon-icon-pack/IconApproved.png new file mode 100644 index 0000000000000000000000000000000000000000..10e982a20bce86e6526821ac086ca1c19604e62f GIT binary patch literal 125857 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 b)zAFv>+gHd!*}QW?mTh#_D?_fiI@Hl#wthH literal 0 HcmV?d00001 From 30c4f153c896d3158943cca8aff712ff7ff8d655 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 9 Aug 2015 18:20:30 +0000 Subject: [PATCH 051/183] write status -> SIGTERM --- usr/bin/sdwdate | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index c7565ad8..daaf20b8 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -177,7 +177,7 @@ class Sdwdate(): if len(self.url_random) > 0: message = 'Requested urls %s' % (self.url_random) print(message) - logger.debug(message) + #logger.debug(message) self.urls, self.returned_values = url_to_unixtime(self.url_random) @@ -386,14 +386,23 @@ class Sdwdate(): self.status['icon'] = args[0] self.status['message'] = args[1] - with open(self.status_path, 'wb') as f: - pickle.dump(self.status, f) - + try: + with open(self.status_path, 'wb') as f: + pickle.dump(self.status, f) + except IOError as e: + logger.debug(e) def signal_sigterm_handler(): + ## Inform sdwdate-gui + icon = sdwdate_.error_icon + message = 'sdwdate stopped, signal SIGTERM received' + sdwdate_.write_status(icon, message) + if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() + logger.warning('Signal SIGTERM received. Exiting.') + sys.exit(143) if __name__ == "__main__": @@ -422,8 +431,7 @@ if __name__ == "__main__": f = open(sdwdate_.first_success_path, 'w') f.close() - sleep_time = 1 - message = 'Sleeping for %s minutes' % sleep_time + message = 'Sleeping.'# for %s minutes' % sleep_time print(message + '\n\n') logger.debug(message) sdwdate_.write_status(icon, 'Sleeping') @@ -433,6 +441,7 @@ if __name__ == "__main__": print(message) sdwdate_.write_status(icon, message) + sleep_time = 1 time.sleep(sleep_time * 60) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() From 1be71004ba8993aa21ca03f3f2b3c0097a9b0d89 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 9 Aug 2015 20:30:09 +0000 Subject: [PATCH 052/183] busy icon only if not first_success, green icon in normal cycle --- usr/bin/sdwdate | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index daaf20b8..e224eaa8 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -64,6 +64,8 @@ class Sdwdate(): self.busy_icon = '/usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png' self.error_icon = '/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png' + self.succeeded = os.path.exists(self.first_success_path) + self.status = {'icon' : '', 'message' : ''} message = 'Fetching remote times, start %s' % (time.time()) @@ -103,7 +105,12 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' - self.write_status(self.busy_icon, 'Fetching remote times...') + if self.succeeded: + ## Update tool tip. + ## Update icon after SIGTERM. + self.write_status(self.success_icon, 'Fetching remote times...') + else: + self.write_status(self.busy_icon, 'No internet. Fetching remote times...') while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 @@ -424,7 +431,8 @@ if __name__ == "__main__": if sdwdate_.set_new_time(): sdwdate_.add_subtract_nanoseconds() - if os.path.exists(sdwdate_.first_success_path): + + if sdwdate_.succeeded: sdwdate_.run_sclockadj() else: sdwdate_.set_time_using_date() From ddaf31dfaa90098081550ea4cfe3b486bf7d8eb1 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 11:47:04 +0000 Subject: [PATCH 053/183] if success / if [set_new_time] logic --- usr/lib/tmpfiles.d/sdwdate.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/usr/lib/tmpfiles.d/sdwdate.conf b/usr/lib/tmpfiles.d/sdwdate.conf index e2bc9fc0..db98265f 100644 --- a/usr/lib/tmpfiles.d/sdwdate.conf +++ b/usr/lib/tmpfiles.d/sdwdate.conf @@ -3,6 +3,5 @@ ## See the file COPYING for copying conditions. d /var/run/sdwdate 0775 sdwdate sdwdate -f /var/run/sdwdate/status 0664 sdwdate sdwdate f /var/log/sdwdate.log 0775 sdwdate sdwdate From 67a2407fe02eb63485e148deaa5922a0a3e49c8c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 15:46:46 +0000 Subject: [PATCH 054/183] remove pool_invalid, return in the while loop --- usr/bin/sdwdate | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e224eaa8..9311f491 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -122,9 +122,6 @@ class Sdwdate(): self.urls[:] = [] self.url_random[:] = [] - pool_invalid = False - message = '' - if not self.pool_one_done: while True: url_index = [] @@ -132,9 +129,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_one) == len(self.pool_one): - pool_invalid = True message = ' Time is not set: no valid time returned from pool one' - break + print(message) + logger.warning(message) + return self.error_icon, message if url_index not in self.already_picked_index_pool_one: self.already_picked_index_pool_one.append(url_index) @@ -149,9 +147,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_two) == len(self.pool_two): - pool_invalid = True message = ' Time is not set: no valid time returned from pool two' - break + print(message) + logger.warning(message) + return self.error_icon, message if url_index not in self.already_picked_index_pool_two: self.already_picked_index_pool_two.append(url_index) @@ -166,9 +165,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_three) == len(self.pool_three): - pool_invalid = True message = 'Time is not set: no valid time returned from pool three' - break + print(message) + logger.warning(message) + return self.error_icon, message if url_index not in self.already_picked_index_pool_three: self.already_picked_index_pool_three.append(url_index) @@ -176,10 +176,6 @@ class Sdwdate(): self.url_random.append(self.pool_three[url_index[0]]) break - if pool_invalid: - print(message) - return self.error_icon, message - ## Fetch remotes. if len(self.url_random) > 0: message = 'Requested urls %s' % (self.url_random) From e42ebfb5d111cbd9a782d7cfeb374e6a750c23de Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 16:31:05 +0000 Subject: [PATCH 055/183] if not pool_done -> modify loop to get rid of "break" --- usr/bin/sdwdate | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 9311f491..8b3b7838 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -123,8 +123,8 @@ class Sdwdate(): self.url_random[:] = [] if not self.pool_one_done: - while True: - url_index = [] + url_index = [] + while url_index not in self.already_picked_index_pool_one: url_index = random.sample(range(self.range_pool_one), 1) index = url_index[0] @@ -134,15 +134,13 @@ class Sdwdate(): logger.warning(message) return self.error_icon, message - if url_index not in self.already_picked_index_pool_one: - self.already_picked_index_pool_one.append(url_index) - self.url_random_pool_one.append(self.pool_one[url_index[0]]) - self.url_random.append(self.pool_one[url_index[0]]) - break + self.already_picked_index_pool_one.append(url_index) + self.url_random_pool_one.append(self.pool_one[url_index[0]]) + self.url_random.append(self.pool_one[url_index[0]]) if not self.pool_two_done: - while True: - url_index = [] + url_index = [] + while url_index not in self.already_picked_index_pool_two: url_index = random.sample(range(self.range_pool_two), 1) index = url_index[0] @@ -152,15 +150,13 @@ class Sdwdate(): logger.warning(message) return self.error_icon, message - if url_index not in self.already_picked_index_pool_two: - self.already_picked_index_pool_two.append(url_index) - self.url_random_pool_two.append(self.pool_two[url_index[0]]) - self.url_random.append(self.pool_two[url_index[0]]) - break + self.already_picked_index_pool_two.append(url_index) + self.url_random_pool_two.append(self.pool_two[url_index[0]]) + self.url_random.append(self.pool_two[url_index[0]]) if not self.pool_three_done: - while True: - url_index = [] + url_index = [] + while url_index not in self.already_picked_index_pool_three: url_index = random.sample(range(self.range_pool_three), 1) index = url_index[0] @@ -170,11 +166,9 @@ class Sdwdate(): logger.warning(message) return self.error_icon, message - if url_index not in self.already_picked_index_pool_three: - self.already_picked_index_pool_three.append(url_index) - self.url_random_pool_three.append(self.pool_three[url_index[0]]) - self.url_random.append(self.pool_three[url_index[0]]) - break + self.already_picked_index_pool_three.append(url_index) + self.url_random_pool_three.append(self.pool_three[url_index[0]]) + self.url_random.append(self.pool_three[url_index[0]]) ## Fetch remotes. if len(self.url_random) > 0: From d00d16202f593e67d0739f5c82c47c9f6ed2c232 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 17:59:12 +0000 Subject: [PATCH 056/183] if success / if [set_new_time] logic (should have been ddaf31dfaa) --- usr/bin/sdwdate | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 8b3b7838..0c650842 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -418,16 +418,16 @@ if __name__ == "__main__": if message == 'success': sdwdate_.build_median() - - if sdwdate_.set_new_time(): - sdwdate_.add_subtract_nanoseconds() - - if sdwdate_.succeeded: + if sdwdate_.succeeded: + if sdwdate_.set_new_time(): + sdwdate_.add_subtract_nanoseconds() sdwdate_.run_sclockadj() - else: + else: + f = open(sdwdate_.first_success_path, 'w') + f.close() + if sdwdate_.set_new_time(): + sdwdate_.add_subtract_nanoseconds() sdwdate_.set_time_using_date() - f = open(sdwdate_.first_success_path, 'w') - f.close() message = 'Sleeping.'# for %s minutes' % sleep_time print(message + '\n\n') From b8532e270716d174df228641024310164504cb13 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 19:21:26 +0000 Subject: [PATCH 057/183] swdate_loop return status only, message written in sdwdate message --- usr/bin/sdwdate | 55 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 0c650842..cb7ac913 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -68,6 +68,8 @@ class Sdwdate(): self.status = {'icon' : '', 'message' : ''} + self.message = '' + message = 'Fetching remote times, start %s' % (time.time()) print(message) logger.debug(message) @@ -129,10 +131,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_one) == len(self.pool_one): - message = ' Time is not set: no valid time returned from pool one' + self.message = ' Time is not set: no valid time returned from pool one' print(message) logger.warning(message) - return self.error_icon, message + return self.error_icon, 'error' self.already_picked_index_pool_one.append(url_index) self.url_random_pool_one.append(self.pool_one[url_index[0]]) @@ -145,10 +147,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_two) == len(self.pool_two): - message = ' Time is not set: no valid time returned from pool two' + self.message = ' Time is not set: no valid time returned from pool two' print(message) logger.warning(message) - return self.error_icon, message + return self.error_icon, 'error' self.already_picked_index_pool_two.append(url_index) self.url_random_pool_two.append(self.pool_two[url_index[0]]) @@ -161,10 +163,10 @@ class Sdwdate(): index = url_index[0] if len(self.already_picked_index_pool_three) == len(self.pool_three): - message = 'Time is not set: no valid time returned from pool three' + self.message = 'Time is not set: no valid time returned from pool three' print(message) logger.warning(message) - return self.error_icon, message + return self.error_icon, 'error' self.already_picked_index_pool_three.append(url_index) self.url_random_pool_three.append(self.pool_three[url_index[0]]) @@ -180,18 +182,19 @@ class Sdwdate(): if len(self.urls) == 0: ## Most likely, internet connection is down. - message = ('No values returned from url_to_unixtime. Internet connection might be down.') + self.message = ('No values returned from url_to_unixtime. Internet connection might be down.') print(message) - return self.error_icon, message + return self.error_icon, 'error' message = 'Returned urls "%s"' % (self.urls) print(message) logger.debug(message) else: - message = 'Something is wrong. sdwdate loop could not build a list or urls.' + self.message = ('Something is wrong. sdwdate loop could not build a list or urls.\n' + + 'Please report this bug') print(message) - return self.status_color[2], message + '\nPlease report this bug' + return self.status_color[2], 'error' if not self.general_proxy_error(self.returned_values): for i in range(len(self.urls)): @@ -203,9 +206,9 @@ class Sdwdate(): self.invalid_urls.append(self.urls[i]) self.url_errors.append(self.returned_values[i]) else: - message = 'General Proxy Error. Is Tor running?' + self.message = 'General Proxy Error. Is Tor running?' print(message) - return self.error_icon, message + return self.error_icon, 'error' old_unixtime = (time.time()) @@ -259,8 +262,9 @@ class Sdwdate(): print(message) logger.debug(message) - message = 'success' - return self.success_icon, message + last_shell_date = check_output('date').strip() + self.message = 'Last run (on ' + last_shell_date + ') was successful.' + return self.success_icon, 'success' def build_median(self): ''' @@ -376,8 +380,6 @@ class Sdwdate(): ## Set new time. cmd = 'sudo /bin/date --set @' + str(new_unixtime) call(cmd, shell=True) - self.last_shell_date = check_output('date') - #print self.last_shell_date def write_status(self, *args): self.status['icon'] = args[0] @@ -414,9 +416,9 @@ if __name__ == "__main__": while True: sdwdate_ = Sdwdate() - icon, message = sdwdate_.sdwdate_loop() + icon, status = sdwdate_.sdwdate_loop() - if message == 'success': + if status == 'success': sdwdate_.build_median() if sdwdate_.succeeded: if sdwdate_.set_new_time(): @@ -429,17 +431,18 @@ if __name__ == "__main__": sdwdate_.add_subtract_nanoseconds() sdwdate_.set_time_using_date() - message = 'Sleeping.'# for %s minutes' % sleep_time - print(message + '\n\n') - logger.debug(message) - sdwdate_.write_status(icon, 'Sleeping') - - else: - logger.warning(message) - print(message) + print('sleeping' + '\n\n') + logger.debug('sleeping') + message = sdwdate_.message + '\nSleeping.' sdwdate_.write_status(icon, message) + elif status == 'error': + logger.warning(sdwdate_.message) + print(sdwdate_.message) + sdwdate_.write_status(icon, sdwdate_.message) + sleep_time = 1 time.sleep(sleep_time * 60) if sdwdate_.sclockadj_pid != 0: sdwdate_.kill_sclockadj() + From 2e1b322f6be086605ed7984fdf98b85e3efa40ae Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 10 Aug 2015 19:34:26 +0000 Subject: [PATCH 058/183] sdwdate_ <-> sdwdate --- usr/bin/sdwdate | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index cb7ac913..a1c950d9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -393,12 +393,12 @@ class Sdwdate(): def signal_sigterm_handler(): ## Inform sdwdate-gui - icon = sdwdate_.error_icon + icon = sdwdate.error_icon message = 'sdwdate stopped, signal SIGTERM received' - sdwdate_.write_status(icon, message) + sdwdate.write_status(icon, message) - if sdwdate_.sclockadj_pid != 0: - sdwdate_.kill_sclockadj() + if sdwdate.sclockadj_pid != 0: + sdwdate.kill_sclockadj() logger.warning('Signal SIGTERM received. Exiting.') @@ -415,34 +415,34 @@ if __name__ == "__main__": logger.addHandler(handler) while True: - sdwdate_ = Sdwdate() - icon, status = sdwdate_.sdwdate_loop() + sdwdate = Sdwdate() + icon, status = sdwdate.sdwdate_loop() if status == 'success': - sdwdate_.build_median() - if sdwdate_.succeeded: - if sdwdate_.set_new_time(): - sdwdate_.add_subtract_nanoseconds() - sdwdate_.run_sclockadj() + sdwdate.build_median() + if sdwdate.succeeded: + if sdwdate.set_new_time(): + sdwdate.add_subtract_nanoseconds() + sdwdate.run_sclockadj() else: - f = open(sdwdate_.first_success_path, 'w') + f = open(sdwdate.first_success_path, 'w') f.close() - if sdwdate_.set_new_time(): - sdwdate_.add_subtract_nanoseconds() - sdwdate_.set_time_using_date() + if sdwdate.set_new_time(): + sdwdate.add_subtract_nanoseconds() + sdwdate.set_time_using_date() print('sleeping' + '\n\n') logger.debug('sleeping') - message = sdwdate_.message + '\nSleeping.' - sdwdate_.write_status(icon, message) + message = sdwdate.message + '\nSleeping.' + sdwdate.write_status(icon, message) elif status == 'error': - logger.warning(sdwdate_.message) - print(sdwdate_.message) - sdwdate_.write_status(icon, sdwdate_.message) + logger.warning(sdwdate.message) + print(sdwdate.message) + sdwdate.write_status(icon, sdwdate.message) sleep_time = 1 time.sleep(sleep_time * 60) - if sdwdate_.sclockadj_pid != 0: - sdwdate_.kill_sclockadj() + if sdwdate.sclockadj_pid != 0: + sdwdate.kill_sclockadj() From 16b07a6274cb0fb1e5383d7741dc0a0ca31deafe Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 11 Aug 2015 19:23:49 +0000 Subject: [PATCH 059/183] new success icon --- usr/bin/sdwdate | 2 +- .../anon-icon-pack/Ambox_currentevent.svg.png | Bin 0 -> 61903 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index a1c950d9..15d850d0 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -60,7 +60,7 @@ class Sdwdate(): self.first_success_path = '/var/run/sdwdate/first_success' self.status_path = '/var/run/sdwdate/status' - self.success_icon = '/usr/share/icons/anon-icon-pack/IconApproved.png' + self.success_icon = '/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png' self.busy_icon = '/usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png' self.error_icon = '/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png' diff --git a/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png b/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd55443a01d078f6bc2b23610ecf0b017d3204e GIT binary patch literal 61903 zcmV*kKuf=gP))YEUBIt3+^P(leMlu$wmC6rJ?2_=+JLJ5aG#7o_Q5@rY1f3ts0QcwvA zbScYBQ>#sC8d1a06MpnqLdF2lj6qXqLjahu@qHhwd>kRnm$L6Al<;yziJ)J`Sb6nu zqu&@iSqg1ELR&60F&`nqLKo#i$S|4~KnO$Ag_f!zZGWHKymy9%T0#jgOOy!uWq?)R=z5jzGhVL=alG(}Bhf^a zF!VB^YZ@{|qV^A3DoA+3ZuyhnE|o6%>4 zpl1QrUDJEM@EPwFKJ7#;U{vK~Eb=H(wH!XY)pJe-g?ZE@GHU9N$E5!AJ$)5FE2--e zUXCac^h<=b*Y>_%GxSeuA^!=&r~C6O(P9WKYtJEPWvH3GF{tXB@}w&;mVAASNi7kP z`jb(qe{_Lae-|<2b(&x>5v;Gqj))&J+|#2NadsC`vugKx!jn znugRyKE?0S=W$a+BT3_-al}{KKT-X6m%t^Ia9E&3&@+tXH*}SUlD;pBkpDeez*mzx z;>9S@X;ROgQyXsfDXQm*u}rsm&SxK$`bfmoemfe_uQ>4G${uwiC6q9GQ6lIW!kTNk zPS$;auW4oeANeo?req2B%{z5mO%+mc|Xc`l~jUZY-8C+fgFuX~3yJ zmcgOnp{w;W|NFE+I=4J^#aWbkewj_Hb19g%v5cE~Y*^~Q9Z%|?*mp@)pG$)!l<<fs_*u+`K0egtvv8=LbEK2o76)`eQV2|u`in@} z<4G_l`jJdZ;-(~FO3WlksaP{<7O7{aT>x<@%pp^MAgXg=X>N51vkfJJo&;82J>2LE z^9!TWe+HT+3X^cL_9B&~LOlyif*OQ1C^MiUAgB&$REIR`LI!044WBMDO>r9VFdAn(nzY24qAW_B#VtYT@ALwSWzk2Z!Qx zkHi@pb56D>K+ZX3D)sE`_{Y!_{Ym|X9iObbr1WBf5)L&=1nq*=*9@F#l>2|Kh5b$G z?`-kpoU`Muk}Y;0lE#2!aka)V%~^$Pt?3MtkEB)QDw0lbPMW0M$X-R#QqdL`rERG* zo{+rQ8{@!Wlz~VbGifRY>FxOESI_RBO9W$Z(9|y6eR0FDa-B*jVP;SwXa`n(x2Mez zq2K8h{$mls_MLT4Z8!{*P|t>xv<6L%sW({J=0_K~gr^Es3{kt0v@_0hle8^{%SED) zB2U{)6Lt;6*x55q|8N}BG%X&AL0fjbq&9NkIKTt=wu}hx>=>AW#mXPB&N(Ad!C$8V@G�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 literal 0 HcmV?d00001 From 2ae378620550a1f4c965ec7c8be1a57fc5289cc1 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 11 Aug 2015 19:31:19 +0000 Subject: [PATCH 060/183] kill remaining threads on exit --- .../python2.7/dist-packages/sdwdate/url_to_unixtime.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index 71574ba4..b8dc0ba5 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -131,10 +131,7 @@ def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): def url_to_unixtime(remotes): - #print remotes - threads = [] - timeout = gevent.Timeout() timer = [] seconds = 10 @@ -153,11 +150,15 @@ def url_to_unixtime(remotes): for i in range(len(remotes)): try: threads[i].join(timeout=timer[i]) - except Timeout: urls.append(threads[i].args[2]) unix_times.append('Timeout') gevent.kill(threads[i]) + ## Cleanup before next round. + ## On fast retries, unfinished greenlets might be returned. + for i in range(len(threads)): + gevent.kill(threads[i]) + print 'GEVENT exiting' return urls, unix_times From 154a368372deb631767d0f2d9340e71945ff6463 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 11 Aug 2015 19:38:21 +0000 Subject: [PATCH 061/183] retry on error "No values returned from url_to_unixtime". sleep times. --- usr/bin/sdwdate | 57 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 15d850d0..066ef6b0 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -107,10 +107,13 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' + global retrying_loop + if self.succeeded: - ## Update tool tip. - ## Update icon after SIGTERM. - self.write_status(self.success_icon, 'Fetching remote times...') + if not retrying_loop: + ## Update tool tip. + ## Update icon after SIGTERM. + self.write_status(self.success_icon, 'Fetching remote times...') else: self.write_status(self.busy_icon, 'No internet. Fetching remote times...') @@ -132,8 +135,7 @@ class Sdwdate(): if len(self.already_picked_index_pool_one) == len(self.pool_one): self.message = ' Time is not set: no valid time returned from pool one' - print(message) - logger.warning(message) + logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_one.append(url_index) @@ -148,8 +150,7 @@ class Sdwdate(): if len(self.already_picked_index_pool_two) == len(self.pool_two): self.message = ' Time is not set: no valid time returned from pool two' - print(message) - logger.warning(message) + logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_two.append(url_index) @@ -164,8 +165,7 @@ class Sdwdate(): if len(self.already_picked_index_pool_three) == len(self.pool_three): self.message = 'Time is not set: no valid time returned from pool three' - print(message) - logger.warning(message) + logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_three.append(url_index) @@ -176,15 +176,25 @@ class Sdwdate(): if len(self.url_random) > 0: message = 'Requested urls %s' % (self.url_random) print(message) - #logger.debug(message) + logger.debug(message) self.urls, self.returned_values = url_to_unixtime(self.url_random) + #if len(self.urls) == 0: + ### Most likely, internet connection is down. + #self.message = ('No values returned from url_to_unixtime. Internet connection might be down.') + #print(message) + #return self.error_icon, 'error' + if len(self.urls) == 0: - ## Most likely, internet connection is down. - self.message = ('No values returned from url_to_unixtime. Internet connection might be down.') - print(message) - return self.error_icon, 'error' + if retrying_loop: + retrying_loop = False + self.message = 'No values returned from url_to_unixtime. Internet connection might be down.' + return self.error_icon, 'error' + else: + retrying_loop = True + self.message = 'No values returned from url_to_unixtime. Retrying...' + return self.busy_icon, 'retry' message = 'Returned urls "%s"' % (self.urls) print(message) @@ -201,7 +211,6 @@ class Sdwdate(): if self.check_remote(self.urls[i], self.returned_values[i]): self.valid_urls.append(self.urls[i]) self.unixtimes.append(self.returned_values[i]) - else: self.invalid_urls.append(self.urls[i]) self.url_errors.append(self.returned_values[i]) @@ -264,6 +273,7 @@ class Sdwdate(): last_shell_date = check_output('date').strip() self.message = 'Last run (on ' + last_shell_date + ') was successful.' + retrying_loop = False return self.success_icon, 'success' def build_median(self): @@ -414,6 +424,8 @@ if __name__ == "__main__": handler.setFormatter(formatter) logger.addHandler(handler) + retrying_loop = False + while True: sdwdate = Sdwdate() icon, status = sdwdate.sdwdate_loop() @@ -431,18 +443,25 @@ if __name__ == "__main__": sdwdate.add_subtract_nanoseconds() sdwdate.set_time_using_date() - print('sleeping' + '\n\n') - logger.debug('sleeping') message = sdwdate.message + '\nSleeping.' sdwdate.write_status(icon, message) + sleep_time = 1 + + elif status == 'retry': + logger.warning(sdwdate.message) + print(sdwdate.message) + sdwdate.write_status(icon, sdwdate.message) + #print icon + sleep_time = 0.2 elif status == 'error': logger.warning(sdwdate.message) print(sdwdate.message) sdwdate.write_status(icon, sdwdate.message) + sleep_time = 0.2 - sleep_time = 1 + print('Sleeping' + '\n\n') + logger.debug('Sleeping') time.sleep(sleep_time * 60) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() - From 09255f18212ad3f5d2e189614c8616cf655179b9 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 12 Aug 2015 18:45:44 +0000 Subject: [PATCH 062/183] icons path /usr/share/icons/sdwdate --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 066ef6b0..6b24914e 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -60,9 +60,9 @@ class Sdwdate(): self.first_success_path = '/var/run/sdwdate/first_success' self.status_path = '/var/run/sdwdate/status' - self.success_icon = '/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png' - self.busy_icon = '/usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png' - self.error_icon = '/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png' + self.success_icon = '/usr/share/icons/sdwdate-gui/Ambox_currentevent.svg.png' + self.busy_icon = '/usr/share/icons/sdwdate-gui/620px-Ambox_outdated.svg.png' + self.error_icon = '/usr/share/icons/sdwdate-gui/212px-Timeblock.svg.png' self.succeeded = os.path.exists(self.first_success_path) From 8d4d623f9863f5a5f4a6a5ee75345ca19f12672e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 12 Aug 2015 18:51:46 +0000 Subject: [PATCH 063/183] if len(self.already_picked_index_pool) >= len(self.pool): --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 6b24914e..93d2cb28 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -133,7 +133,7 @@ class Sdwdate(): url_index = random.sample(range(self.range_pool_one), 1) index = url_index[0] - if len(self.already_picked_index_pool_one) == len(self.pool_one): + if len(self.already_picked_index_pool_one) >= len(self.pool_one): self.message = ' Time is not set: no valid time returned from pool one' logger.warning(self.message) return self.error_icon, 'error' @@ -148,7 +148,7 @@ class Sdwdate(): url_index = random.sample(range(self.range_pool_two), 1) index = url_index[0] - if len(self.already_picked_index_pool_two) == len(self.pool_two): + if len(self.already_picked_index_pool_two) >= len(self.pool_two): self.message = ' Time is not set: no valid time returned from pool two' logger.warning(self.message) return self.error_icon, 'error' @@ -163,7 +163,7 @@ class Sdwdate(): url_index = random.sample(range(self.range_pool_three), 1) index = url_index[0] - if len(self.already_picked_index_pool_three) == len(self.pool_three): + if len(self.already_picked_index_pool_three) >= len(self.pool_three): self.message = 'Time is not set: no valid time returned from pool three' logger.warning(self.message) return self.error_icon, 'error' From 5810f4d7e36b4abbfb66f2280955fc8930a6b7d2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 12 Aug 2015 18:54:39 +0000 Subject: [PATCH 064/183] sigterm handler -> logger.debug --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 93d2cb28..604e62ea 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -410,7 +410,7 @@ def signal_sigterm_handler(): if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() - logger.warning('Signal SIGTERM received. Exiting.') + logger.debug('Signal SIGTERM received. Exiting.') sys.exit(143) From 1d283590013829ada2b9e3b3952fc6b0e2875ec7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 12 Aug 2015 18:59:22 +0000 Subject: [PATCH 065/183] logger setLevel INFO --- usr/bin/sdwdate | 50 ++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 604e62ea..1e3e3bfd 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -72,7 +72,7 @@ class Sdwdate(): message = 'Fetching remote times, start %s' % (time.time()) print(message) - logger.debug(message) + logger.info(message) def general_proxy_error(self, pools): ''' @@ -92,12 +92,12 @@ class Sdwdate(): n = int(value) message = 'Remote status "%s", True' % (remote) print(message) - logger.debug(message) + logger.info(message) return True except ValueError: message = 'Remote status "%s", False: %s' % (remote, value) print(message) - logger.debug(message) + logger.info(message) return False def sdwdate_loop(self): @@ -121,7 +121,7 @@ class Sdwdate(): self.iteration = self.iteration + 1 message = 'Running sdwdate loop, iteration %s' % (self.iteration) print(message) - logger.debug(message) + logger.info(message) ## Clear the lists. self.urls[:] = [] @@ -176,7 +176,7 @@ class Sdwdate(): if len(self.url_random) > 0: message = 'Requested urls %s' % (self.url_random) print(message) - logger.debug(message) + logger.info(message) self.urls, self.returned_values = url_to_unixtime(self.url_random) @@ -198,7 +198,7 @@ class Sdwdate(): message = 'Returned urls "%s"' % (self.urls) print(message) - logger.debug(message) + logger.info(message) else: self.message = ('Something is wrong. sdwdate loop could not build a list or urls.\n' + @@ -233,7 +233,7 @@ class Sdwdate(): self.pools_diff.append(int(web_time) - int(old_unixtime)) message = 'Pool one: last_url %s, web_time %s' % (valid_url, web_time) print(message) - logger.debug(message) + logger.info(message) if not self.pool_two_done: for i in range(len(self.url_random_pool_two)): @@ -245,7 +245,7 @@ class Sdwdate(): self.pools_diff.append(int(web_time) - int(old_unixtime)) message = 'Pool two: last_url %s, web_time %s' % (valid_url, web_time) print(message) - logger.debug(message) + logger.info(message) if not self.pool_three_done: for i in range(len(self.url_random_pool_three)): @@ -257,19 +257,19 @@ class Sdwdate(): self.pools_diff.append(int(web_time) - int(old_unixtime)) message = 'Pool three: last_url %s, web_time %s' % (valid_url, web_time) print(message) - logger.debug(message) + logger.info(message) message = 'Valid urls %s' % (self.valid_urls) print(message) - logger.debug(message) + logger.info(message) message = 'Bad urls %s' % (self.invalid_urls) print(message) - logger.debug(message) + logger.info(message) message = 'Fetching remote times, end %s' % (time.time()) print(message) - logger.debug(message) + logger.info(message) last_shell_date = check_output('date').strip() self.message = 'Last run (on ' + last_shell_date + ') was successful.' @@ -283,7 +283,7 @@ class Sdwdate(): diffs = sorted(self.pools_diff) message = 'Pool differences, sorted: %s' % diffs print(message) - logger.debug(message) + logger.info(message) self.median = diffs[(len(diffs) / 2)] def set_new_time(self): @@ -293,7 +293,7 @@ class Sdwdate(): if self.median == 0: message = 'Time difference = 0. Not setting time' print(message) - logger.debug(message) + logger.info(message) return False else: return True @@ -318,13 +318,13 @@ class Sdwdate(): #print 'nanoseconds %s' % nanoseconds message = 'Median time difference: %s' % self.median print(message) - logger.debug(message) + logger.info(message) message = 'Seconds to add: %s %s' % (signs[sign], seconds) print(message) - logger.debug(message) + logger.info(message) message = 'New time difference: %s' % self.new_diff print(message) - logger.debug(message) + logger.info(message) def run_sclockadj(self): ''' @@ -355,7 +355,7 @@ class Sdwdate(): message = 'Running sclockaj, PID=%s' % (self.sclockadj_pid) print(message) - logger.debug(message) + logger.info(message) ## Running sclockadj_debug_helper, in case... ## May be read the last line to ensure sclockadj is running. @@ -375,17 +375,17 @@ class Sdwdate(): def set_time_using_date(self): message = 'Setting time using date.' print(message) - logger.debug(message) + logger.info(message) old_unixtime = float('%.9f' % (time.time())) message = 'Old unixttime: %s' % (str(old_unixtime)) print(message) - logger.debug(message) + logger.info(message) new_unixtime = float('%.9f' % (old_unixtime + self.new_diff)) message = 'New unixtime %s' % (str(new_unixtime)) print(message) - logger.debug(message) + logger.info(message) ## Set new time. cmd = 'sudo /bin/date --set @' + str(new_unixtime) @@ -399,7 +399,7 @@ class Sdwdate(): with open(self.status_path, 'wb') as f: pickle.dump(self.status, f) except IOError as e: - logger.debug(e) + logger.info(e) def signal_sigterm_handler(): ## Inform sdwdate-gui @@ -410,7 +410,7 @@ def signal_sigterm_handler(): if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() - logger.debug('Signal SIGTERM received. Exiting.') + logger.info('Signal SIGTERM received. Exiting.') sys.exit(143) @@ -418,7 +418,7 @@ if __name__ == "__main__": gevent.signal(signal.SIGTERM, signal_sigterm_handler) logger = logging.getLogger('sdwdate_log') - logger.setLevel(logging.DEBUG) + logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler = logging.FileHandler('/var/log/sdwdate.log') handler.setFormatter(formatter) @@ -461,7 +461,7 @@ if __name__ == "__main__": sleep_time = 0.2 print('Sleeping' + '\n\n') - logger.debug('Sleeping') + logger.info('Sleeping') time.sleep(sleep_time * 60) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() From 2648e71a19f0fc97432813fffa407d99b076e2a0 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 12 Aug 2015 19:50:18 +0000 Subject: [PATCH 066/183] debian/control python-gevent --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 45ba600e..7d460568 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,7 @@ Package: sdwdate Architecture: all Depends: sudo, logrotate, bc, ruby | ruby-interpreter, ruby-inline, ruby-dev, - ruby-all-dev, gcc, libc6-dev, python-dateutil, + ruby-all-dev, gcc, libc6-dev, python-dateutil, python-gevent, python-pysocks | python-socksipy, python, tor, ${misc:Depends} Recommends: vbox-disable-timesync, timesanitycheck, bootclockrandomization, timesync From 76833466f25220552421e00beb58d792294d5b91 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 19 Aug 2015 20:57:25 +0000 Subject: [PATCH 067/183] configuration file -> .conf --- .../{30_sdwdate_default => 30_sdwdate_default.conf} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/sdwdate-python.d/{30_sdwdate_default => 30_sdwdate_default.conf} (100%) diff --git a/etc/sdwdate-python.d/30_sdwdate_default b/etc/sdwdate-python.d/30_sdwdate_default.conf similarity index 100% rename from etc/sdwdate-python.d/30_sdwdate_default rename to etc/sdwdate-python.d/30_sdwdate_default.conf From d6f8dd608d072388ce4a326742ae54026f697772 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 20 Aug 2015 20:00:09 +0000 Subject: [PATCH 068/183] - print pools --- usr/lib/python2.7/dist-packages/sdwdate/config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 134eb928..9b58ebbc 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -107,8 +107,4 @@ def read_pools(): pool_two_sorted = list(set(pool_two_sorted)) pool_three_sorted = list(set(pool_three_sorted)) - print pool_one_sorted - print pool_two_sorted - print pool_three_sorted - return(pool_one_sorted, pool_two_sorted, pool_three_sorted) From 3735df61d4a2c9eb9335857f5068dc6cfd37b356 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 23 Aug 2015 11:02:08 +0000 Subject: [PATCH 069/183] success file in order to display busy icon - no internet only if not first_success --- usr/bin/sdwdate | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 1e3e3bfd..2357e84c 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -58,13 +58,15 @@ class Sdwdate(): self.last_shell_date = '' self.first_success_path = '/var/run/sdwdate/first_success' + self.success_path = '/var/run/sdwdate/success' self.status_path = '/var/run/sdwdate/status' self.success_icon = '/usr/share/icons/sdwdate-gui/Ambox_currentevent.svg.png' self.busy_icon = '/usr/share/icons/sdwdate-gui/620px-Ambox_outdated.svg.png' self.error_icon = '/usr/share/icons/sdwdate-gui/212px-Timeblock.svg.png' - self.succeeded = os.path.exists(self.first_success_path) + self.first_success = os.path.exists(self.first_success_path) + self.success = os.path.exists(self.success_path) self.status = {'icon' : '', 'message' : ''} @@ -109,13 +111,16 @@ class Sdwdate(): ''' global retrying_loop - if self.succeeded: + if self.success: if not retrying_loop: ## Update tool tip. ## Update icon after SIGTERM. self.write_status(self.success_icon, 'Fetching remote times...') else: - self.write_status(self.busy_icon, 'No internet. Fetching remote times...') + if not self.first_success: + self.write_status(self.busy_icon, 'No internet. Fetching remote times...') + else: + self.write_status(self.success_icon, 'Fetching remote times...') while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 @@ -432,13 +437,18 @@ if __name__ == "__main__": if status == 'success': sdwdate.build_median() - if sdwdate.succeeded: + + if sdwdate.success: if sdwdate.set_new_time(): sdwdate.add_subtract_nanoseconds() sdwdate.run_sclockadj() else: - f = open(sdwdate.first_success_path, 'w') + if not sdwdate.first_success: + f = open(sdwdate.first_success_path, 'w') + f.close() + f = open(sdwdate.success_path, 'w') f.close() + if sdwdate.set_new_time(): sdwdate.add_subtract_nanoseconds() sdwdate.set_time_using_date() From 63d8acdb0ab86cd56c05129c7d341f44f3ce8e0f Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Wed, 19 Aug 2015 23:37:06 +0000 Subject: [PATCH 070/183] workaround for 'dh_installinit should run systemd-tmpfiles if a /usr/lib/tmpfiles.d/ snippet gets shipped for systemd-only packages also' - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=795519 --- debian/sdwdate.postinst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 672b244f..94a51820 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -42,6 +42,15 @@ case "$1" in ;; esac +## workaround for 'dh_installinit should run systemd-tmpfiles if a +## /usr/lib/tmpfiles.d/ snippet gets shipped for systemd-only packages +## also' - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=795519 +# In case this system is running systemd, we need to ensure that all +# necessary tmpfiles (if any) are created before starting. +if [ -d /run/systemd/system ] ; then + systemd-tmpfiles --create /usr/lib/tmpfiles.d/sdwdate.conf >/dev/null || true +fi + true "INFO: debhelper beginning here." #DEBHELPER# From 685d7ca99f41be48ea8bfd5a51bc72a56094d9df Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 24 Aug 2015 16:13:18 +0000 Subject: [PATCH 071/183] write_status, "Sleeping for" --- usr/bin/sdwdate | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 2357e84c..13debea0 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -399,12 +399,9 @@ class Sdwdate(): def write_status(self, *args): self.status['icon'] = args[0] self.status['message'] = args[1] + with open(self.status_path, 'wb') as f: + pickle.dump(self.status, f) - try: - with open(self.status_path, 'wb') as f: - pickle.dump(self.status, f) - except IOError as e: - logger.info(e) def signal_sigterm_handler(): ## Inform sdwdate-gui @@ -437,7 +434,7 @@ if __name__ == "__main__": if status == 'success': sdwdate.build_median() - + if sdwdate.success: if sdwdate.set_new_time(): sdwdate.add_subtract_nanoseconds() @@ -455,23 +452,24 @@ if __name__ == "__main__": message = sdwdate.message + '\nSleeping.' sdwdate.write_status(icon, message) - sleep_time = 1 + sleep_time = 10 elif status == 'retry': logger.warning(sdwdate.message) print(sdwdate.message) sdwdate.write_status(icon, sdwdate.message) #print icon - sleep_time = 0.2 + sleep_time = 0.1 elif status == 'error': logger.warning(sdwdate.message) print(sdwdate.message) sdwdate.write_status(icon, sdwdate.message) - sleep_time = 0.2 + sleep_time = 0.1 - print('Sleeping' + '\n\n') - logger.info('Sleeping') + message = 'Sleeping for %s minutes' % (sleep_time) + print(message) + logger.info(message) time.sleep(sleep_time * 60) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() From f786a5a89ff8dd54d770c38a0c14f86f0f12a83f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 24 Aug 2015 16:15:20 +0000 Subject: [PATCH 072/183] indentation error --- usr/bin/sdwdate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 13debea0..e5284071 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -399,8 +399,8 @@ class Sdwdate(): def write_status(self, *args): self.status['icon'] = args[0] self.status['message'] = args[1] - with open(self.status_path, 'wb') as f: - pickle.dump(self.status, f) + with open(self.status_path, 'wb') as f: + pickle.dump(self.status, f) def signal_sigterm_handler(): From 41bb96e9ccffaf82ae7aa8db0cafab68ec8aec39 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 28 Aug 2015 18:53:24 +0000 Subject: [PATCH 073/183] timesanitycheck --- usr/bin/sdwdate | 27 +++++++++++-- .../dist-packages/sdwdate/timesanitycheck.py | 38 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e5284071..f0b35221 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -12,6 +12,7 @@ import pickle from sdwdate.url_to_unixtime import url_to_unixtime from sdwdate.config import read_pools +from sdwdate.timesanitycheck import timesanitycheck class Sdwdate(): @@ -102,6 +103,19 @@ class Sdwdate(): logger.info(message) return False + def time_sanity_check(self): + status, time_one, time_two = timesanitycheck() + + if status == 'sane': + self.message = 'The clock is %s. Current time "%s"' % (status, time_one) + elif status == 'slow': + self.message = ('The clock is %s. Current time "%s" is less than the build timestamp "%s"' + % (status, time_one, time_two)) + elif status == 'fast': + self.message = ('The clock is %s. Current time "%s" is greater than the build timestamp "%s"' + % (status, time_one, time_two)) + return status + def sdwdate_loop(self): ''' Check remotes. @@ -109,6 +123,14 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' + time_sanity_check = self.time_sanity_check() + if time_sanity_check == 'sane': + print self.message + logger.info(self.message) + else: + print self.message + return self.error_icon, 'error' + global retrying_loop if self.success: @@ -404,7 +426,7 @@ class Sdwdate(): def signal_sigterm_handler(): - ## Inform sdwdate-gui + # Inform sdwdate-gui icon = sdwdate.error_icon message = 'sdwdate stopped, signal SIGTERM received' sdwdate.write_status(icon, message) @@ -413,7 +435,6 @@ def signal_sigterm_handler(): sdwdate.kill_sclockadj() logger.info('Signal SIGTERM received. Exiting.') - sys.exit(143) if __name__ == "__main__": @@ -465,7 +486,7 @@ if __name__ == "__main__": logger.warning(sdwdate.message) print(sdwdate.message) sdwdate.write_status(icon, sdwdate.message) - sleep_time = 0.1 + sleep_time = 10 message = 'Sleeping for %s minutes' % (sleep_time) print(message) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py new file mode 100644 index 00000000..f42df687 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py @@ -0,0 +1,38 @@ +import os, time +from datetime import datetime + +def timesanitycheck(): + whonix_build_file = '/usr/share/whonix/build_timestamp' + anondist_build_file = '/var/lib/anon-dist/build_version' + spare_file = '/usr/share/zoneinfo/UTC' + + if os.path.exists(whonix_build_file): + build_timestamp_file = whonix_build_file + elif os.path.exists(anondist_build_file): + build_timestamp_file = anondist_build_file + else: + build_timestamp_file = spare_file + + current_time = datetime.strftime(datetime.now(), '%a %b %d %H:%M:%S UTC %Y') + current_unixtime = time.mktime(datetime.strptime(current_time, '%a %b %d %H:%M:%S UTC %Y').timetuple()) + + build_time = time.strftime('%a %b %d %H:%M:%S UTC %Y', time.gmtime(os.path.getmtime(build_timestamp_file))) + build_unixtime = time.mktime(datetime.strptime(build_time, '%a %b %d %H:%M:%S UTC %Y').timetuple()) + + expiration_unixtime = 1409936800 + expiration_time = datetime.strftime(datetime.fromtimestamp(expiration_unixtime), '%a %b %d %H:%M:%S UTC %Y') + + if current_unixtime < build_unixtime: + status = 'slow' + time_one = current_time + time_two = build_time + elif current_unixtime > expiration_unixtime: + status = 'fast' + time_one = current_time + time_two = expiration_time + else: + status = 'sane' + time_one = current_time + time_two = '' + + return status, time_one, time_two From f2fd1c1dbd6586f154b1a348f5d8fb2a290d99cf Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 29 Aug 2015 08:55:39 +0000 Subject: [PATCH 074/183] if clock is fast, currnt time greater than expiration time --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index f0b35221..a47a2987 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -112,7 +112,7 @@ class Sdwdate(): self.message = ('The clock is %s. Current time "%s" is less than the build timestamp "%s"' % (status, time_one, time_two)) elif status == 'fast': - self.message = ('The clock is %s. Current time "%s" is greater than the build timestamp "%s"' + self.message = ('The clock is %s. Current time "%s" is greater than the expiration timestamp "%s"' % (status, time_one, time_two)) return status From 8bf60e130fa487674c779a197acc562d87f0e5ee Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 29 Aug 2015 20:29:30 +0000 Subject: [PATCH 075/183] import sys. Cleanup --- usr/bin/sdwdate | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index a47a2987..017504b3 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -1,5 +1,6 @@ #!/usr/bin/env python +import sys import logging import signal import gevent @@ -158,7 +159,6 @@ class Sdwdate(): url_index = [] while url_index not in self.already_picked_index_pool_one: url_index = random.sample(range(self.range_pool_one), 1) - index = url_index[0] if len(self.already_picked_index_pool_one) >= len(self.pool_one): self.message = ' Time is not set: no valid time returned from pool one' @@ -173,7 +173,6 @@ class Sdwdate(): url_index = [] while url_index not in self.already_picked_index_pool_two: url_index = random.sample(range(self.range_pool_two), 1) - index = url_index[0] if len(self.already_picked_index_pool_two) >= len(self.pool_two): self.message = ' Time is not set: no valid time returned from pool two' @@ -188,7 +187,6 @@ class Sdwdate(): url_index = [] while url_index not in self.already_picked_index_pool_three: url_index = random.sample(range(self.range_pool_three), 1) - index = url_index[0] if len(self.already_picked_index_pool_three) >= len(self.pool_three): self.message = 'Time is not set: no valid time returned from pool three' @@ -207,12 +205,6 @@ class Sdwdate(): self.urls, self.returned_values = url_to_unixtime(self.url_random) - #if len(self.urls) == 0: - ### Most likely, internet connection is down. - #self.message = ('No values returned from url_to_unixtime. Internet connection might be down.') - #print(message) - #return self.error_icon, 'error' - if len(self.urls) == 0: if retrying_loop: retrying_loop = False @@ -231,7 +223,7 @@ class Sdwdate(): self.message = ('Something is wrong. sdwdate loop could not build a list or urls.\n' + 'Please report this bug') print(message) - return self.status_color[2], 'error' + return self.error_icon, 'error' if not self.general_proxy_error(self.returned_values): for i in range(len(self.urls)): From 79b3c4040c611b9bf499a71413ba6c0bd12ab85f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 29 Aug 2015 20:33:33 +0000 Subject: [PATCH 076/183] methods ordering --- usr/bin/sdwdate | 242 ++++++++++++++++++++++++------------------------ 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 017504b3..c47a0e99 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -117,6 +117,127 @@ class Sdwdate(): % (status, time_one, time_two)) return status + def build_median(self): + ''' + Get the median (not average) from the list of values. + ''' + diffs = sorted(self.pools_diff) + message = 'Pool differences, sorted: %s' % diffs + print(message) + logger.info(message) + self.median = diffs[(len(diffs) / 2)] + + def set_new_time(self): + ''' + Do not set time if diff = 0. + ''' + if self.median == 0: + message = 'Time difference = 0. Not setting time' + print(message) + logger.info(message) + return False + else: + return True + + def add_subtract_nanoseconds(self): + ''' + Could we replace this in sdwdate_loop pool_diff calcuations? + -> int(web_time) - old_unixtime + ''' + signs = ['+', '-'] + sign = randint(0, 1) + nanoseconds = randint(0, self.range_nanoseconds) + seconds = float(nanoseconds) / 1000000000 + + if sign == 0: + self.new_diff = self.median + seconds + else: + self.new_diff = self.median - seconds + + self.newdiff_nanoseconds = int(self.new_diff * 1000000000) + + #print 'nanoseconds %s' % nanoseconds + message = 'Median time difference: %s' % self.median + print(message) + logger.info(message) + message = 'Seconds to add: %s %s' % (signs[sign], seconds) + print(message) + logger.info(message) + message = 'New time difference: %s' % self.new_diff + print(message) + logger.info(message) + + def run_sclockadj(self): + ''' + Set time with sneaky_clock_adjuster. + Should we use sclockadj_debug_helper? + ''' + if self.newdiff_nanoseconds > 0: + add_subtract = "--add" + else: + add_subtract = "--subtract" + cmd = [ + "sudo", + "INLINEDIR=/var/cache/sdwdate/sclockadj", + "/usr/lib/sdwdate/sclockadj", + "--no-debug", + "--no-verbose", + "--no-systohc", + "--no-first-wait", + "--move-min", "5000000", + "--move-max", "5000000", + "--wait-min", "1000000000", + "--wait-max", "1000000000", + add_subtract, str(abs(self.newdiff_nanoseconds))] + + ## Run sclockadj in a subshell. + sclockadj = Popen(cmd) + self.sclockadj_pid = sclockadj.pid + + message = 'Running sclockaj, PID=%s' % (self.sclockadj_pid) + print(message) + logger.info(message) + + ## Running sclockadj_debug_helper, in case... + ## May be read the last line to ensure sclockadj is running. + #cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] + ### Pipe stdout in subprocess. + #helper = Popen(cmd, stdout=PIPE) + ### Read the output. + #line = helper.stdout.read() + #print line + + def kill_sclockadj(self): + ''' + ''' + cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) + call(cmd, shell=True) + + def set_time_using_date(self): + message = 'Setting time using date.' + print(message) + logger.info(message) + + old_unixtime = float('%.9f' % (time.time())) + message = 'Old unixttime: %s' % (str(old_unixtime)) + print(message) + logger.info(message) + + new_unixtime = float('%.9f' % (old_unixtime + self.new_diff)) + message = 'New unixtime %s' % (str(new_unixtime)) + print(message) + logger.info(message) + + ## Set new time. + cmd = 'sudo /bin/date --set @' + str(new_unixtime) + call(cmd, shell=True) + + def write_status(self, *args): + self.status['icon'] = args[0] + self.status['message'] = args[1] + with open(self.status_path, 'wb') as f: + pickle.dump(self.status, f) + def sdwdate_loop(self): ''' Check remotes. @@ -295,127 +416,6 @@ class Sdwdate(): retrying_loop = False return self.success_icon, 'success' - def build_median(self): - ''' - Get the median (not average) from the list of values. - ''' - diffs = sorted(self.pools_diff) - message = 'Pool differences, sorted: %s' % diffs - print(message) - logger.info(message) - self.median = diffs[(len(diffs) / 2)] - - def set_new_time(self): - ''' - Do not set time if diff = 0. - ''' - if self.median == 0: - message = 'Time difference = 0. Not setting time' - print(message) - logger.info(message) - return False - else: - return True - - def add_subtract_nanoseconds(self): - ''' - Could we replace this in sdwdate_loop pool_diff calcuations? - -> int(web_time) - old_unixtime - ''' - signs = ['+', '-'] - sign = randint(0, 1) - nanoseconds = randint(0, self.range_nanoseconds) - seconds = float(nanoseconds) / 1000000000 - - if sign == 0: - self.new_diff = self.median + seconds - else: - self.new_diff = self.median - seconds - - self.newdiff_nanoseconds = int(self.new_diff * 1000000000) - - #print 'nanoseconds %s' % nanoseconds - message = 'Median time difference: %s' % self.median - print(message) - logger.info(message) - message = 'Seconds to add: %s %s' % (signs[sign], seconds) - print(message) - logger.info(message) - message = 'New time difference: %s' % self.new_diff - print(message) - logger.info(message) - - def run_sclockadj(self): - ''' - Set time with sneaky_clock_adjuster. - Should we use sclockadj_debug_helper? - ''' - if self.newdiff_nanoseconds > 0: - add_subtract = "--add" - else: - add_subtract = "--subtract" - cmd = [ - "sudo", - "INLINEDIR=/var/cache/sdwdate/sclockadj", - "/usr/lib/sdwdate/sclockadj", - "--no-debug", - "--no-verbose", - "--no-systohc", - "--no-first-wait", - "--move-min", "5000000", - "--move-max", "5000000", - "--wait-min", "1000000000", - "--wait-max", "1000000000", - add_subtract, str(abs(self.newdiff_nanoseconds))] - - ## Run sclockadj in a subshell. - sclockadj = Popen(cmd) - self.sclockadj_pid = sclockadj.pid - - message = 'Running sclockaj, PID=%s' % (self.sclockadj_pid) - print(message) - logger.info(message) - - ## Running sclockadj_debug_helper, in case... - ## May be read the last line to ensure sclockadj is running. - #cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] - ### Pipe stdout in subprocess. - #helper = Popen(cmd, stdout=PIPE) - ### Read the output. - #line = helper.stdout.read() - #print line - - def kill_sclockadj(self): - ''' - ''' - cmd = 'sudo /usr/lib/sdwdate/sclockadj_kill_helper ' + str(self.sclockadj_pid) - call(cmd, shell=True) - - def set_time_using_date(self): - message = 'Setting time using date.' - print(message) - logger.info(message) - - old_unixtime = float('%.9f' % (time.time())) - message = 'Old unixttime: %s' % (str(old_unixtime)) - print(message) - logger.info(message) - - new_unixtime = float('%.9f' % (old_unixtime + self.new_diff)) - message = 'New unixtime %s' % (str(new_unixtime)) - print(message) - logger.info(message) - - ## Set new time. - cmd = 'sudo /bin/date --set @' + str(new_unixtime) - call(cmd, shell=True) - - def write_status(self, *args): - self.status['icon'] = args[0] - self.status['message'] = args[1] - with open(self.status_path, 'wb') as f: - pickle.dump(self.status, f) - def signal_sigterm_handler(): # Inform sdwdate-gui From 2018294a1eb053381ca2c42875dcd4feff9608e4 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 30 Aug 2015 14:04:49 +0000 Subject: [PATCH 077/183] if __name__ == "__main__": in config.py --- usr/lib/python2.7/dist-packages/sdwdate/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 9b58ebbc..49d921e3 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -108,3 +108,6 @@ def read_pools(): pool_three_sorted = list(set(pool_three_sorted)) return(pool_one_sorted, pool_two_sorted, pool_three_sorted) + +if __name__ == "__main__": + read_pools() From 9ec3d298543bc93634d0176bc8a37a065352d056 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 30 Aug 2015 15:58:10 +0000 Subject: [PATCH 078/183] fix: sdwdate_loop started upon receiving SIGTERM. replace gevent signal with standard library signal. --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index c47a0e99..83d3b962 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -3,7 +3,7 @@ import sys import logging import signal -import gevent +import signal import os import time import random @@ -417,7 +417,7 @@ class Sdwdate(): return self.success_icon, 'success' -def signal_sigterm_handler(): +def signal_sigterm_handler(signum, frame): # Inform sdwdate-gui icon = sdwdate.error_icon message = 'sdwdate stopped, signal SIGTERM received' @@ -430,7 +430,7 @@ def signal_sigterm_handler(): sys.exit(143) if __name__ == "__main__": - gevent.signal(signal.SIGTERM, signal_sigterm_handler) + signal.signal(signal.SIGTERM, signal_sigterm_handler) logger = logging.getLogger('sdwdate_log') logger.setLevel(logging.INFO) From 05bfa5f28dc372b9c5237f6d44937f955b5da1d4 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 30 Aug 2015 18:24:35 +0000 Subject: [PATCH 079/183] fix: error if tor stops during an acquisition cycle. --- usr/bin/sdwdate | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 83d3b962..b426bec3 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -3,7 +3,6 @@ import sys import logging import signal -import signal import os import time import random @@ -82,10 +81,15 @@ class Sdwdate(): ''' This error occurs (at least) when Tor is not running. ''' - if (pools[0] == 'Connection closed unexpectedly' and - pools[1] == 'Connection closed unexpectedly' and - pools[2] == 'Connection closed unexpectedly'): - return True + try: + if (pools[0] == 'Connection closed unexpectedly' and + pools[1] == 'Connection closed unexpectedly' and + pools[2] == 'Connection closed unexpectedly'): + return True + # If tor is stopping during a cycle, some urls are not returned, + # raising an error. + except IndexError: + return True return False def check_remote(self, remote, value): From 6c233f822760ca8b16bc61e6b8fcabda83094071 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 3 Sep 2015 19:48:03 +0000 Subject: [PATCH 080/183] sleep 0.2 second before start to allow sufficient time betwwen status changes. --- usr/bin/sdwdate | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index b426bec3..f2e05b3a 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -434,6 +434,10 @@ def signal_sigterm_handler(signum, frame): sys.exit(143) if __name__ == "__main__": + ## When restarted from sdwdate-gui, allow sufficient time betwwen + ## status changes. See related comment in sdwdate-gui. + time.sleep(0.2) + signal.signal(signal.SIGTERM, signal_sigterm_handler) logger = logging.getLogger('sdwdate_log') From 6386ed14cc0b04b9571de79ed83cc39071acb6ba Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 09:03:57 +0000 Subject: [PATCH 081/183] HTML --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index f2e05b3a..8440c95d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -471,7 +471,7 @@ if __name__ == "__main__": sdwdate.add_subtract_nanoseconds() sdwdate.set_time_using_date() - message = sdwdate.message + '\nSleeping.' + message = sdwdate.message + '
    Sleeping.' sdwdate.write_status(icon, message) sleep_time = 10 From aab921241a020b3b122e523540aa858abe1c3058 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 09:05:49 +0000 Subject: [PATCH 082/183] typos --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 8440c95d..78eef12e 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -245,7 +245,7 @@ class Sdwdate(): def sdwdate_loop(self): ''' Check remotes. - Pick a random url in eaxh pool, check the returned value. + Pick a random url in each pool, check the returned value. Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' @@ -370,7 +370,7 @@ class Sdwdate(): self.pool_one_done = self.url_random_pool_one[i] in self.valid_urls if self.pool_one_done: valid_url = self.url_random_pool_one[i] - ## Values are teturned randomly. Get the index of the url. + ## Values are returned randomly. Get the index of the url. index = self.valid_urls.index(valid_url) ## Pool matching web time. web_time = self.unixtimes[index] @@ -434,7 +434,7 @@ def signal_sigterm_handler(signum, frame): sys.exit(143) if __name__ == "__main__": - ## When restarted from sdwdate-gui, allow sufficient time betwwen + ## When restarted from sdwdate-gui, allow sufficient time between ## status changes. See related comment in sdwdate-gui. time.sleep(0.2) From 98f59c77f0b3bc528c60a25a1f5b9a0ad6d439e9 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 09:28:35 +0000 Subject: [PATCH 083/183] improve logic in sdwdate main loop --- usr/bin/sdwdate | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 78eef12e..2a72e190 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -455,22 +455,18 @@ if __name__ == "__main__": if status == 'success': sdwdate.build_median() - - if sdwdate.success: - if sdwdate.set_new_time(): - sdwdate.add_subtract_nanoseconds() + sdwdate.add_subtract_nanoseconds() + if sdwdate.set_new_time(): + if sdwdate.success: sdwdate.run_sclockadj() - else: - if not sdwdate.first_success: - f = open(sdwdate.first_success_path, 'w') - f.close() - f = open(sdwdate.success_path, 'w') - f.close() - - if sdwdate.set_new_time(): - sdwdate.add_subtract_nanoseconds() + else: sdwdate.set_time_using_date() + if not sdwdate.first_success: + f = open(sdwdate.first_success_path, 'w') + f.close() + f = open(sdwdate.success_path, 'w') + f.close() message = sdwdate.message + '
    Sleeping.' sdwdate.write_status(icon, message) sleep_time = 10 From 2a5eb50b2ae144a9cd4a74ad2cf9929939d1094f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 15:00:06 +0000 Subject: [PATCH 084/183] timesanitycheck expiration time --- usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py index f42df687..9f541321 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py @@ -19,7 +19,7 @@ def timesanitycheck(): build_time = time.strftime('%a %b %d %H:%M:%S UTC %Y', time.gmtime(os.path.getmtime(build_timestamp_file))) build_unixtime = time.mktime(datetime.strptime(build_time, '%a %b %d %H:%M:%S UTC %Y').timetuple()) - expiration_unixtime = 1409936800 + expiration_unixtime = 1999936800 expiration_time = datetime.strftime(datetime.fromtimestamp(expiration_unixtime), '%a %b %d %H:%M:%S UTC %Y') if current_unixtime < build_unixtime: From c0cfee6efd55cada8187b3327899cba6795e1f58 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 15:25:58 +0000 Subject: [PATCH 085/183] HTNL --- usr/bin/sdwdate | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 2a72e190..cf1ea94d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -112,12 +112,12 @@ class Sdwdate(): status, time_one, time_two = timesanitycheck() if status == 'sane': - self.message = 'The clock is %s. Current time "%s"' % (status, time_one) + self.message = 'The clock is %s
    Current time "%s"' % (status, time_one) elif status == 'slow': - self.message = ('The clock is %s. Current time "%s" is less than the build timestamp "%s"' + self.message = ('The clock is %s.
    Current time "%s" is less than the build timestamp "%s"' % (status, time_one, time_two)) elif status == 'fast': - self.message = ('The clock is %s. Current time "%s" is greater than the expiration timestamp "%s"' + self.message = ('The clock is %s.
    Current time "%s" is greater than the expiration timestamp "%s"' % (status, time_one, time_two)) return status @@ -333,11 +333,11 @@ class Sdwdate(): if len(self.urls) == 0: if retrying_loop: retrying_loop = False - self.message = 'No values returned from url_to_unixtime. Internet connection might be down.' + self.message = 'No values returned from url_to_unixtime.
    Internet connection might be down.' return self.error_icon, 'error' else: retrying_loop = True - self.message = 'No values returned from url_to_unixtime. Retrying...' + self.message = 'No values returned from url_to_unixtime.
    Retrying...' return self.busy_icon, 'retry' message = 'Returned urls "%s"' % (self.urls) @@ -345,8 +345,8 @@ class Sdwdate(): logger.info(message) else: - self.message = ('Something is wrong. sdwdate loop could not build a list or urls.\n' + - 'Please report this bug') + self.message = ('Something is wrong. sdwdate loop could not build a list or urls.
    ' + ' Please report this bug') print(message) return self.error_icon, 'error' From 1173ed1812188b8e60b88163a0bd39d933c035b0 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 5 Sep 2015 18:48:10 +0000 Subject: [PATCH 086/183] remove usr/share/icons/anon-icon-pack/ --- .../anon-icon-pack/212px-Timeblock.svg.png | Bin 15843 -> 0 bytes .../620px-Ambox_outdated.svg.png | Bin 120421 -> 0 bytes .../anon-icon-pack/Ambox_currentevent.svg.png | Bin 61903 -> 0 bytes .../icons/anon-icon-pack/IconApproved.png | Bin 125857 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png delete mode 100644 usr/share/icons/anon-icon-pack/620px-Ambox_outdated.svg.png delete mode 100644 usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png delete mode 100644 usr/share/icons/anon-icon-pack/IconApproved.png diff --git a/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png b/usr/share/icons/anon-icon-pack/212px-Timeblock.svg.png deleted file mode 100644 index c1ffa34d3447dcc35ecaa3da5e24d5342b9ff069..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15843 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

      |szeP)>9Pi0?2Vza#}FU;)Hm5_{XR?q4XjY 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 Mpj2%!^V^#3*xWB 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 diff --git a/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png b/usr/share/icons/anon-icon-pack/Ambox_currentevent.svg.png deleted file mode 100644 index 8fd55443a01d078f6bc2b23610ecf0b017d3204e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61903 zcmV*kKuf=gP))YEUBIt3+^P(leMlu$wmC6rJ?2_=+JLJ5aG#7o_Q5@rY1f3ts0QcwvA zbScYBQ>#sC8d1a06MpnqLdF2lj6qXqLjahu@qHhwd>kRnm$L6Al<;yziJ)J`Sb6nu zqu&@iSqg1ELR&60F&`nqLKo#i$S|4~KnO$Ag_f!zZGWHKymy9%T0#jgOOy!uWq?)R=z5jzGhVL=alG(}Bhf^a zF!VB^YZ@{|qV^A3DoA+3ZuyhnE|o6%>4 zpl1QrUDJEM@EPwFKJ7#;U{vK~Eb=H(wH!XY)pJe-g?ZE@GHU9N$E5!AJ$)5FE2--e zUXCac^h<=b*Y>_%GxSeuA^!=&r~C6O(P9WKYtJEPWvH3GF{tXB@}w&;mVAASNi7kP z`jb(qe{_Lae-|<2b(&x>5v;Gqj))&J+|#2NadsC`vugKx!jn znugRyKE?0S=W$a+BT3_-al}{KKT-X6m%t^Ia9E&3&@+tXH*}SUlD;pBkpDeez*mzx z;>9S@X;ROgQyXsfDXQm*u}rsm&SxK$`bfmoemfe_uQ>4G${uwiC6q9GQ6lIW!kTNk zPS$;auW4oeANeo?req2B%{z5mO%+mc|Xc`l~jUZY-8C+fgFuX~3yJ zmcgOnp{w;W|NFE+I=4J^#aWbkewj_Hb19g%v5cE~Y*^~Q9Z%|?*mp@)pG$)!l<<fs_*u+`K0egtvv8=LbEK2o76)`eQV2|u`in@} z<4G_l`jJdZ;-(~FO3WlksaP{<7O7{aT>x<@%pp^MAgXg=X>N51vkfJJo&;82J>2LE z^9!TWe+HT+3X^cL_9B&~LOlyif*OQ1C^MiUAgB&$REIR`LI!044WBMDO>r9VFdAn(nzY24qAW_B#VtYT@ALwSWzk2Z!Qx zkHi@pb56D>K+ZX3D)sE`_{Y!_{Ym|X9iObbr1WBf5)L&=1nq*=*9@F#l>2|Kh5b$G z?`-kpoU`Muk}Y;0lE#2!aka)V%~^$Pt?3MtkEB)QDw0lbPMW0M$X-R#QqdL`rERG* zo{+rQ8{@!Wlz~VbGifRY>FxOESI_RBO9W$Z(9|y6eR0FDa-B*jVP;SwXa`n(x2Mez zq2K8h{$mls_MLT4Z8!{*P|t>xv<6L%sW({J=0_K~gr^Es3{kt0v@_0hle8^{%SED) zB2U{)6Lt;6*x55q|8N}BG%X&AL0fjbq&9NkIKTt=wu}hx>=>AW#mXPB&N(Ad!C$8V@G�S=coq1vM)JGmq`+J5o+2=n$|iQAGcVJeyGab+OzrQ zQOWs8I;Z~=|2l@_ywEvH-%t!`n$xPDZ373ALdf2v|JLn8_3tb_I$pwb;}uKLtH0g9 zS}za$C99g#m|pc96p|HHCZ{e8Qs%cm)8Z=76x&W;CmH7RpD~hF#LP$1ImB-5zlD%h zwj2^ysAy*&;-+L%#~AJ1qa@-<$H*NGIZ@X*JZw_jRcTH>^XX~Qd$px0hM ze7#;9{Fo4$>eVGvRL`l6DlN%LEjksDu){;T1#BYi=C=h*2KAKJ`{?1)CL8&jlE^QcLohIT~x{ z_?2#M9QPWD2p7>9ye5aG79p!E$ZL2RGZCGs{`=Ba|#)kOm0~;6_+YdlE zyqdcDOVDE{V1pY$7rAbA9AWmFfiLd7v|*!5#S%&=h*tzbula7|R$pcCttm;X{z5tJ zUe$95ljED7 zt-;z@h3|2_Jh(7rQLvYbz0i$8A124w!|99s=+0Dj0XUVR%I@4$-XzRduHX(8sTM`j zj>F+b!a4m@C}6jCoPBa0D^;J}ZWvnY{_msbvZpI`|E_+eNwD5}WHiLkS!f-XA>wn% zK~d^C_naBV=pKq_$L+bQd6!F|5=v0QMe`m0Sbgo#iSe4?-Yhv!AkTL#4cE0ACd!Ts zC%JT;pVuu3U}yp{VORZgiy{gUZhl3uis6 zNn~x#zgWtJ&8V;mhrczB$Z^Rjb1es|!wWLo(sX_@CT8WINi04Kv*9|>#wiRp^-PYv zf>(}a%f2}~-_Ox}uoO2L2|jX+;N7c( zR0KqZG;RN|lS4kTb`oq(eFss4n&fP;6vR3$B(11tBV%DNclucSq_XAstJ+bpzwKh@ z9IU0RDttu7mQ?Vs0MN!sHeN?!$!VC?x05<&+)^H%jesP8>1#DdZ(H)M&Y!qAl~6(s zjKVI57uMZ4e7ja%_9iEhsZk)cF#*3YIlTp5bwsdmNQIhHeQ%wnw69DPjrFI0nyjSV zIn5o`p-NIjmMNRq7M76Kwn&$WZ2TO*iJeTjrJi#vX+m`NEgjz)OJeU=y1%BGq(8EU z@z@{$x*npoVhNhiZEZU1I@`CF>sr8mkn%tp-6Y#C$E>~`{lG>>7 z%JKK;-|Rec%O#B`m8RmAPzauA?6AN_X{>1-ecV?OTAf>1Kt1OWUDm!JnB;Snl2$9UKRws?&j_m;j4UJ3;F8Q9^P)|Sk-eGCg&~` ztZ4QX)fefqUw}!BBB_*9mg;E zIHw=;WdtIZkQL*w_Vey%KFX%{p8-&R41^j@Zv;JI6XT1~_kR^_Y?b?m>5-h%_K5I} z4EV%`-50f%-i26#5{ErOue)wwk*EkisfCOx?_Zn=yXB9SoEHR>ynBV8pl^B(j~l7F zb<>qNoIV~&I;YG7Nqbpm1Bai=*V(6$Xb%r<^oG zIY`RyXFk6?U2`L9FKSwq@|YA!EBaxJFBNrEaVF|SV!b@D5wI>z*eEZM1` zH;0)06;Rm<<;|d_Gf9>XSlb=iC+K_8-oGWy|Hes2UP^1tamuzOu>Ig&{OQT}5KoQ* zP_s-@-fC~b3rRagut?UT@B13s@F|m4&(@s;LU!q)_w2g3>F26wCA>0mSQ7NQ>jux( zYRmp03_ZoB#XjX_`2(=3BEj2N1kh0&5U)o_(N^9sxyr8SvMOz1-FAB%dqzoG=Jq*9 z$x4$)cl?yQp8Y(dF-wj@Kv_N1EPz0zdTeeaJ=QPi*&<1d+CP-~iX&Oj@EV$`*HRv8 zMoLM~@D_G<{)Js#*~R68HBi4=I;JX4PDQ8w2=v~6M?ZKuLi%$&y{KoaEh#kFLGZ%e zmp0w3XugD3APzf%UUhx{d;N8lKNQ-;<9kzh%FB<*TNlFW^o>Bpyy3}0@%>X0tg@c7 z>jl)bD@j8R!MhQ=8%ZnGI(CwF&Z^ko|2(%n@k#dd*wd=zjZnJ~eC5tjJI3Zh&;Zhe zf!%_kc8Qtv{z>^Op>efDGn_HiD%uM{r#Hp;k@)u9fQZe_6c(eNt(zv$N$AR*mp6Yk z-*`%RW#F(P=(RTteNe9{yE#M7FlqH%r6u{`F$Rql+95dzRswTux|4$oQ7io&O4b^u zAW1v@o#l$QZNxtDcRUf{?&rS9KepaPQf8N)D650IrKuZ#s`2KG+tF#2e1nSFgDNN2#NIf^|ef;OTdO~iqG^U`WRVqvAqxin&BVMafrKXii*P(K) zzFtV$)=x#dR;9hK_ZfcrkN40syd%?&5rF!oP~Joirh}x@ws-`_2O&N}<}q$zK%hzz ztWDj7bOxDj6`k5JFgh;B=zfoTq=KqCwP6AsqWrVFE^GXXq8lZ=LU33R^y+H||IMf` z|C5&IwN#$$m<$~JJR`l=v#4<$uU>osCoFy|mBIS#qrwgpbAa-aj!U76guM{8>#B7{ zlJsO|c36E(Q}V!zH}R*ZKSv@N&DbmFK*M4bDFyd&D(!NT3fDEB%FlZng=2v>a1Oq` zUz*5Edmv}qZq{xBoucf*otHJ;r07}+uK>(?f?j*W;On*O@;}?j85i~J@{K_v%~5*y zzK`+YyKE)8=40Lbvp9G4r7Uh-mm9L!BjonIv+gUp?j+%2)U=A+RX6g|cB`)^l2(*U zU^LdxFCV^;=l9)_DK~=9d<=xD75!JBFrj)=(VlI2RP9n8g6Mqh3DTbue+Z{t&x#{oc(HCjY_?6W|({j*V z9=@C^Nxu{-da?v90TG>tfA`I3(Ibjh&)Ho`0!?=6p$m3j+WZ?uQzg8NF>48W-E{+t zL{)f`7Vzb+P*lu~L46PYn)4Bc{b}0KUKlj&r{rZ&v`_my+CS#qxz*#Gl$edMXGTt`j^jU zv(J!<_CnAqTVb-#?u%G6f!#MF#+It1c~sRg^klbQ@v7aI);*%=VhJx(%u0ej;ah{X zvD)AsJ?PJ!kX6KuL1ApgS9>l(JCNg@9(Xbi1MM)l56pPB+;4>V*l9O$;-a@qY7CQ6 zaZaMnr>1jAnp{#(Z+ju>9CfWy*}#hj@8^5>y`Ay+2mpo;TGv9bQqgw>X1j`>9TjR}Tjpl@1=& zv*vpOqvp*BZ6L>6HE1)DBI*8JU?wO0gy3VR-oz)f{zskN*q zl9qOx%?F2%Bla`xf8s|E{zv9!pg#n2)`DSKb$iB0`XyA+Qzd8s%pm^V*P{=gGt2q}<8}!_+^~>c8LfhKZ z!xfNP7W398zvfD?PGZZDp_Q~1?Fq%UlZ2fFYMlzGBH+xv%RKH(TP`H6vTL~1rdnh&-f zd?aIcRnFugZ5Oh361CN+Qqwji?C5(TlC~1Oa8y>R?unh@u`X`9?}E%iN1-ZcTO-g6 zr`|4}q`hM16!XkN*_MbCoqs9G>i@&jN$r9R1h7MK+Tz51HL4SH?) zHSP+juzJ?~4`I~46(NQur_LO9>#Xa98OtZ$Qgu|sjXPO8+jIKmRntY1w6)wy_SOr5 zvoC4<w8GpI?`_0$h_lxZb)5hr@YB@-lVfd}<$ zMT0qd*%4QFoI9OeD`9rwB`4_RH*}Tj)xmpm9z?N1$jR% zHmUla>rEHyDt>_!ZZJu7{^ghz52beVifMJBE=}sp(OX-;**n)$KTA05@RAd>-&gja zaynfG`F{oqeJe)ITR;p?xi4a(x$dnnkP?k(LgS4qKdMY$o=ON=~Lx@q|G#7p|2uDN08tG%piwAEBjQoir8o zoVNAtf&RT&`bLtAauPRkxBVnPg>m0L{q^+C(-0@2WMpN_0 zgOyM-A0>(<>HIM~R`K@AcCXEQ_L&R{#?{7%F1QFYu-C=Gg`q9xN*QZjdi8-{PNI7y z%yPVB1ikv2fisPovMchQ!TF}uiE;Gm3lPTc)UL{~5wzZ?EZ?!$L<5!tD^$S3AcHYs)+}3B0VlFzUOVpVn zacT53zVM?+-SI2X_0~&nL7h4F_Jv>Xc;EE*u7sBoGj-18#p3IFE32z2I+c%cXOR2w zN!Bhgs@{(lyp>{a8s)Q%_QAf1Uz>AkmU8_CPa=w(f2(>3#cfyh(23Qa?R@%d>dXMq0fx<8zG^nai!eoDKMv~($#!tP{M z>jL{Qg-Ct(()RQ|7T-)AL1s~SDcoZVOS!dww*F3(r~QVcdyQnpBShz4L16bca6QxV z{HpZJq+Vep9tPTq>QV`_7Kc2=vG#_+i}k9|s{CH=)9SRquhGNT=FLM+hMdQTAd*UD z6Y`bunq}{_<%Vrf3M&4orr6CQPNf)i%4@Sdua(`jb*LhP;mt1fm%kQ zeMBP=jo3esg$>8CdfppZ-gYKUmCMjHourxM;NTYSdhTmHz2|lSy0<|^GYD<+Dq7{9 z9lLNwYk{UoQ`~=J#^RJ!=Q>*S-q0E}N`WsYNI9+60Vr!+~c~yCHu$KX~_MDuT6n z>lcFS7t)#!b~xwQvFDVX`DPS0&+idLk&m|c|N42aX38{8=hy}3^STw6(o%I) zPW^N}X_{Pp*QvDkrjq${*QFi}SB=*>R@Ht%+ic21yX2r%aSO#D87BPVuMy)5vjvL4 z!o?oJ7>X@g(D1LfiI}20B^)L=WGRldHw=HNFgfGGYzg$r52eW20x!f_h_FpFHSal% zx2(CCiePO{`XNttLz%8nr7zM2&WBc1X^)J%!`JN*ZmYl3?&|C8pJnIVHJ_udd>#Nt z)UW4rZ+MXRz4`~VRv(qqH(TF@#)>&-WqhKlr|$E7aFb1aOqb7;;5gn>iGe=K*!)i+ zwTYaPlAP6ckdzUxjPKn4)2Vc&gqI$NgrJYTdSHQG8Tw2?N0*AbZLM0wo7aBQ6X~YOQuRo8A@WWK%OO)YqPDbS9jKFdRpqK*7*0Q1*+iUB zppuWD@xPq6_VaxD?7uO$c4f{*LJ0x7=65UdWb=aT7^d-jID~b)#4y8Kh|Rky&yB%6 zi4zHRa_sF#e5K>KY3N=FFA)w&4mw%szg_p~1uh0Ixx~O<(8AZ}+C8*eD77&<00X-x zc7@=>r+gE?v;-yem^~_-sA2H{cf2y=dBzX-nDYO!dTj+<48M=tC!9^uEav z!6g-FG3wG36=M80V9unwTEa_;8CTG2t{Zw^;aMBF;+<4eR7+N$WE?s- zWt)B7^7pZB-WeG`+l&oGoV@DT;;2&DDC!hUO<4S#DmF)LJNqMY+qUlH^efN7^rE7- zbv}^U4p!Me*eNUKDf&zYW=b);Av*Ufn1MasD_9PBm5jD6`kw<|Dx$d(W)WtTpch}) zTWM5>zFXK+eOtw-d>?4tIVDs5vP$6Kv-Z@+2TuBmotPc5GC%*_*jYiw&LD0?opG_& zO~-T@PhOI?#8cJH@y~M+$`E+I18V8#?(RqF9!^b(@rA$_a=m=n+M1Jbf4BeuAOJ~3 zK~xfoc=UTRZ5?Vtc0y`nj4!whM0_GvVfAc9T{7~S`Cr_H@;t?n0xoQiHfc`%z)^-RMIZz0zF z!#v}0n?fS-lhn$5+&*KSD`CbkW9RS6>jsYx)K~md z`FsZVL+1-w`^Bf<#2H7w%kGy)dFE{-|2-S!OsTwY@3#6YrsvtFxjCof0f}Uc`(OMe zPwl>&&Y>OjjUFWEtDv!BJ}c+Gmi0%xk><)J>UfqmZQG7^tS^~8IJlKB{pnatGYJ|j zK1pB%tz%MbO>xTIbWFFCiXCT9i=Ap$xK81zcDZBG2)+12jNx@TC2rI3GX0luKvbQ- z>&lk@Re6;#J27Jfz3zKsTlLD|(t?gTcb`o3n%ANE{s~Z!po<~w;V$TSKHKEEt3Jwy zPX4;xDV0lXe8)tA*~a(`zeexKKG%K&V?fVOmdd$0dS@wuIX)Zr0s`vByn2e)cm9)$jW(VOBz)iJ+^$GxQ!`b=mt1 zGMZv)fENA^dgv!WVRBa4+(-Mw~&eVXH6i3EyqJB;) z!I^6iIlI|yM}8w`yw3aw@SS@<#P6Q^I%BaR#nG^%b7&_IZuu@5#F@+N`k%$Tg-fEj=jCl^FMcSqkrq}G=xM18_ zqlDRqX;;u|Zy37Ts0^Q8&{1*5>s9}OFj5I(J7IbwXcdtTcfi4aL3*a%kXVuejD!3_UH^0ob#gNm!KzZX$b<@wf=@gG4e+5;cM*g|ucK`&-p{6zEr0J<; zK`R>s_45QmgRvnok)|-&-t`y{Zo7r*a5Hmjmn+9(?f?G%&0PD}a~O-I-lAU7AZS{e zdq9fL=TF^rN_A!;MxltF+YlznvQ7AV&P&zupq|q(Fpa1`w5<1U-@4Unb0xfVnAY>| z{EgDTdtqX@@XU=|^{fT{fL`^^+>T8)y&<7*7xeBT)7Gjvr|`M6e?!<`t|mv9xH-0b zM=g&UwAymiwBwNE+_1xu|8?Yxt#yXRdik$Eyq>-Nsf%6n5<%kv;rg|WN12MhZu?W0;wr=3yvrA-a>bU=&quRQmQ#I)C#(| z7^-q_%DgxQow7_NMzH-#{M~Q&te(?|qnTZ0)vdce+0>cSr4nWprj?-A+&J`oUv0$) z3OYJ+&q72n>ehgkU7vC)=?yaho&QQLf|SN_3(n>nI> z4Spj`=im;W+Ve*?AGjyu;|mGqt&{je*`svG2^u`7ISLV@s+Y5kph=cfvFVS9cwMeX zK2y`6gpVOrhW)-H5pqJm!RUWRY?H7h#;PLTX^{khF0WExWZl{V^wJyCM z9fE_KAU>KcJA1_k`S8hCqfgwmQoXOw8PkoJy&f>>+|C`gnxv)uOPVHNrMkBG_C^kH z%cEc5{;j{ll&O4{#S`_r+>v}6|YO8t+60~AkonHAqwBT>mo${F880~?cZ4k38 zZCPL5#wBNbpOte?&ROTed2_8GITv%o3-@1_IcW-V?I?Uy520-7c#J!r{{jEl{=3ZV z2>>*xpCf2qBG3)@z9{=?v&&4|<3p0jpdcDaT`+_u3D*k3HKbA}lS)sxtLUN>b50Cz zYBzrjRe9mL(8d26n7V?NfHAzDvaLUGadPX7Bq21p-wZF=d)2%hlrSqXEeg8A@LlgI zwc&v9J%tvuKiH&7Z@3{klzJ1&Xpi0g>LqXGqo-a&MWDLiYzQYg=a9By)`WB03h5|I zD`6nJf}B+)<51ETqt#zZ=o#71Lpy%Y{ab&{_O7RM+OBF8v@8__%Iy8L9eInvi)s`C zLRErbl`}{&in%zS>8SIP;t+Q@QSk)vst525p6r>N0Ssx1GF^Tjc+JfBvV_TC$_o1Y zjncn+Q6eIIM)B&o<}5VdgX#|DnY}UA4gGsyY`|`>D{tfdCw-MyFL`sOu5#Uq38KzR z&I)3l)J;O?wu>^h{hioIN|W8)|Ki#G_w$eKzh_IwW10MNrf&i&o1kHyMp@yR=gJ*- zb?MZ5h2wcwuHF^2m$-!#ba63M<+I~yLbrW=d?DqVZUw2Uy4K!<#ii%1`2=rW zcL_mXDv*}KGc8SY{UlfaY}^oF0r21U?Unb%37p1XIvmgwj|Gy9qkj9`j*geEaf z0SSnWLek}}YGHpRuU-CL&RO|U>dV_~#9EYkw%Q7jv+#D%WadAI%~H0t<4HdL+tW#! z2@es$@Ih6Rpt>2#YlUkTC?7G+n2PqCcU5ZVNh?R?{xnUsE2MUdk1=x!8bBMLN5!Vw zCsefR15uxlQK2P9mmXPGdVk^U!;}>C+8c&GSdg3*r}g;oUu@(IV8$U9PbuX>6u78y z4W}%5J7*mA9>Rf&9AZ_Gs&z9bwc+CKmcRCVRTFli&g7Z_Dd2^U$Mcf25D=~sRMbI5 zogiH8DK&3X?y9osRMG0dhaRsq#jJuEY$sO#2mIaVWsM#VN$gl?&8FZ0(!lZ|;tJYQyR3_V~3cWUjANko3wqCn1DDP80^ppteQe52v;RVS$H< zi>`1ZEtTEfmY@1w5jXt~BSf2jgumw;M=ly%f6h&8l$&Gkr9{xP4wFuCth}b@xL{N5 zBY7pABDE16xrbBg9_O4@m!oUCE&VV!)=6JvKcn$M%Kg=p2db$FHxuxMlrt>Sq?(h? zaPFtK74)AMi630cBRhDqx) zBNY19WR`e*O=}}>Jo?khvg&XXHQ^>@p4bT+$>wWNrJAvmvWsip_XN7tkrtawJIHJd zr;>i}iT}mWSRZ#h|04hnZcjNhEfSuto>m_>vMx?B&rZzdzRfm7n}3>8(Rq_yxd@rb zPXQ%@o<*3Hf?l`rKv-McI4q1jZ|YX4=P;vOb$nS4xmp#jO%-FeJ5{pH&$4YJRJWLp zGFx0lY#ep1aQhla>)$*|+LV%S|Lr2~-1L)-y=`e~fumyK_Enrt6`lVEod+L9w8^V* z#r`WsK?~yXCgRcgBoj>}<8>sHbtDsY(03*>-Z)W5V4{&uSfy&FECSFmG0Y@7Cf!>2 z^CtQhAcYeI1G8P}r;gFBv2JrTBlo5UA_n&#pTkX30)3Gs1BA_hVT{AChN z7fRo$U?$XUNik=UG%JXWEh9R9G~;8-iH|QJ5pN|Pn};-msMaaW{e>vq&soXD+eszB zf2V93y40xn+Tb`}9oQvcFR)!mo-@tl^8ugve9sjP!_zs=5^^y)1^vUZF1;++;Od}I z&jFHrc7u*j7dDbpD%@ffw8O3}6ZXM6r`9>!!%=2y7mGnT{@hKO-c~5-beSnlzWIM2 z5{Y&>GS4BN-hzjbgP0phi^>h{aedSd1k9+s0y zq#E8XB@YWB9@b3r;r@8dW;O~_5u$`iqG*C%d*i@5qo(o+_x!U|euLLk#yJ0|K<6)vhgOp0dn{9VrjR5wLBjNLoqQKr#{M^4rhhxqYc8EUTLY3s>jKB#|QF z{tijc9*7Kj)15-3-n7!XOe3>;w?~3@_bC)ZwXfK6#7M>=I-Hs-AE@xshr^4Y4)5oIo;}nz^b}#txIKdUjm#rcC0HIj(s~d=McY5J~RD;LiR}*S0HMODF=x z6ZCgS{%X{eo#E=BP|pI%rN=;RnO2yf^AKjy1g#=}w|29gb-z%M-Epez?D*}3>?B~T zU8SM{x`+1j-k+^yJRU&^IObFxpUW!{Mu#MOHc7^YbB}x2UqM^VGD5y`63H0*2DUOZ zW?#6(@Il*hjjASGNhM`FbObFE5b1@Hp44CK>`Px5Dw?6Z34D&HPY)l07K}#LGc@>m zhKJ4|I(9Tpcp+z3;`K_-nGO}Lp4C20c z#uNoP1GE@%E?SqWGTR9{uXXg?w+MB4eZ;MIS zp6qe2EoR<1r`e z_`@i%`&pB|gQ)-)9D60_ue%&g^V!=bOVH`IcK1BbjrU#1uAXNyv1;cC+Kv+0^{I2l zH8TV)Wr~=4cctEj<3v;Cku+2u$-#l==^fc+D-$|290?Vz(@)T<-wegZh{YS}@Be@F z^}d5xba74%m8P7mdM=8bGrlvXo)vzXI9ny+hd$yz>-uczwOjd66hW`MDe{nB9X?4} z=%SweB)MvXL3+-|VN1}$?t{gi{$u@eI=7vwIlaGsa2I!Px`jt}-ObLPO*xN#*E9(h ztZ~mlZ~dp4$vc1dT~E_iwZulm%C;xA6J~;2{`pz{{CxI>3DwOocZHU7X3P{p$0Cx0 zn_%4Wnyn-1kK@=y@8+nM*HaN{%JhA2?-M+=`!_tk^Jl~o6AOR~Xj%bfjp@KIH9-r8 z22Z26_q_}aor#o2x|{YI3iX`hLlGpNQ*|2DvsV(&L(n##i9ZvzIBkXM0Zy9dToa%GBt`1_mj=!YjT2IkU2xxFdwA#F zT=mCy5wpGt1@QS(izAg)rxr<Iq#4Vc!8sdU@!TZKt4#38h`64tdMkPZKc8`1wCS z13-7X#0W$EyqWtTMtV}@oW59;1?o8e*l)9X-kYu5k#jnvhgKPC;Xhvezg+W|FNFe@pyx`(^pB(3Y$kiFR+tyo;DSJlSNK^H8~^T z7%Als2Zn0C-0-EI8xn?gegCIx2dCbH!yBFn+VJ^5sV;H<6stvoITbF`4^&|e17da} z-wB?6IK`U(7#i#2zyJK+3_0rtRJRBk<_N--!WJOk;YcRn;EwElmeZHLWAcLl&R%&D zeUXFw`MK)==-4U=l?%#hrxaF+=m;EmHcQT}HOFw_X}41uZgI<$*^oXiy5=Y3x4fm` z5dT0+^LC_!_UB9%pP=CjOgVH0 zrU?h0lb93NmN~VnxaiC~3Hqw?S4_il(c5z1ZrXbu1yb)uoJsX;g=vQA>iGbjT^Er^ zOk5!He(;XehQh;ZYNIexJ^@2e=@*&y!aiXsmW;)-6DNmbl4!y~#$K+Jmi|oZg-L=5 zHAucB4DI69FZW!ng~qPicgb9r*B8Hh;fbJEUo&u~a%x?k4^aTex2iKDFH4B5l5zW4 z4p1Jb&TK~`lE?s5HRgG0-huH5?0Qx*I+Tj)(}P@e+VyBco3en^gvN!Z+{D3wZM64o zA`yf3XH6EZ*CrkXo;rGWLUiKZUS+6>3tw|PAzzhy-z~pBtmqlp!T&ybVP?N-9*5vU zFX$h56MGMABp#bHVe>*#7EIwo%KUN=&|I$35YlJ}Yt)4V^&yR_prFDhDEDjlbVvIx z7yk5qJP9LllaZKYC@Se0P0}-J(i1W1j+k_gBuT22ve)tC{dM7Plaji)$;E2U-v^6HLRYVU-tq6 zzA`R(?XURk?I#n7_A@#F2VOL3SvGZsPDG)veL|@T!M~q)GnK(cb>Cfx8DJo?mml7H zE+f%yAf=qE=TEtNc@Y_1$=>~6WjJztrhBe;L~WRwdiH5BuTp10jmF#xjka=~mU0ct zk}7#gI2}11Pda?k<%SM5x<*YfU1-?iBuwZUNwR-9$=<;vJNpyt?N1Q*n$~eeu6oXm zB>^!fX6m=JeyQtY)dBO8&6hVn>LK`H4^Q;8{s&_nT6wU^W^`50DT3Zm73bV#flMQ+ znGl&-66t-#3<|4#(z7_6+aB7o+LdiN>M4$bZN@}qHiCq~v0g5{?M-a$%zZOA8u$b0 z*I7xDiPSq8ou~@cbJ=Tu$l7_QXJ==mn@XnzUF@o-7oByUY5yx%|K%;niL1e!V+7T$ z_VGE#Yu!+l_w1DP>`3jeo_7u(IOUhQeYf^cF^+?SFYu%L-^@T{F93P~+SY?FWbK2@ z-^U`xkW5yv_rONFd;gvE&j{hHF~!`om7q0XLAB0`dYvUTI`b-ZjPw*$d*iNZ7yl*M^IQuX?1l>RMTK4SyCb4*n&Cf|bS(}Sb+U7z&VMB0CgU(U42FKLvl=+3F zTvZ3gVv@d*BtxT;kuj6uQInBT$!OGMJSIsd9nGu0ua+-_5D@eWf&oEgSfe7G`db;& zsIAbb3~OjYT|JGFm}GNzoGqPkp6`q?8qXf1LKi0ROwJiUfjrP$mi**}<$wJBhd=yb z56_++eke%L>%KE|g|@^cK1_|EuGt^jvh+D&!sgBxPwb7csWVP2X+PSwgiBaN-7*c}I7sRIpGjXM*0WSpYtY<3vp^P>0cPuu<=YM|+2M4zSP+kx7*9iN7 zEpglkdZr?1SuyRiaVR;SV%x$VnbTM_&H zgJ-d`{aO-<+QP)^m7GbDiU5|@>b$DWU`4%wq1n%+^ky_J+25OBUr&JM8-`b zV@b^PiLYh<`U8UI8l5=}I`bQh6k$6Kgdj!M&mD~Mk3CVgcEyq867N(W+R+64O`+H) zzJF@RZR^*sAN4r&S&za5{gc?R9`u!G3@+NqLQ`1qa+o0`eJ4qKJ=>C0@ju%qfZ&>wNUcc%y zo(TG$&EMvZXRgY`DXULC)-4>*KRF4y5E(mdrWvID;8k>VeF#vKTh>+_LHh(8*J5yD ztB-k=nzGLDsLAfG1pB%Y^z^5m4o#D?Ap}(+oyu~J$}*kuGM!MsEB#I#rU|1HX_TRn zBmY59&3*hHwzs{2Z>Mk=JAo0 z{=|6Cz{FE$4AAidD~*e!;8Te7vg7pggT;EtN*cOm=oA*wYcG zZy>9Zb)dS;puWnWrc$RYqKo3QE^H5j#G|Q)QPM~z<2!$YO8b>;U^Z8^b92E>`Tx$ zoM1FA*)$So)Al&!WrC%1d>k{+M@1-=*D3dFoV6^(=}Us#vwfU*L?SZtLTk%*vfPtR5c2!8U&#Vi9aN; zPLh#8Y&7+Dl;Ivpf2W^Cy~sA_@MMeQOGR-)b|GFMiCJ`FUKM4#~&e^%|Q9+yN>^pb``#Uco zbBT5#=$zjPF6ud?3r?G>bIKeaAw$?M^3k~D#r<)%?TIlqCLx(p$_-UMnrjVes|++3 zQq}~tR>8bFXsU#|a;PiICEt8VnkljsKw|~!3xLbXJQ{mQul?9r-q&Cuc+kBJdjfRbgorH*sJs*Kv>&(=A zXt(y#1vw;b+emHh?AgpiJMQG(7jI`<=d-qBQ&s~F^E9d&QG6L4c~8quG66k%C0)Br z;+8qlK0U}gkGqodR(~8#GY$zsO9{6;@+toBUpHp@5w3>T72pr5b*2Q&IJ|g&DrkAI zo-d!j9Ua59Eh$a@>vxM89`DK=k5CnaY9Uw+!5aIooMH$%X_oQg?(Z=$^!jXR5vdKQ zpC;hM7K49V;G^7Uj}RF**|Imzj=gabF^Nb*O}S26y`P2}cT#SvfhCPFzcxk6svo7r z{C?^fgx6i0+P>@K&{nN*Dk9)m3=R&!_8!>QnNr&-jK?G$U2*pJ#2HJZ@;5?(2BBRRJ5}%wl*z)#}lL6 zwdodaed-4G_HWNQO5tij>k>h^W#J+92_~Ed8_Xp1?2~kCH%VB2uq|y`$7QemG0jyA zfte?03Echsb^PY>&t)ED(F|x@B&eC2+wXLoJ&#GoCbAnJJoQ(sn)}AQZ9To`cl_ev z_hXv&U#FT53rp@fKd#`70u0E3)16x*+|;AFI#OYMt{I`)Dn&b2 zzyHk(2DZ1fviUtax?%XVKwbGg#i-{hEy2a>{5iy^BxvWhb-!4GcGM{< zW}dn@{VnAHvBVg^di)xG_sq@8{IU^%)+K_PR{PIgOK63yfTM(X4EDWX(%+H&0K)zX zE_%(4oV577;t5(RwhIt6z~j4q&yOCwkg@o1cFaMjode~Kf*4Pe7wt~}U*^i8^rx99a@R$<1bx*X;jT?^)-kzti$#9~c67t0gK$7W*1ZEsb{~k+ zADKvh7|_A8KnqFUVg_mh{X)FuH`G3a*a)@xMd1z}#r)xHcEvBPQTU@9A#P(C|_wkLt zUc`ZcUAAKoEQiV_K~*D!D}*hc6K)6J>kQU`F3H}1+0!0pulNw}f7SnZdhL~plrwe@ z1TBH?p*{TgA0Oj|1NY_hPxC>r3^boWXp%$}#8R&zy#7!VB$Dw(O3R zh)Q%*Ft@?S{ANEsN7|#h4A#$sqnaV)C+|68#qbzxyaNsn!c}L((Uu1s`y+7rx3l|q ze;gVsv-_QcaMtyy?LS|X%4w(3HIyZ9fO{^18q3Onn-9Vl?||ki_`(~ZvVgj`j-x9A zoA<%9`yt}ctiutL-3Ov{4JME}pu_xDgOisBs0?MFZy!%c{`h=^f9@JbMLdPnv(--% za%@fQ@PA&seBb{i5{cn6&NyR8%;Gn>xZY-HJ?FDmYC+!u@1Z1X>m+k4?eibgf2{{h zH9yj}?;u1^D%y>pRsB$?XjPlGsuLTgnc(_+FXy^@FS9+OU0w@qM+sVw)F`hL_yf+t z6-UyAXj+mYznT_7WJnU9So?ol*OR=^@hHb0@o)HzV4esCf_p^RU(Jb&-@&}vl^h&= zk)g5dE9RsL@$u9R&sYQ!_V*L8qw`^oU3dY3i63N1GSl;4iNrR6a z-Eqbo>x@cyY<#vXC+Cy{-_d$zQk*4 zBaI`f^-Ph+b1o+|!t%Gsy6FY-o%3Xc#U~x#x~i9i%=9^@Yi3`b z?hguDkAlhuyA1L=1b074?rE5N;C(?@x?W@di!$?e@8|a2$ER*PjnAEZTP80((^xa_ z4XmE`2DW!T%2Rv(z`qXsjgGp59JE(-_n*(UJvSg#TBKlVBa3xS25(vHBdCkaXEqj$6 zg|o~k1T3Ee>*qp!`a0zP72;n9;46QI>M(reJgBlf%09m?yFIto5ktUt-wEw~Fs~ML z-Cpk8w(jdX{QUi}yDxQ9636#vuI1jz{Ca!Y&=`FBw$x3+&z=p(Sbo`95%9sl``z(Mt(j}B@ITufzV@m|n(M!CtogG*AfP?*#1n(FaVnlGhiv$1EUH`sT6uO! zbR<`r4GFEZN`8>-ZTGc}U4ZQWO-NfI(a=im-v ziBXW?_mxu-Y#`*XE|~A`7~H}yAN~+~`<}{-uXX{{E>fp3GM%&R_YAz57x&zVl-Z{= zZ0gxV?m3{rISc(9-)z|KBeZwLdFI6^(NT#{3YN45X!-x_op+cWN15(_)!iq~nVd&6 z(kNQ8k|o=6z!4LSF=5$21_KN1Vs^Q}!jhKjUBbe>?6NFjNyheKxQh)eX~EbA3xjQp zE!(oJY-Nq4$$3ufuKP!w(5J&WeMXXH8-LH!GpDP*3f*1x_S;`oebpW+Yrh-%;QS?u zMOGABdwJjY;J*Ag^WR(rZ@92r^`Qfbgj;6W&k!j`Q!)AM~*ym*NPXQOKl`YAC{K(pa-wlVuQ+Z`Frb9XkJ_$oC`8K`BL^A`VR^_YD4Yd+!5D! z^U5$?c}6Uynl|>(?i7PVIYd?s04`q`!LWhH!dAF+xtbp5Cj;Zk{@mi&1!h^N0%%RZ zicWVrCqOuBRFP;(XXWsIGZ*Mmc3#xg^4Z zju1N!rkF~ZY~7t<|M46z*bptQN<6nWLQjLvEx(^)XePj{Ia~7(r66tBSnPj)en)@m z1M80dC>D!p2M!#VSiE?#|7YD(ES{Kf&KDt)MWcb8#L-XrYQ;6kJ?ytwKy;F!nt%( za^Mk3$~H#*igVt>>o5ESBL5ooWXRPRv@_f*&cV+81{a--KAoH8{wHtd&TXG%Ha%9X zw`M@+YN%`XzFsf(JT`STyAIr3#+)G-Ko`z8b?UroX}IW%8I-O0;Tw_;drXYo{88-}yk#WIilfIN}QF?Rv%P&r2dP!Lkbk2OqVV$z$D*ANvBwC-?I=&;KUTkd3xEGLp8Vs?3Xq zm&;6Ygx~D>8o%BBwK8BvKtmU_EC;V|IX{`2y?{LjzdkQ;F39tH4X$1kDz!VApP{iY zgUmvAV~DdBMKJQSICKpzSgK4KhOS&{49~!$2jN9)-1*LkA5nwztSog_qhr6cU zylm)qV`F3D;fEjQ=>XE6R;-?vfAqatG_f|;i7LLatFY)w;U_B+HG<#LEy!gc zsr=hBFtLX%hkn6^g)gKo(&SF$lb5WlGB2PWntGC-Z~YYCzW<%f; zLStWT1A=p@v)I1>zcEestdJmIGP;0QEmb}=3h}9oq=T{h#kT4u5$I+_MWVU!mBrU7}(5!vQsI?XRwY#5^b4)FR0&+_PJi^@{Zn zui>RPUumz0-|q)g!aFZh_sA{>M&LdF1wVWQ&hJ#et*(Sbuxj5zn}GduPA_{vg91%S?nzk7|KC8oK+&=lX|KmLd`NP9w=9|!>Z7gv?N#4mUEfu1 z(}a&1!ye}q?iufhv!uln19z}_*Vou}{MSWCNCk z*YyWs&k-Hourkb9O(l{sIb-qrCz8y~Sj04qbxUK^*C{~Ib+~K=tm$@VW?JyJo7G~C z4U6F4U*oEhX9a6*3-f6^71Nxe#uzl4>%R++9D-#XaPwQ-{z1|?xc^Cb{Ag(qao|Lj z-G@_HhMIEuyme95^p(Drb`EB^^}$IpIhzfwGJB?|C?TxN!M|F0>}{JmXZP#6J~=x( zJNe=lzxa&#@bKg(y|4U-e-a@hKOMa~d)AS>aG~yMX!D)Tfi1i&Pd^r2me51jhh>+Q zV+$LbInGCa^a`@3nj+S|SkThv#h9!44R+MuM@~h*s6=ApO@g{+!PKzYa5j@ma^I7; zk+U+a>b?X~%cNqTax0ldOe@0!`@X|B?tLTo?*0ZN(+7%iLO^{dbghQw{_-7Cf=Vt| z&$cIjN-Dk7Gro9#eZaXPB>3yK5f&v%-QoCHjt3r3l9`h ze0e-PO{l49cRZndU@UEi{=iQRYXkjD&TJi#DS@th3Ox|~HxEwI?0B9&PNz8{u z+jZ=5L0NQ}jz`%D_|(te!0-0{NL^bK^q((0%_V*SU8Wa+MF)-dl4{n4GL|_D2OqP_ z1`k(tKaan;>L!}wJ>CO>67O)YI9s!jKr%hWy?butS3AGLq^>$! z_6k$y8awvi!r0_XJg>hRd#(=&-nKeSw{7n7fnyoA?MWlE(AN@T#iD5O`JcZ8&Rc|Q ze?G6ee)NZGc0zX({O4OC?6>ORj|`wQIe7gy)iUtsErr{GiOIKKHH23?@Y})_+ux5<`6Trlk zS6=y)&+c$D=o_Tb+aYdHEz*xYmrJuQIk#JPfo$Q2A7ga^s{>@6D-tWQXzcp9!z#_{ zu-EIMgLiPt1Nn_oHCVJ!lnqjy42$+o?=6+|f|`7#<3tZZbB`dGE!m|P5RbsiH>jy^UVc@)>bt57 ze)2d>&Z&hO>w2qI@(jyURFCgIsK7RCz-Rvy>H|223L9#LWYmCx3D7h_cbmb?v}7)6 zF)?E?IAyZ5%Rm!?L{wvSPlO%E)1v{)3PRx8!WA2xE=Vxe_{7L9{Wy4B6e3 zYP!DB$(QovkqpJyCzFzsz za8`DB8a{uQS_-|cx8$Q)qLx!9Gw{R!>^uf9Uh~up&OM!FWClL|V>P?uFV0ib674dV zhJU{s?%JVzjX3XDXQNsse{>9TmY}1}AXykZOiGT7nJnoxFf>6ttg*H?!uA0L(srrD zo(nRb39TQQ3mrSJbMCON>$1MSo;SVeO_^J6x#cO_nAQc*-7oy`RazqQs*3E{7A}CX zv72~k_s6+=#~0as{5Q;|$LMTaroh@^J}CSzBd;gPqRYU~gDE@Hx~h}^anGLHxZ{a$ z0gCDMt_$jgu6B_2&gbN%5|G;CqV@m=Zo>M-aXMO+Jz_}*T)hUuVcWo`?fAmoaQ6<_a{}t3uzErH z{aM)qcW#HN6ilYnC%r2l;W4-x;HXu49pBnPMgf7B?IFoi@OaBT@W)gmUo8Pb~IhwS-k2O zZz)WiNJh@>j^`feYsk)Ivsr0b7E6{aNpIe~`D8yd1Bu&hydnDnA+t@atga_|M2NzeA`mB zOGvP%;PjhOUXJ(4{FtME4T6JnK_Ydq_>ybM! zE$^8|63FD{_|N$O zQT?)`-r%j5HWP7p6%69ZFl$kN^PbilCet(kh z1j93jcwpac{CdYXIXt$FWM-0RC_yaLh!7ex=}|s+$F*!d@{3}gkvdqgUeLB!phwC| z@~qT+0Q=zak1;awO5b=t_G}1v^U4T|>Pt4~?FZ8wIFvy%Vcn8AEsd&6T(nG0k#kkq zQQ`G{ia9@j6}))0LpLu=+Ts3v$|dN)g#WVXXS;mzr)pNql6H9C3%zamOWTzhV@)r- z@j{=ix$Q74yY(#eeE=y;B$-60HJP#@LkZw>MIk#v5_Ug-(>_iv?> zF}2p+Z3s=G%OE<&_k z0ij3LlHF059uTBPv72`Y=}}&>;iJ5G&EKMH?g2$75If^sgMA*R3_p-B{ugfDLfqed z|M~1bq5LuStPwQX=C=kiU*tmqgLI{gD@0WUG=CiQ33tb4(=c`z#*Ru%pDFr=9-%JM zglXoONsp@f3TWvUv@fCfT~^B7qck6Y_NuQvY+~jTw(b2P9v^>2@dM~TT^eR%dx?|m zAIPxt$uwFH)-R3I+Nh%mc>dY2xWlI&0pBQ+@@M$?5xD7h@UG{3_^hbX&irIPFZGIP zPLa*cFPpuC@TFhDb(g?~8eEoo$(H-m)Yl7EtPNvXYW3mvfqXd!(i|GjKp1+ub*?!# zUUcocL^{Wku`^k$f>SpP@>`efv>!|=aR2cg8W%#0XEK?wE3drrw9S!r+n{x=s$s9}0syozmyRBV<`vL$ktE(; z3g0{U05|;P67~;o@wJ&c2OFHl72~~tN%{jpA&uSk4DieRz#u?VuMNSg*l@iu%ikVd zR(G$!TE6#v(p1YP2sncR-R3@JM?UKcLGL<2OCQv=luwP*L$L6?a&VrnRJ;6rM?O^% zoV_v=sC@%pyf9R}IinLM+jggrSy_2Zpwkl~PuO102iZtALVoxw0#X zES=}f9QW`0u{sgZ{}(Lf1;c!gB@7uMn)d zN&)w>%LGyH7UTV^T46r*8lS$B!Lip>)GuStd1;Lb{%mQe=n!izZSly?Bvu9%cZBKb zP;t*c8+u#(Z}-MUYA&u2YVa`VV|wm5HOKi&K!0Qku`R#XEm zxD;0RsOPk1MU=*f0a@6(C&TVzS&*uo{Q3Efv?TmSe@pGhiM&T=!dKk>WZNY|i26(> zQ=d#G>+ieozJyOkCoOJ(7P!Ywr9IjaK$Y*`yu>>s-J4`_Deqink}ur-=Zwr8_AJX2 zaAYaWzKYFDzxZ&aN0=?I9mnryDy8hGu?Dq6iSr5_RSPJ=K-%*a;2j{y=yq0UWtQh-_e5U5iLmK?~(lAj&Exs`=$(Ef-vPnrty}^<`)zmLup5Lvc z0z#}^05`q`ZoE!SyF25E^+$G$-8+SNtN_kA#Daqgr1LT}T>u2Qa2YIVQv;0a&WaKh z8f4+#$5NaaH<3Urr16^bo67unS7OgL5u1-Szhfe;wF)8XBauh~sQ>xTe_mnE$^69& zplu6Mt8^m?Xw^3$DC81~L)XRNEiQ0c%Zo)?|eBA&Y=ui z?|wMm2WiEoUiSI~7d}5))E5FS&wmj`B7*g2#n4T#GH~}}$>Q|8p5_oQUadSIdbJ-v z6UbbSuCH$Ic>|EhX0!ED$^_7G!womg->Hp{xH8m)DChmQp5tcE0JVAJ(<9Jvjzr79 z1`duq%CB~Oy}U>pC^#IRl>?Is*64 z8|r-0`=t(a9rG#Fk+7C~=%tfPFXQOY^%Zr!n9u4KoeR5+V$hMHEXPme2uZ=(Wid2O zEh1Ud=f2PThv9}F!>tb}SD9xTp#GST?|gZhAKa8Tp&D7lf`iBteaK7{1T1@FD+8+< z**9FCP7`qDDriYSeL`bpe+;rPJ0;n?Emdq^7cGmks>eZoeHy5}o$Z~Bzx4Mf;wylJ zlrm9QSC@F+^PcCw`|&)*YnLk9V$}*#jCKQbwGJ4HL7UB)>Cd-*j!b^@&|+Fy`X|eS zK-$9Wg-LhWl|XtjIl{oi9srDpT0Q71((|XEif4?hsB`M1RE~FPf$Cr7dV_`qiL8`v z|G+=tbiQ9Lr^c}0W&NSzeVCrL*ts`_$imtEQDPB6caz!y#A`t6E5C*Z_rZ4_fcy7) z>pXXy{^S3Bb(-&dCGRxG$RTw%Kr=vQrQq#>@CwM73>Kc!NK?oLM=*LOR1_xxO>KA4u;!{s0A^ z#PMeD>Q}0Vm-yd;QmtIqcM3!jtb{=Iu5z7~!@Pb~FW*aNV)`=1CSO!h*K1ncH4D}5 z+eqb`dF$RJRu&evhiPv%Xo$g;XHy(FbRPB&U44R?Z0%u|55DN|{ z=9~xTtN*NyKY4v=XP`Qt=TskqtJbJzy=rNcL|BI$JiIf_(1fx_N5dM|uFI2Op9bo} zKM8}gp>uwIu;qLp0VKlVa9w|Yf8tcm>Zoqwz~gOVzX&n^O>pabN*Qe*__gcaRARq= zVMxchUtSXS3_ethk2O_$XSz>(o~5g7zu3xp)y3WrtCd`-tXi+HW|3w8{L3yAjr{|k zsJW=sEjnlAmugGk$-`-8CoSqj8cTa47@GR@HFSS)_?s8N>n?;3yad)SJo8}n$98=G z#%aF&g*mQq`CNm$tvQOmf`UaaiS7cPg! zI^Qe-Xs(0TUIdq|tgQE(oO=1;w`REI^Lgg1=g|5N!O#LoDf{CU*T#9zC!3WwMBwt3 z3ZR>-JkwPR)P9Hc35~@)5y-*#xWz+z(?!g9*@i~yB3|<*@(Kl!rM5Sn)qB6atNjJK zuGi_hUYAa%6WMIG?%{_Y4r-UHC>{V^J}l>J+9-reZ!hFQ`lYmaRuw{XrE$TI2Y{F9 z1O(}VNu*$ZhAQ@Fk9t%%t$Ym?wY(uX2u#nX?9&d;fzvv|rNQCB_xr^9XY_*J5OJGn zW9R-9G6##hBGlJuEbWBF?aa#_`rL8)kDq;amRmkQ2`F$DeFu?)-N>ZsoR?h_<2|2j zR!+=n%p2g66>3k${=P7cF#~e2eP4#bNoDGYhc#ZXy7qCtM~3QOp2~^_DP^6MvMwAB z*Cmt5Q^KO%HfSlWU_CT{BEuDQ{6$KbI8-|CZC+bs#Lg`2%-0i0r2DsBSK5w(eMyY}_30Kw1=Ey7?F0Fq@4|<_ub!SeP2sn6q_h8@PK^}ZXofu8MEYHi zte_O+9u(_!ipAa#YbsxQ{&b}Y8Xh#vPb?YX@QK>kvk>s2zEH88xs=7>qsp|gsy~XR zDL>3!cIP`DSN2Hx-!apKHXduoF(x6 zht;!u{0RK~ad&)sqncissMA=~RWfz#IIJc+2mvo%*WkNrj^Etqhlk=XpGnI`P1E9* zWz`vm5s$~?bv{V~i+sfj?l^tN zJwKV_Yo9DJXRYr5jC8=9vJjkid6d8VTnm~m0_C^d=7n%!i?_~(erSj*Q%6+SAO~A^ zXPC<`VCiiMv${9t$)3GMlFaGNKRnoRnUu0l)3kUlmy1tLP1XJKm%p6HHpnG$8IXk_ z2SM30VEF^I#Qpjh+XHh$C2)S-1^>zwXT80oD?y4*{w6uJB^vo~KnMz+FBmapfs*wg3%I-|RD2PS zOyn3JGYKJB+8d!hT3U?aB^NJ;fBp-|<|^xz*R#5u?q~XJkb8eN$Co}nj%86YYm9Wj ztg-`dyfn%O{RLa4#wJGa7$MG+p~mn55(JiI5(WvkT! zm=~K-j0IT3%+YnRC1=(MBwRpCO(*(}aBiJjCO09H!`U+-~t14)?bK2N$P#@0I2?_fTry&PRdLo1j!G$aG z^PMZaOICDIOkcIcfZgmlfUYdR{rMbU{D*NYlcHHedV#Ycsy-K|tj;?;tCX%S0n57q z3|a>bMw2<7Je=k1g<)FjH5y|&8yCfRX#Z@L(n^I)X7uj6kG5>Mx_|sWP191QX(lbp zN^ah~xeoC6C|5z;HfXc3G*FiP^RHiN)U%RmW)jBsmCj$X?t@&i;w>)hm-hdT-^KsZ zBaKbRGY?rFrwPNBMpmqUO{_|~13iLHt9X{@2&`16d4uywh8-RJ8=sPuIH%QxHMpd^ zw4D3cNS4`Yi@2__pd&~7JUk0!2` zQpSZ4u}~-!(=;un>w0Xyi&tC>T9a}*P}eP@Wej8-#GG#-Rt|=@3&=a}Kezu?ylnl) z%CfRqHHv|D^F)CW7ja?+d^q zVm_bofxtQ7JKalWldG7RzO<$^8`=zFHrwQ(!)Y*K$-)TX5Nzx(&gy4k4*u;hlkp<`gx|O4&U4>lFX~Q3cLINLX+XIo<{{3Y=G;AK@ck zY9$gqHIs+olGC!v?Hug`OBhLdqWfU{v9&O75kH9GDRk3m9fqI6OgF?uT0^@rHxuiO8t6$6;Fa8Qbh$5>k)AwM{E*+b0@!W?=7do#qe@!dd&PzMK%%4en z#{1Az={V*iRr8n!lRp*UTua{euoI(iq*B**vS(eurp^+-I5wOiosuNN8l7zhYkMIU zR_O#VHK!IcSQ0kxg!g<0?s_8s(&oA2sW=|qoZ??UFov0v2qBekjmcJ+Rx8%dJ~zV0 zzSK%IDo(nroPZO+FAl)x?}kSYsb^mwg^_egZ&`uDhr{7{v1pG~CUT@|DreoSX3z$TQNs2kJt3G;n|`BdA>MZB zO+<{+cc&~SD0RXz+j4;_^T__!kutUi%udxcqAnpIhFwv7A=Sn55rkK<+r-O4Zie7 z*t`pF{vAxt!B>6-KYj$>dKsMCAFTLVq}JYey7lLq=2VpN@VzPi@B7EdWhLanSx&Sd z^WeNCk+UpzO=??HN~Q5vK${a z8J;j{Z_=rY>a6LDvGridLf}sa)AI zfcAr=3U#eT=VYmK4fzGFXZ5&K3(ig%o_Hm>T%&J%sYA%(`30S!;&UGw%aWdx#C46X zHiHfQ%9EK8<%h1pRcFIj-wc0xz8dKt7>7^%7(V$E7@TycdG0v*o? z#F}hDPB#Itd~KK;zSc@SA?95M0wy(hA_-r9KyA=FJf)ubEm_oXK_yY9Y3CQNHF3oklykz}{J+qP?o!k#teF4~>haZp^@{$lS;?zeKi_WH< zFO2FST`kU8cE!jbpzWnwfiVZITlKj4gLBYw2ghDpQ^F+;IxP{^W9;m?uQN6K z@$)wkf$N?JpL;c&-J|k+_yGLvt#I3eV49WFKX(K_w*4W^XWy3xXFx1Cgq&)GDFx2U zR)_h=ueDN_(B@snEO1!DfA56P-vzslsb|{X2G?H!uUiAlLr^EZ(`!O)+)$s;Xh|4g z!r{RjQ*&xjb#tA;(oTOzN?u{xM(Z{KQ6WUkFpOw6n~ez}qT9D`558~&uT5ixJX&)v zwAyiXX$g}DN>zW^IUgrt)VYASn8t@O+jVOO=?XwCC3H6~%hw;$u1PSb4y2uB2Ue_s zZP!A~gS-ozzg%Ylnne&sozCjleJM&B<+q2-<_E4HnUO!4Et(dduJqR=P$Vo-$mYp5q zpT5~jU4u5S{Idd1XfUFK)M4jw$e7Sl2iIK;@4o^rXn{s3vTVPjuv^Z;?hxc>bnHA@ z@^ydil7cs;@)qnhyICL^o(nBLI2G*zqNZuafM_%tjSdYB)x@Gb9QKPnUW!hwGRZA+ zY0Mm_I561VxQg@oU+rzy@BryDP<0?|O(5+^Q!F!oEbF|W7@nN;0+iq&T`PmE1yHM{ zuk|GnEdKO^Gr;KNOYr6(j?jGK#*ko9L&=@!z@TE!-K`;-;>sCPt$lsd5@nx$^@Z@3 zOVt46(*WO|(Z}|O(tP4wqoh*`oVA|A3Y;g@S8M-@5I20ajYNIz%-NDK0vs2R&Oa+n zz)RP|Rjc4bFM_KUKnH4&El8dK>lQ(4t3f=Zg9&?&WXa^zB-|w(5gKBCo6NdJg81#R z`VE$4#SFuU8io;-QbvL3ZMWSPI3xEw=;7ppC6EjD4J&gCUA3D&QtDpUtp6CAR_atP zz;qPj1=98^1p;Xw_HEZ)-eCji@$x*}xmE|lQwg}0j``+aO()RVJi*ydhR0tSK&zDF znpWkIK}yKxBx9pFgbBSJALA4!amRW-|mDr zd>wx9a6V57IZJszY)?RYJuK)@W{zB1a&S=Xmx_S33(KAM__61L>>Q1)*EFpN%!U8$ z?d=}kf4s$Yhqm0%Ge^$!_Q#? zvp#_0fZ^E-pu01KCWT_r1KA?sTh|}=b)uxCyp@k6Lw$#*!yP~ophKZh#5B#oJAiw> z!lWr@g0g1-%)YYInWLqvxNPNLqiK41*Oz7g-2dkZ(w;!=ybN2q##jey7hDB^Wy16c zhptOXApLX(>iN~L(ziqT$*BNm35-p?1fM48DGTZ}8p5Jj@1c<_SkT=T=BzF-JWQD( z9hT%BBDE!;qyEf;*t7L`@`()p@b*#WW-JgAt?MYHVrn4@(7V{+pTE^kQw`vp1qKBS zX=-k>Q|c@Q%*|45rdlbl_WIJw(*8Czi^r+6uoapUg4PDbqK8LKCi8=ZjWL6sW;Mp= z&7R9eg81#x#CprJqNZs^bGckJlgUJd5YgLiyDiXQp{KWfnahoW?lJGl&7J|Unvt>r zA*Qf$kQ&Wru1C3G$?K7Y%1cTs0wrZ{BMC)ENZ8^FMHE!M=13Rw1Mhjz`8W#s*)qNS ztT(gu$ejR84nX4qTODxUbbF|Gyj$#dEr@=tQtL~g0!Q`^ImO^ykn!o~dxn?pQ8{}) zt4S}mi`k^b+>C`G1YNC$n)mE;b3gOvuQ;N#JyjC;S&JI=Yt27snv-apPwvQ; zz!?x-N3haS7;6WhyU*ZL-)y6~E!Yd$Lb2sZdw;h{L_o6zL8eVhl-)WUf-k%tcAbDV z3n=c&VizG`c^4e)Fc_Xv1mM8&Eaxm$4QJ(o7z1NoyGXfPx&Zb~#8(I*el3KE01+WX zB$LTR5{X0hS$Z6`$jPL4>z^5LmVjl&n3%rI zN25Fgff@uV?lmZ3WK4CQwg!U*EzlVAXHJO-T(lIL;%A;^J{ylid$N4|9V5(6=fS!A z2xOu#rhI>P^%{KUrZ(EU^s0FQOaVuQ`Y%LzybiQW=&>LkATLWQ`*MM#HO1k=C8|yM zrCZSjZ7l`{8klhKM6TF(R`tg48sYPNPe?dE6Iwi%(-Kn3h?Fv7Sym(z3Plo$L?Ei* z{W>$PeT4#oip-r2lw64doWJ;Wfy@pk1JcqKY0uwT^|G>Uk(w6b(zD)BJRjR{OW;$- z@|bEm)x`^R{f?8#zfuU!5QoaC1ZRM$*$c2N_gIN=X)5W7Ys&9N{(NLK2PSm27_98^ zDeO5p?Q&>umXEw`gsJfYa~^2iIt zWM}ph3FM?2{IllKX<}jMYlZe^11y-DwiwI5eBu$Eo|ba!gqPE#Lj7`57!RFDEYmbC zVh3r%Fv3C19Nr8%$D@uApVLyf$gaXP1*r*DwTMy2imogD!BUn^P6ni%)seP1&wTCq z=OeFPeLZ1AfyK;-ZBDe`WxKD&=j_Z%Ex_yoXaCbv5&a}w9Kv=chp)O0&6I6Ry!w#mg^0d}&oHTE5&bt|Nl zkpf8Tx*j%7vtZ_^D4qZ2^*^a_DVdycd#)1|a#>uj6<@j& zlR2=UrCw)AyBh3q$`b?dwwvJHx5CJ@JO0@uy99j6fbZ&0U3)vcKgWl!8)l*a&K<`< zYRKVk09rb9{`tl>I(qz>vxQ>IM}+b^=adl81$0XF+fSraW(?B;B!SE_qtnV^);n&3 z?ML153tOSH#X!%a{=kq~#ET^zC0wn)S z%ilKblQ}Ct)h-~N%B&_eUaH!v1ut@~1axKL09I%Afk7H7+NCS8=|aBOu6rM0LoM=| z9){U5A7$q?xWpeGPgm!u49fnm6Hj%&_)2MO36~Pns$(`)?wDVABq)R>LW0()R*V~; zPypT9X!tJczU?73rEbrlnqC*A(>~LVVc=#BZq=3NwjBZuY(2SlP1ZuDyXd?L|vqU$;QzU#?ztpxtviaga`{E!j@%)rIe9S zC=|Zuo_jneY81qGNL6NY<9>0TUD50$H(m0{v9#l|GF2DY7NVSt*O7ZQkan_Zd%i*0 zv;-Pr9lUtWyNiKCJJr;?N|#+3bgRQ=t-xJ9ZS@>ou{FJXP9B^AX67~^rC+;r=d>_U z+HWwMlgvz;2nk)S2E8qKexk1IDy3iE>8ph-X<>c3wdB+85- z0p@ufOKf`=uI%#G>2HJ9CS`|~791N>gM~uCqK-%)_6!m!(H8nu# zFjtDN1)KpUXD=$#E8UIK8`OS>79>h+WqQ^G3*upo= zq?Ukta#A1W8La*IabYI|4ysqVlxh*xa;g+xD>zGFdTwKd(%jM&E7_l?W)wg-*Xb;5 z!S@qZ6KbyO;x>PsXWB6d{I^zeDC~Fdf2XNA#HX_Cp#hT*zIm9TV|j3H9Rwp=0_P@; z&wQ(mB`ZQMbH)TPpeZ}EaMUX#p&M9$T5jhoS4suURt@&$pvR)t{CsGidllAUX}3mL z7hu8AxLN!XFDzIID#vHSJwk}Elro&RLx(j@3u&4bs^rMu0Kq^$zZalQOa3}g$u{&2 z$Ib^}LCg9wJa=~RN+505aRikCt1~Yz5b>Jj;d~J|fqGr<3%Ts9Hy7iNKMvWXyDsj^ zyMs>dWk0{v70dRi>wIf5A8-bkO|B`1Rb$UlUC^9&EsBJhJU}-j4EowQ{Lb0S1ET!K&GZun*Gcr<0S2_ZsK%CHb3 zR5&u3O!)TOZ}%Mo^#$mrhUOniE8n}y5YAsBu}uh})6=}pQ`^roT|w82B|Gye0C`Pz zY3EDAabDEPhri*Xe_?T(VtHm3j%`){@yOqG0Y1Se57^#PHGiMTAoV$EaP|n#nN4Ie z9x&qp&H!EUlKt7VBy%$+Na*j<2sYLK*&_mYh2_3SUb0O<0JoG?Wpv2o18*K-;E-}u zBU(lf;dBw46Ac=ly}6C0t4bJ?mT(Lh7Rt_CCK3`776MJU0jM}5!_mjjl=QYhQ$j1g zErzD@FDXI!eCR55oy}^A(K)RFH6bM=ga{Q_bbEWdFCF&<=*@2tDVa7)`xE-1k1br& z>M_DA>1teE_L1&ntzO}UFegj)n}K1^%S+Gmek}6SeY3Zc%Lf8!kQRhA!HRBge-4v# z@P)hJE5C+h#vT7mJB|Z$w(~{6IRSy|XF}o1e9ImmHu>P2M;JJm2j}Jyh|XcQ@B{ya*-ZkuzRAeI}gkuAwU)870Z_}zu<3fHv6pSj$eE_4ZXxfw>ek}ayYw_1-QwQAuqsdo zK|~Mr8j`v*2p;9BH=lSHIQzuSCC~Ov?A54SqgwH~&ZR7{pgyM2-Q-=5TkeA&KMLR9 zQgSKzOqD%Cf$VDlA<(ySRJ3qzp-;cl#w!lxf!hhcHt+RB2 z29^K`{aeG}4iDJ|OJ{x@BPw5O&tm&+Op@>$AEp<-ITZ0TDy(rjVY7Lw|sj$K=hbAUH47 z4Vaftezg=81iS;qIc(0(bjh^Z#U==Q255^G5dmz`ApvDsExbEecDT3s~{Q@V8O(+S;UXF zhEQPk3^0-kx1c^A3|-d^(=trYfGT%I*QLDo#XsT;cfN{b zb{aFSm~{JcXzX?8@4Pd<{pd7NhFY@?qCa17_L5X)Vfj6&z@9?_nhJXlqD&k}Xs*|2 z^l^Fqri+y0q_7SzTVJ8#Pjh(~n04I583}g-ZNLRmO}QKMNNblQaJPV01Z)^nlNKMi zZiM~2)u4-L9)94o3d_%Zse({nz6gbZA2BV{19_5~_1SHZG)1Hh@t>6rhN-y%u!6l6mtxPLDr%W7+ zI{a60MZ@swP4Fihy_bML6J!$DUV$Nx0RKa<865x*pjH}Ilc`CI559SXeLM3b;*H~o z_!M$Lfpa(_xZ%sK^qd#w2;SfvMopXRq6E%`6@P{PSzy>tha;rJpKO5FY*J2=ymZ5OO6d)nrWL@rcnBekP$=YShpy_J)ym`!It%izhzj37 zo-I>76(H>#bMhKIEX%_Y;Wg94E3Frx1N1blGfw%{0b4iy2KF zg#-7&;0|y5^qH1gK)srtD=siNLpImy>0;C=g@)b`D(#+ z4f-ts9;yJI6tLYVer{Isp}!nq$79NWyl5DQ`iUZQj<)LjubbOgaDEu4_XB~NF4rfa z0RsETqZdw^x-+J9?&&S(+d&`g>-z(%~b(fq_wZw zU4sVc=2NCdGHZD{Fe>Cw4Wzvmv-rlzavfJ14}CwNt_IdfIDg5X@rhS%;i|L$s?1~G z)G;{n5FC0yEfY^qd1Uq!Nv%vfh^~t@2LNY#zL{)SDYOE6F6bmerRJH=s&C|ISkPSO z-H(v4eEBQ-1e@Y_e&{%C%4kA`tX;{IBGs~8(`$~wlBv%)AzELV`vg~45*vs6~CY@zuHE^Y*JmI zlu#el3BsNWKZfQ)4S->qreT_s%-^1_B3S3s#llUzX4B{R^s64{%9ZaZ0X51lJ-QDL z{{asCPB~thK3?5keX>$3DBIK2q*GxUU8V4x*;+XP<$xVGN(?%aQ2-qY3F>{k=zjSg zc-O7)hHt{5QU5ZY&azaLI;HUH^&Zj+X!aLg0aYCL*|{1bIV<_lJ4SfokvwxwOd=Y` zu#PVx(i@`woG{mYq=8lI%2S4bE}?AT1;eQ&pI(E1waDQyc;k(5{deFi_jv1cHH+f? zoJ(27Tbqb^4|Mne_GDU*3n6q((~2w_Fmkz^zIpRzS7yQZc{;a+Na%9Up5^mUo}9?B zVL@I+s@fIaC`HFe_KUHH%TnVkG6GS$=yI@du)vX$lS5b_dJwMwI{bi7HBS z-2z^B;lJ|Ib$`zT`@YM4d;gOYQ+tc?xs=-LaB@JMYdVAz5Uzu896~YBBVa@!6!9s< zM^2_c)WWRG1FExTzqo9!wJgT}Qk71goB*hi@;pG-g}u{0a!CEp)ByS`+H}EeYvNobz3BN@Ki}tJl7ltJl7ly+aT1_>o`m z#K2t)j6Z?2ftqD1JGGy!+KA)G42#;f zxH5C$2prK#d^gTWma>;VoxCr)~fHz*~eNByV2!#Y`DamAI5m%xfexWP1?b)mrvxBpyX_{qO z1)w&7vRQM1y7LF-d4h;e3TGeXua0i||I7=XFHV3bPM0pX*OJN2_Q=Pw6?ggvVQMHoK5b2Td7bqru zzko^`!w8fuxUk!E6fpLW)Ary|gei+2@BmV&9Fev(SG^M+yDP@vL=#4fQEcN9_}yJe zuDCizOsGv-!}$jPEV!*MNkrh^UQ=BsWC+qGWHO~m5RtG?d-Pz>0CQO_2IxWvO$eb$ zDRn8OZWx9Z3WZ#m1p?^XZxEStzdW(qNJLip)WS1(-*}Ddke%T)6D@T)6D@0GL*mq3Qh`o7lzZ^g+hw1{j|^ z#^l^_#%2eYN)A>8@Pe3G7}^2TC!lk+V%}x-t(rps{ThI|QhXuY%Be|VLD(}u#Ll4e z(+BFK;u-TfT}wT4DCb5=pPP7=RI@t{QCjlt`6(`xT{yCDob){hs5bla>?@|xc10K- zHXxh=-GpQvBS%fNV>$X37>X@F3rra&BT+;7fFYM(o^DD)x*)2TV`{awoiRnI0L+@E zX{KpvQc5kC%W1h>Zhim_Ksxsw5|K|7&wX7@mBL4}f@H=b7S?J4>EcD@;|kdZwo7}& z2L)=^iSW4TK1`ylem;!Y^3FfksB-4}3 zWyaYz^dN^u9|0ga21ot?J?kOdP@!NS2?EU3t|@4othlqBO*?PiDtt~Oz_Qd$k3LI1 z*iA^aWV#XpHUcYw0n}m@FVTVcT$ZuB_aigTy|KNeZ{ZM=#K@+@$cY9r$8s=c(bb?o zV+a0DhZqKs0i<&5WauDtj|XOB&(7GKC3GpJRyZ_G)1;J|VHlcW81o0{`sUWp&6>%7 z)O1l{=0^#+1YjZ9I*{gq#jz5@EogdxbV15=?Eo$-+oiFkv$O45fZCpnLK*h+3LWTF zxJML1&=~8aG1lpux0I6K?Y)^FJp3WjxmnCK3~Yh!4G{HlBIK&8XJBo>TrJ)iCuO(- z!zh7sA*?vqD@#=?_)B;uNKH0qk^fSxA`xH-YUaiva4}>FOm7=vZYmSFo&ZhZ1_qBb zkv(Q2vy!)cs1dC}RAu1LRtd{IL9mC}_=TB&_h~{MmMr0w67Pp;N@d;vG|RGzK%L9w zG}AQa573+6B2wr6$K*aO9$xN?L>`|(PfnR!;9$vqAYC+LPywXzHhFllX{Xtv8c=(j zx>&vI(!Ch=NlMvudDTS-E?MywR&-y+m+pQgBhv@4OgR1+^lXGk1txu}fVrBESDaA8vMqn*4i1NK$82ax94(u8wnR2;x zs3_5Xcj??4jDSC5M4~o{4I&}nwJqgS6GNI-Y*&^kk$F(kG=b%@ z^wXzhfZz2}{w0ER;kC-+EN5-@CHHRvKG?W;G zbGo*dUE0&+k&pK?dsGB!D4U*VFRIcNIvMtP=)i*+?TTI@&u8|DIfjJfN*z$s$lbFWOLM z4-*B@j}6Y&4A44BUh$W8G`Btr-d;XpS!##(AY$n7ii^wlNYrg@uehR8s@1J#ncdUB zhgfR%h6ZRt2$45)2u;&OCX+b{8#Is#TVJfRtWOEe<0I39Jr@GE52opD4i!xj1+jy4 z*_1d3V=mM8F?ke9^D}$c#S^Hp;gw(69`aP1=$z%%$2z(Gsvq+&cRZhw=>wQ)7~TP$ z>!?ua`2lk^z3Or0l&sQ#inOh8a=ow}=XIgr<|R#g>P#fHm^kKBrQ*F3&?sSM+Tu44 zq!}AG5t-Tmx}h;bOWU&-oC^}vfmTQ_?NwM1%hr4gOWOU!uWbvEd0-ZW|Cvli(=<)! zx?Tt`W?d6NZ@<1{=G-rh?=TXP)#Wi2sw&}VQjoPIAx-%2@SO+SGS{LG#_a@fdl`1y zw5RRatCl``WiLaYp7T_hLA~y*(*#l@y784`TEJTPBruRzq6>)QrWZ0w73G8v7JGMQ89rea@3WNh|FieragrNl{{Ooq zb($P@XLi@#-PyI*VI6VAfnbucfy01JJd?v6U$_9A;malSJ@dVM9N&Y3-vM`UWNZuv zPJnYx>$KirZ_Yb;dU`ras`~v=9n@;6TRj_XcwR5fw5pyem88-~A3gQdQxs-mNT)pp z-FN0N>lBG@@&;&%9B?9QSE?!eO0foe3ezw`~j2vbt%7Y zfWEg-P_(ZNr}(-f;$ zudaI(<}q0qWWl+`h?`e>+tk3GeQnK2!PfC2$1P0M?9Cn~9W;0tWS91yv{%2G*`wOb z5Jv6G3TLcgwn9J4K1AYgSI`URE$93_>-YW!SC*LD54}qv-aXIb%u}Z!ubfa}&{3J9 z?J5%GfJvepE^RJ%_-x1rD(%>y`+)NVltM0S7FfU2;ek5}Y}x2|uCYR-vXCgC@}0Q* z6OfM+7bla>J&9^~Db`E8FUfg-d>)2plJJ~@-zZpXz{}Brk$xvoDEpNQz$#Coa(vz! ztuB=%kY)B5Jr+8|##L9(^zy03J-7rw-fA0j8KUfm>k_ZB2uMBpLaQ<;u^Wdg$fr9C+g5fiwy*dsQ zm;>`Y35GG-m=VIB1yGcVxDwR|0w7ZiU-QkC@c4FE)CITG)X> zZJKyR_DlE)aIyg}15z(ypx!YF`_gdkNVpqjzM#MW%Mw+RkgrY#Y7%l(X9J3q<=R1E zMZ;uX0R7mfkJ)w3S7)}F9kCUSxnZ^eJ+pBCtj(gHSRE3FG30V`OI@#2^UFJ}fV!IB zgjI)%Y6S6y6Nn-q2MRlWEB5;O(>}-JTYpH&&5@e~Coiy4!KI?I{`b4tLJ|=U?m9ypu8b_WNPw!U&B%O2q{pUT<^5&vR_>19Mjfk<6p! zx>1w$6PgebW5FnLY60=S6kR(`=i)1Dx|UC{Yo8uCOfK$X>Xjq(tzH1Y!@ysdFs*cO zri|~tY>==8I7`4eM&ksI7rJblfcO6w6l{3wS@7A{H|n^uY+j2)JT9v4!NpQ#>DyMK zbvk3R6jZ>ntiVd<1<(L0FaL=Sz8W?#7}PivJh`XHWj(Pfd#(dVkIADVQP{MXc{i9n zJoSeHwf9F8RvcjQ2){MHj4+noO4cTn1+W8nDt)f+{(8Ef|L z#;nFAPMfigf?JCkW|n=PW}5)?+Hy$Of(;w?y>J2gz9&lj@aH+UZt)KE)dRDf7;Y?R zI*5h)kdwpU#!w}PH(b@h=f2pFVT=H^Yx^Y$o69%G0scqAhYT1I@PyR1Xz$TWz{3ij zlW>86RWBkoy?(FmGXT%-3D(KmH7tq8YT%JC8>U;cXD!_cw+NtTc({TZ zdwS;W__*dX1OG0IPGf+r78t!J4-*-?PPJiB(3|J0qG3?Zv+v)$1-*lQm3o4Jq}kwK z&?zG z;YJCQ>d0TSgR#B(B)DLu?mHj4I9O+<@>wCu3>w`pi;?1;s7g_~8ZSM7@PJ;3$;`?ke!TuV+}hrbKe3g|)CekFsl45B_C( zVXn8TACC(zyd=qM-k2hvm3;ZSG;?zbIo(5Pw~IYq;&pH8;LrcQA0xybu@rczfQLNS z>3RvDGc{}L1}+t_TEM*u#(mdw7w`iGrvMj<7q)#mm4Gk5tGUi-hALWjBx`T3Ox|hU zkA2szC*~9>12bqQ4Jaw4a$Q&b^mp;HsF-PGM6p9o)Nb9Qa@WXsY3t$<8EpzsS&9=Nl>AHHLp&wiw~vPp5b zjY!SFz&NUu&^G3hc4m*q1+RTm2VeN|09U@Zlm32#B}*;-=+ApeWhL3COW0#BuevP7 zU;bk_ID;lbZ#E$5=@xqwJm4dQO9i~gfJ=?W6~ra5R>5~9Jf#BX5WQ$*&xF*>Q9wts z#-KB~R{rKS_ACS}?kZ->;H-oYdQlpsR4f)#sZ`@Y(E$PU-PekOn=AdY*+Um35_ot= z!P9(dzx^QO15^KD0zu0MRDKX`0Q3N$t@A^Wu0QZj~38JtyF^r9-7L3^ip!vUH&01T(_$4Vxy5DjtERbV443404LnXwt{ zE)N*`&q>kX2VG%M6$vwY;3=d0isfD9`Q6Z5;nP!%#|t^zMmd-rHtdp8f88q5lNx5} z;Mwe)cnoPnaYm5rSAMmqhi#J38k{QA-@G=3X3b@CGZN8g61^isXN&_wt zjZ>vx$g*j-Q)l9+ocdhOt<$T=6IGYi8+QwMz!^}6VN`!7lgWmUkOPj6^4O=3+2!U+ zPe#+ObD+9N001BWNkl$(( zn7!a^01E^xKR~;0#gEqluJFCp;vJ6%T@{Bc6B}NMWsIcvVRtS z|6B0xZ|c4iBvQeyaTg4|=yPXE!=~dZvTZ~S>`OU$rIhq;6>(ixPESuaJW2;8e3hNC z-ldcu9tcN*a{6ciwoMcoz|*Hz2f9JkH$sys-kzQUmkQKFIW<_AN!_G%$B|8+`DOySd@6MSScd z-7N3`vp~my9ua!+)`KZ$2w3e6{(~McY$eOe_UTs|knjPui?&OD$Az^=v2ptPHSpYi z-Dke@P_WK)7K$z)psUlY_RZ-`sm^TJj6DMkB;C9aLVCA~iNiEanMfoWuG9gWH~^mh ze9!ae{@dglvomo{xK{Av{Meo%BZJB6N&8pg_JeZO{7?a4pE<&B_NeChOdsB|e({<> zJcn#J4Hc*mT&@*s4b5*+(4Xyp!43DFN=IUVTyct02BZ_d8hGHL0uq}6b9la%WO75x zgLtpobWyUPJZc_KXa>Dw!o_uiLr0!83^$j5pD-NF)_pKVy6~%C^6AE2Rpgy>MnZwYe=~W*o}@!yg<~)CWiT)TlKk8)J>Vc(tt0@ zTAbr`64nE!w4iVndIHPI=m{@RtNOc{1@2H9q%Scb!WzeiRZbkL{XTh7@HO4z@h9x= zEHB@uU^*SPc4+Vuu(+!<3%C{bj1HXSR#g_x4`sbTEvR*@s#*xxHh`lF2kM~mtt1SzBP1V?1l1V|(h3BEk-?X; ze(x2@aG}|7E#g8i zZ*?fdAJ1el@G?UW_EqEt2`8D+W)7wVTmr0C+CJ@{?=hy}ClZbq@Cu;^D31oY^i){f z2SrCu^A3_{cer3EFrcSPzp)fBJy!$hpn*aEDK70U&U%4aDdkEjrBX^7hT&$j*@JI` zuE(SMrj{O$cEJZwAFRG5|^mNdYC zO?$(?o6i|J{NN1mSD*BNv*98J#}LDNP`NJD-epyAPId_1{n0LNxMvZc{CqEi3y+Qg z&o>#+<+%a-sR!&RGA!WjmUiLm^$(;g*d*b4}0=2BClomp`lv%V8!mVtMerQ=NjV zKi18ScQ4`-pY3H}&^Y4l_HLa5-rz%on_L~6qusiWGvK|ZX613;jdp>DrM6FRDBss3 zeePjf$${r~In^LZ-5vU6YP*Wbv@beilLuiz%E`wqWdm*yNE?RXj*N^n46YnhfPUyR z3pToQ#k=cMBk)QETW2Nv(ma~B6yWS{XJB4{qCdZK zUdIfaS@nl5WkMEWe}UnR2JD*JuE2KqxrLCE=2yQX>0)zuIbve!T{MwDu+4*Rs0+o(Fp09oRCCdOk zlb7t73O~wt2ClmazI=zaFCJ=%#|0nwL^<$wc|fD1OQ+p<&mund=^lCqj$UA{hylFa zXK}e-!tMhA&@KV5H{e|cED1obT}n@KxlIO7k~tJobadai?k3$=!eq^+n*Dj8w>D%q zHmz4Fj>4V+MtX}ghG9797*r16Xzx1LwQbuqP1C*h+G`Ju7l&7#`qTcIv%WO>t#~Z{ zv1($Zi>{b+1#5O>IcKC31=VPG)i+sg6>hnY6Q2TTb zl?1+2S{%2My}0i*CNi4=u;;)W2(L5js8LQa3UF(HZq1%6I^#K)lgjOlB@C>Dfn!Ua z+9p{#7;L}&%3W~NGy3;%54_{t2#r5Lx%v;fIq9@Gk3Udk@iLQFUzVb~_viyy7ZUi93!k={mun@91iZu4W{rC#Tm^S+&hmSw_3F2W zppH!=Y7cLiOP;sJx>ZoTy56RU=HMbi(wnhYK{#7)DylUYh7@bMKh(Xt`rFP%t0ihX zqJVdOf~2G4aWa`pK!si3`aUt5by?i0kv7$7kuhx6Y;%~&L9aiG5AY|ifnx{s!}sPh zBA&=^i?pK9Xus>%nA%2MksDn&-O#q>1CTxo=tQAre~!SOkL@XC41tXYp#584Utge8%OU&z zdg4zPeAUg_;q!V`!;h*=+puQGT%DQ0&mz6`Gz%(J$0S3TufK2*18tWX2QltQTRZhP zX{SG7u=}`bS-T+`T>Y6vJF+d3b2I`wqY2y5W-eU)dBX}wQCQbiK8Mi z@4QON%9^(kuWngw-K5RFbg2q-4dCeqvxq?L2TuI@O~5(~u-gIdcBR>|0*@ck{(-I) z)d2oZEea2i?iV0JtSkZli81 znHMlqpHRt9CN~m~?`N?Wmw&GF>EB4KdmpoO=>( zX{4_-Bc*gI>>03?QnqQDcBxcyGMS9y4>MYkLq5d3CcW?jvS`*yr>j7(jejs260MDV?LJtyFrg9#tmNw8eQY~N?f}k*1+5zxp1(h$e zyXW?nb$UDhuGt#NNOhVBOCW*!HZ(_!$F{>?{14m_6b9tCP};#Zfu0%Yy$_E_*mMX1 zG9}>Ure@SD0uX9K!A~9dnS|NK?51+(M)-@L>pl@C3b_7(k~VLcFjO{gC}79FTumv0 zoG@6JQA*iDh*H_CQ3YqmaU3b7-PP37YTvEiQb^hC3ycBGk8k7Owdz+`JY(eZ7w95~R0n zT(NbZ3ns*qg2A$RLn+uZT5(R(9kUsG1~|DtH;JCrUIl5xFl?oiT`_UY&IZmI2Mz~7 z15aPGF=zC-f{BbU2ERJOk+Z`y1KgW#4tl$O7MU5U+jOUrCJ7U25V(@B9FB;&@lfaWM{HG1iv}ljzbG;1f z3%`2YsSXYf55=k=JT_gRP;{d`96-{|K#_lWxoWD{(kRQGnPF@_|Cbt~(!#L2J7ux>jitQeDKFt(RTWq-vNeOU7 zy*u1yrA-kB!|kq>7{Fqo-TWK@qrUE916!nCEMTD#94)Ui0jrlle+oYJT3FcKsFN$f z|NU_u+W=y4@~R}U@{027HqS6SXA_k@pE8u2K5y~dP9P(M$N(8&PAOIWpG+n*S6_W~ z$efYG#GpaXFtlgW?^h~pVt+7uu3UtyE4X)4y(v44Sq3uF5LS(k6-Qy$LC3Gt_p|MJ zY|Ts>?X(Nexes*J7_?hx)Q=*8jCmg5T#?>^p9XgHpdk|h?47eII;wiu3X8t=$s%JR zE2x|}4A)!=FIyGfrr)fGw|))2`*4fK{}#*Z&3aQqM!~NR!!&xYqy)U$gsWq4tPj0L zXW{nU@YPxPg&S<2e%bMG&FkT$kQeoDo^r`KfC;^Qre1##D3&C9M)T1h6yTi2xly20 zX3*9DW#(KmO|vvUKHfB_-(j7H`sC+U{K%Or-cpwscESM^a!*e3%&u%xlRz~s1gQOh z>_28J6Gkfu1JwC$?H&x=y0?ZS8OC{_mtmo13{y3g+1pDj;L z(fzO$drq0sJ$+$zLMc@T&O(Til(J+PMkyAHm5Rk;(~`4aflAnMtNV(E>*6h&uzE?x`v7p8*r5H# zYn2fdn1ghLDA3dY^ZK+FZ-O1OI+C4I+9$fRKL!?D|4@Osh%<0A1^9y{kHDP48d zRYwnl2H@^%9cRz(*D6;Ki{L*3b+sER(Bjv zJHpV<584Xo?-;3OR?HlM&uSG#uUmRgV!G)XF zI8AAitKj>O!kfMdKX?=z7mgc(uU`%Sa3w4q3@`g)*$&kE;++6N5OyGQUFVV1;;F|vFAPeb7Zp7ZO;I& zTt2&}%$|!v2roz%rIe*qDpf2Ni={CC=ZYMWP+L#_{aH_!()q8nU9kMU?kc!<+Z?%) z#`tF78~8f_)S7{H=oM5x-GFjU@H42Yh8IepZ91efe>qg+@%uZMSM{BO2rM7kuvWSO zMVqy>lAlbiBbj;@Xw9B0vUR3Z%`r_us%(q?_8s}qi7QX+fcJb0{_$6uVRa|r&n|@@ zeFV;5(c)xYOw#JcwUA{7T;wx#{K%=TXLzKfTj-gHAL@da9|KDVz%=yW@2)BM^&a^5 z3_Ke!40hjo$<9g5o?}VD(12CFOq+LQXa}4ZciDv%y~SyzR1v6d6_7Rzqv$wJ$+qnf zid2&$77Ovo&mQxqPNwi|Lxu`x&t56GaL2|pwo_ZPt{G5=ZY{vN3UpCHyUBrD4hpt` z=s_TkuYD?@KyT+rHOVbBIb28^+IiW8=Qif|4}LeW^M|EP<=wiOg*}6Dgr!&K`S~M_ z-`s0EueVem^`qRj=>39`P{Owy|sHepVy-4L?^v@mvZ}h#`ZggSp zV0Hcj6azTRgim(BmBVoAV(3ajXAIslP_KK(GcNnnnmxxm1dA8MtIyfSt!XM(EDC#$ zn@V21G`r6*jDk|CD5b1!6`(dvvzW`}iml8WmGd}a^4u>zlqrqvd8Kq@)m9OB5dy%u zoWYE(xMSlCG7Pl5fL1LP0jwbuutUrEnd|{>4a_^>m|=|yvyu09EUl_nZO{?Uv6VE} z2?yqi80IW}gWsu3YsIL?a}E=^S`ekK{xXY}@U`3OS&tC#rMJVk-q+k0`nN(35{SOW z(n0tul2v*Q!=p)(=yra*3*NQ_J~{;6g>7aF@Rk25R&CG6E{RiK9ggv-0%Mactq^r` z?&7)0uDF~73Wi}648tgxrdcpevw&VftWYYIic!oPaD)LG;OW0P^J#l}@=uUap}-?$ z^@)Pw&JEMGpzc1Pt|kQ_PBXCf?+2P9LYj3jwxxy_I+zlo@ou0ou6}&2@Ed ztGX)r{X^eGFD~Itt%+zzR&1K8tz&7%3_2IY!Gb%UDe>I?`Z^YL9lS{>FJw6oFAlF* z=r|o-xzo|+j-yQ$2zY}97j)LAfB$ZqIY)w}JvSCFtPKu6zr8*7EQI3KBeQ!b1F=%7 z=w;Cr`?G19k<1%#6aX|JpZxO^zGi0%ziO8~du22)d3e)IJz#`@R~^{;YHS6RLG^^% zkPn2Zgwz+trlYpMzhk5tc2s%>3EORQRA6papFckq+eLTpO+d5<0$?)lFqW&uitg@< zV~9?Z`Ub(GSS0Xl3m45JXmya7iCKm#~y z{%pzb+1cXr(bQF7FWa7Rcz9E~qTwf7)navk<_FluBJ%@oU;S-w^Ej42FX*zFMv9KO zp5yJ5K*{zV*w!GfJbznhH6{!Ue>HHBIEX!0KzzeQp}N?xFa$jd5@5lzdu?ucs`)qf z4<3c<*T9WW!dLHXs`}!QgNe|^=@9{vTm0H78r3DS*k-pDPZfa z*|OhrbXmQikU3$XFrkzx2qE%@VdSNh1*KF2NMCZvC6O=9(dA$?zTW9N_VgR#L&ts0 zFpZG)62l(<8UkfXipkj`%NC@nozdGT1N&!_PAfupTFZQ&g?W)26<*f+g4#LxAYF$e0X;N5s)3+c(Lsa(Bg?71T8g3Hq0IA-~V$5STxjE(ES?g@C+ zX;vUGtzM#o=A5-0-glu6s&ud<^1Tn}qX)HqUzvjq&;Tn1+%I)Ne}F8|0UUprDc~8$ zYLI@-$?*1bg2x=jGw}Iu=Wq-KF<3T|pua~4y|!)1nrA1FWxs`Jwr9Bd)QPPFDQ8j$ zF$>Hpr80(L%$5Tq%^HT00cMvjU7G&#m%khtppPa74IrQU=6iSL_V0MJbVH&lg?+pG zWM9_h{^x?3bRgsOBsMcK1lpXNNK|&+Hrw{MH|&dm#;<}wPh4mx*VqN4wILc~4y@0Y z;LlHVJVSTyuj&;%J3$YPBNfk26sy<4@(dV0ys5;kA*K#Pz?Emi`!8(HxL!;WI;?X5 z(kbBgd@r|OI*>jD$7&`WarnSR8l24lbaNzJ`~3n1slbBHUXvw5go#t?1nm2{WgEp4KLfu=Q7EcnvTbL^Z9jv4*iwjGR#DnZ*N7A*B3N*?$h z_sE~Gce~+1r)t^|F<7xYQSHl<(*<_!tHs=i()R?4Hyk&;n;J0ZDnPB2%A2N{S4!pM z@pv9>;%MuYbuKxkL4t{J~OG;5o^ITgZAsH3t_B& z{ZNb!0@^0P8@cW#FM^=+8|XTrn(t&AiUdflmUe(S@P!wa?~{1yS^5Tk-cU)6e13;34eb>fhgqYp{?+?ufwN)0)_TWk-ybqpd-wAFzHwW5#$XJVE=$nap=rPCDxP_6ipu9jRAO+(P%b^vSDX|=kxn{(h->t~p(tSnOjS{lKt z)z)yBFqRv!zQ~&j0OA&a9QM)~=sE$@)Po{Urm>F3+FAo@V+mhk_-`P|8}PiS$XHiTBN(*Ni;dh&@=cdqR<0| zQTk51a_2_9&gAAX_255ldQ-5*%Ulpe_KPK?hROCBfl#;tQ z&#-qoUn}AVSHgj<8HfkU!A9ta)kkT!Az}=4YBPs)1#W{?Ol!70$AI=UOKeq_Ki`b) zVZq}6SIuq3o-3ty&J-EXI@Od^Pn>v<_Uib>qebpm+nh$9I9xuy0V@|YRsEd=$UCh1 zRaQ1hSaS&6?p7|W*_l@jhwC{KuKQ7uu{k{<#Y%{p&oeMSnP=Oc_5vaSymrOx_+ZLO zd%;;sS!K?KVdPBH%$cT{+q-vf+w2*RHZP7~k^AoW^36jp|M=NfB7S^pbyfGTt|A3H zr%T{U7WURwSPQ%%qUmnS^aIgthi!!Ewqmh+rXOZ}R>!JJ3@EG)6{gz$k#M7E001BW zNkl&Ax7jPDI zX>W~}9S`rlAbghS5AJjL{=&^a(Vg^w)25A#y6Ql zr7NBrmCZ1WSt;c#Fe`+ZEffl~S6p#LdvnIiaA^!Ea}HwH1n4wl)M3nXK4dwHZeV(VqIY*;&uP_T*t4Q7w(PB|RbsoW zxj~E^J@WJ64~=U00YJf#Y$z}6pwNN+dlzeav@PI|znNz&XiX$ED|`7W9VhyCGCz5v z1$~~IHl+^D@>s_Y!7EM-_SgWpW3AM7=a_;R9J4f`C#0w{7Cb*iq2SUkd%o)Asoily z&Iuv1KvpSLF>_?4l(`Cfwrx8%H#ax$IjS!NfCd4^FFDXXgS52fbIadSn9lsDC2x#? zGJEE1ZhdBgbgtAGvXj<;UAe*Y0nc`}CWj9PV-;KuYUjD6|NN>>vGs~m4fVB3i~{yi z@|6UY{WYD=&+778n>%21&Su9*kWlWbW;6fBU=*JTDZ020byU_~hqT ze7G>3xgj#u^P`nVvX0`;jnnL#&ey3#0_?g1!P`L_ER2ZaVc=bNK4n?d_YzD)+u@v9 zL9mU#vF=uhQGvOcjyK=Uf|QC5`#NGG)2LYgqfP{{d9uJnz80$^*&QdfAPzCuG41lV zKP}=!c;J8f1pMpm@U?s3t9Q57KU(Eb_&|rrg(j@@AWR9kah@#tD|hSb{-xXX)cjVm zdkQ}P?L2PTV`?HgiDElj z#47z@?49227sE&}+;@hq&jmZ68?}5XHBSnZDB$fCH>)3;lD&@;yY2^q2L{6)E(L7Z zpCj*C*r976LA+0oWIeR0#NXZ6I;c|711!ZCu8XR-*KGW;Nalgd^|V2@KqP4)WtFMC&ECm8 zLp=%NF=PG%HizQ@Xac-ViZsa7+$Q!;>4-9B63XkzhSwePvY7AjqvSg?0((kd)H9Y| zh!H!B$$aAl2pvEKT&bAJJ1pvsS0ni)IxLC>J!rUXzsu+}TyUZlI?#0DFce)_H3T1j zWwcp`-l`lI_NAdG6|Q(V1_^~YJYBRR0|G`B?DpKRof2MQwbzc*mcU#I&Rqc?yeNDX zobe2N^51hz6*M@DBpiQS2g#)7qS947v39IFFQOt*+H&f`+}xEXO>Y6xK-w^jv{Gue zGDs+dNCUI3>&^-x(idNRamXk~ba^2HG?YPl-<|(<$Kcsl=dG^JON8)q0~Pqw|73d$ z62-%tQ&XAj5eG-*E~G5PRUiI3ogUmaSc*Jkm!tI7agRl*s|9}V(^j?GmvFkCR}iw z9z2XXCEQ!h4JS+gs;bqaF%^df*wKWUVz(S$dHjFAAad_nkaN%*` z;GE9GHUF7s*GvtZk2}7Dt}gAdA*JHUXU9orDuW2oO&k45*ZJf*qnqLuGZj$w{#Q!P znx>hq?3YTVneTu9`v>e?_(C&rc*O>aPyfy7|C-sk<@c4{e(5 z6Xc4uHSa1~ElnCnK*HJ;*j7)%l7S0J#`XF?_AK@O&@Id>QQLGNItqnr*B+llYyi_R zx%{|T^`VL~SXfD$$vbQsuld0nrl5C82WE!>arpi{1^)S_QY4#nMV=dl-~Sf;uOH}z zXoF-T17EurN{(I^?}*9~U>W^FFXrl%AKQ9vv|na&@CVbXWr}lKjWEz+Y+Dy}&b6q!U7)I7GjO^u? zUw%-Al3&OG4H~2i>;C@2|Cw5M|0S;DmLHL5?75kYW^I16c8tw?GS!k%K=4q2c$E4> zj~kF!`6-0S$imlDk5e2KhXt4`dd+ex>#!)NrkRWl0D=_*an4^7q3Z@r+ubu;WSeKE zpl}k+E0T0~Th;r#bw`>#o;Vxr zu;+`9%}k#@l-m!?DW&Q_SqL#FgveIFxmYY_$H&JrEvk$xFLZ#0^1$Kq|MAu*_x|R) zXF3IYJXpnej+uSl}0zwz-3n!Mtd?b8KzP8F)mN>~QH$9Cwz zN^!VreTnOSQj8RbR%i+~0>oBlJllI=XQS#|;Gea z(T!L>bQuF(CzW#qaT7**1Z&4!B*E;tnT>iXTOw{5bS11Z@RkP-OD<9>rsgE~KI3x1 zNfte+;0s|07+CMF7jgj(=OCdtvV+~r$^(WK*{9%m6NWsE zI<{ZOKne4xQNV3ax?K0eyk4>@4rWp?vLZ!SR}Iqkk7jvp^HeAyYjzzh1NG4}Mt2x?U<(v&?nlu{MXjWkdl7<64X4a|J!JKs5|vHM<301Xw821f7w+QYpkzHx1=XYftJ z5T;i{R8_;O2v2epjAlxVP37qCh>?hwM?a20#}5PICdq;CBq0n|4qpzyo~io)aB~8C z+8{ol5LRgM*6nMd5yYCk;${WTjJ~qNedn^M?|hG{K~G%KpHMtEB^xSf{!l7VFqyT9 z83x@+eUicuq`G6cO5)m*xq{-CkJv03G#MF=?tYms!5{xv+n!%@GQ9V~Cd!8spo#Wn zbf~1a%$j$2Zr3&!E{R36x8`m5hZ{?L`}QI#rolPUZLs3F4pJSp$9(VJ49{)#fpZ(| z`7I|*kDa}EZVzQ(2GT-^>V^lLf!S;}J9Fu!mmc&vrY|;tu80FL{_wZAi0*|y>0ET` z+YHO-geEtpDPA{b<{j;lH=8Na-<2R{wPDVW@_@8~giTc=d?ZxTjvag%oykS)82>d2 zg)BmG)F9RaMjPw(9SmTu?7BIyC-t>m+5&%Kb#xZ4`OgB6Y_h9?k5auRBP%se1&Ap#=kk7qMW>~dvyS7;3L(;$UV7<4HEmSn#R||+X413kZ_n)g^}l?5 z!An1UOe~Q&UAs$%p;kMnsC6?%mu+J?X3_=vy5hu|ENa*8v(ff=PDHatvwDYmU&4Z( z)7dt9D^l4gsl7Xd0kIyMp2WP&836^oyY8$}ov5;O;cHpke}S*CHb}1Q6U-JR`{$yt z=h{IkX)}3;TuHK^Ggh6<5=&VamcS{ybUd@o;eqv%mmF`=8)EVR?eg`%f2Mt&KX?&b za#DT1E8*_vU@8ks`Zz2AZM(33FN|hjuroZk(7~29J7E1jST;cQ#g;7Fkk@-y^nMp! z6AP|GDY*4Xm(PAH$No7z7)Ts?7FaA>p29Nqm5@@g;kiln>f`osfdZ%wbtp+BD6yX`1P=v9a{^*I$1~hF@RI01X$>Phe!x8`(W^HwAkN z0DD^C<^=IUJGK0-f!BdPt%es zxy%(^20LRIWstTKCb7;KPRT_{#dKD3^CLFBohGXm8-eD6OaV5H>Cf@MdmF@kKEPkO z6aMZt_~kQj#!^^15Zs!M1kEjb)*78;w z@Q?#JK-h4(f>i;-IU!BV?c#Rda77{?F>m|&<8=S7tOl<3DmdTksUC9_J|I>MZ(h3aQwh=4eepx!= z3pwf7-)APfndv#btcaTvlqMkF4_=N^m~t0LvZG@@b)y5cJ$|p@LfHtpQ~SbcmVp24e-Dg{kuB>Z#cb{ zX8@~Bh}-aqE;uy+oS)dHxaOOAp55c>h;(s?br>vNk)pf1_FNST4$rO|Bb}+A1=J3E zUeZ-6ee#?-kB!PUYZyjaN;zwqW?D))TV~X0DP{U~uX|m@C4c58zYPElwZTJ`-L|H% z@AfbM_rSUD?~Nr>X9anAR7)|Tv?$TK;Uqcd)iN*-RIZdxV;l>LdO5$Cu? zZy^!uW%txQC?!$0ptw&UEP>S>GCW?cq*>ifkMEJv{+gTJsmnh?PsfT-U5OxW3Qk)H z+h$xU&ubL+TuW1wT*YXnNJq@1BVKmHGX#mQ7-rJIF8lTE7<2jAgPKhrzu0K7?{PPtV)Gxic=ODBGMXgMk&>vk)TfIL@qPSydOiOD?(OfMad6 zlHV49u1F;^w;H|YYj?-Gm;Wp|bmFCE%;;@YZI3;Np;6A3?3~CkI+;UBNl#}jV9%kJ zAPY?t?~XbhAsFsEk7EX3!On@>v7I?kU{7iHw^(1jm&d^Z=88^Xr-3Tz>vit&pC=Iu z@MrV1RUt413>aCq&nQYN*z+RX1X4b>gvq%Qh9T%l)lSfgo5Z^;99JTxLMnK8qs=qh z6ele+>sDzAyURm8+Q;{@(;#8h6a{eI&2ZDx@Z?Sy>V{K}X>z*ZSq*Zi0Q#p-z`xwC zr)7_4;B}|e@)8zYei~eLP9r!6i7VmuXC(jo+xha8Z3QvyLbrH%l7Ydv_Cf)afL(iL z*}QcMB^!HFRQ9|eWjmiZXKYJP!ktt~%^HSL9T>!8;-~<%Qfk(5ob-M7-Iu=m?z<0( z-PtQIhM9!ziVYNi{I=`=Xyap_U3zwA*N(rGj;c*O4K?P@!^&IEnkhIuvul=HAK$~H z>ql!b0Sui;R77I;8oS)r76M8<0i#1Z7XV+1qgu$j$)=! z0J0WtG;a0Kof>IcE}bj#_=Zt#e_%5^Htpj~V=vuC<+Hardk$)u0=7@&cy>>Qk|#ii z88hfz(n05FZ(F)clXpuI!qE zKm7@O;g{NN&@0|HCL)`?8s(AAiue9Yj_ZD$W7c-7UK)LiVyrl^lb)Vh<%N>V+I8b> z-!T*TNJe$5>rcAQ$Il+y+Mlo|DchO7gMpR(vUy|HvaH!ssWiJ~%a+*#4I>&=e%lyy zMHJchFz`0xprnwHtuwgDjuWvr$u=1MNR#GXw_5os6Cnqd>IpV6B*$1;rd5h!xvNLAMf)r_1zR$fDU$M- zJU|a1#=9&WDUq%&v~|DBEsxrCB(>jkSmd>!1_t6U8E5+3RlBlWx}UgMm&xuvwAghx8Z0 z>Bs7+?1?rld3&be%fBe`^;-+fmSojK!|D_)T9#s9Adca&9ZgN;S+`-Fd^tdL)UgGB z+Jcnr{L#7lw=C?iCo1l9)SEVDD-%>IrVc4(x>PF7yz5=>I&2}Yjsma9BQ7QoFK@Ba zKlAWMQj3oHOEYHFNA7LLp2Gk-WOPjU(gljXl*Qt~PL>XJ*SYG=`x`0(YmEZRVKSY4 zjysy$O>) zcte6qPBn=)1L}xwcDFtYpZlr){f_hC&o8Ux6>NC*zi1DVR2=U3RDIl!{b_jJzv|z= z`V%Je`$IOy175;PI_4LOl8V;8L(%h zlxfp6XM_+lhGEPYhB1BNg%=*uK|#O#4xqINs_$-M44%+&)+c}Vr9^M<+k{beDhmYY zCgAM(=S>$4>GibM5YU&3F+7lB$$~DD$tK^8?U=(u(htOlCY8gpJHF0io35ke)O=5k zI3$-#5{qTa9u{_dSrE)VszXH@Mx3`_^emI<4#p-j%y~h%$$b$5QkEiNN@6j=GtLkb zZvA`LKXJv+o@;tFow#MNa$zTfzL?=kD)O@>3ez^qR{9&wh3=TiRWFNk+1VC@U4fN* z|AmTD@ay&3_3X`O*0a@o310C}8mKK3?)+4}2T3!4PUm%5$JM}m@1NDjye>KLv!^uc ze*G!)pNXh|b-NYcyQ9Q|8*H%3z+49B&OVEU!wF*Xn&njhlhb*&ZJVZ49S96JThstr zHKqZ_^%V*qdCB;e?zlVUnX+9s38eziuIr}b@%W5w+i76tl1nb}hQm87@;iX8mkOJX z0r4eo`uwuRFa6+uB|4L*2djs(WP z*}@*~-|%N_-T#ZabRz+YC6dGvg|Hfr9NHEMbI({9vnSALUPy1^_b{Df8cRg@sxbs4 zEJ@0e#4W*d?jXCI?y9DrJ-1}ol{5tdU2#?nb&~M8T1crV1OT;(;EMATyx}~Hb-i3?prt z=FG(xUwlYr3bvBp0d%lbKspA*Px`}~F6$XN?VqfK=?R9|j%h=uw_?vBJJns&fUbl^ zUuT@b-V}rV9av3_oQDF8o5|j(`+4~Jzh-p$k-BtYKzxxTzDQwpDO5nO6EcaJJsE5E z48;C3NjRrr(pUGeHAuv-8qK67NlTJ6VUOxzy**gd_Y`f#p8a|TFx;19S%BF?x{7?d zL@`|kYM1gRSbeO;J6;~={NoK0@yc=9lXmp?r{T3_fc|v=KpzSjO~b8EINWf5iF97p z1G5PoJr?~7<0MnHJ}dyomF(I*%jnn~O8K9g=!htQ0fiwTX)01NMZ#2!(Z#c+1=VGk+GWpvNmzpA3pyF>X|!`I zsVL6b!=G#3jGNCUCcK7H>; zJBF5h#*CZ3Zr)^Iu=cYq-TyNZXjhYh)8nR~BVmz>n{=e&bf)5TcEsrJOkfZ&`F~6ZUVd7Pm!A^j^bv#pfLVz3 z$8BE@cCaL=G$=pvyyVWOY&Pz8G#FO~{tUnbGcM>Jh|%2}!-{!;89>40OrAY^(pB$_ ziW~@g?oT?-2Tz~axw5}724pI02zu8OL{B=Y#B`pmu;-%zoZ)u>-Hyck+iBN4{D;ZG zMW2ktJ+67}vgZh635VH(oaSJnM5lh672n33FNYJN}7PO{<6HJ<^* zO1aLs#W8~^`n!WYN1T$Rm?=@3bC4xf2h@5KoVv{7;?))>Ej3v&B$sUXD-WVxQ5dF%#St*#F&a-#lEZN-u-`=_Z#&ukG z{G6G&uYHgdMN$+MNwy?QjxEKGWh)P-u~O8I)52{6*D6}Lfsq#V4{ZtrC<^3<0Qw<7 z|ADlD(x7OX!U*Cv4HCC<8n?=$sq=8sw22Z&ajZm^Vv3|hav%3Ob54KQdoOpFyChd4 zDN4CtU@&t>!@F}=yPy5ubLSEFpFDI8?)lEq(Eq@_bFWq`y#TG9Q0mYMZ{(mBLe#Qx z&KaDOB&iu=ZfIDeT?L>AQ#RToO9}w2J3sj4$E#EOKdfvu)%k>bkkCfI`oY}uAkfBv zoai8Z?pMEhpEt@qw+pcq>WHPNJIz;5@0maI;!ERy_}B%t|2U_Ux7HfkSQek|YUkP# zc8+>5G34}|LCOH2Lj$p|A+9G&u6S;LwzSJre?^Mg?rI3^w+7t$=0# zs0RSkiwv*LGQ4`3;M_$6KRBO2GlnG~3+PCw5K2`AsQ`)=fcmCDFbsI5$lk`nVIWCh+4X?MvaJS#+X{ER9YmW z+KqZ3*h>YVuT28l0G;KB{^&FBsO~%ZQQNf-Lff*YI4ICM4tv!P0B3-nfL;;KJx{fY z<6oz1xi_uSL$@iI3|1)#4_y+Ez@ zJoeE8WB0xHBUZV5TuIRtjJ)ygIfu`n?sH-vj~qGT?c2A{ot&I>ckI~V((rAgpHGf|U|J8|-+rcRanD&Fy1!&Yo&{sWE^Hri z@?}kXi8MhH8qkq}BrsqiS^{)CtlJZ0Wn3z+q&)7xOUJ#ESSkw(!70gBawRBNE?YAq zcgw_Zb>4^Bxh6E4anENlYjMx3!TFwvX8p;-bLYzzEdoFT5$nv{=!P>h*E0t#rL09! zRF_iLW@l#?PnY)u7 z*gZ2KED+9Ko%zY-q+Oj}QnWWeY~D4h&bEWyAEAT@f*ugh0mxlo06HfQcDMcYzyv znq^tF?c296-hKDon>KxW5EX!4 zUkT`hbe`Vu$Y1Q<@s=k)VwHy;l1jAACmRH4J?a4l3J3Ie&x63Zt76bT_o!db2ZK$N zQ7vfv&&=08{_)z2pL!LRVBDSOus(kLxH@v=NNMlhz24;Hq&qS);w^}g?H~K;-ltxz zd5>_{!eZCzp8G*cAi_}DhT$|hYZxk-otqJW5(3r?l&%Xx#K4AIYlHv*o6Zg*V!F#W zf)EHoAqd$@N(qn(0n;ChUJ&k96lz$khfuFYP-{fMJ*MBUr+b#j@Zhe-{QC~h|H4(K z4k?(Kxe2L*h9zJxgEJA;jWKm8<>I^E^)B7xCANeLKyQo?`hXe$91+PE*AJ3xK> z_;K<0{WW0~(fqT?c@Mlrjf25!JQU^@ksRxYpgPt)>Fd8?SUiYbT_$^1=82-P&lHj?m zjUP&`0?$$4Sh8be$e@tz=Y~5Xam)}W27)kwAkYv5G5A3ONj8zBp9$!$TkG9L>z{hI zDJwGX*|%`{_}Z-+)oQhN|NZy-JuZFAsQ~om zDIK!E4AObN^VkO_#@_z^C+v~&ciXOWmyqe=orAjPTXa}mER?BheW|fH^R0#N{Nv|p z|NZyBfTa<{tEFBpx;^jEnKNf9QpzebS29QgK;@EekAL!&ohN>@SbmroEP%DaC%@KJ zZryvmAN|@P09ZHdMQfb9QUa6|AVe#aA-lFBXChz*Fw_c*XiZ>DDj07^(&7ZxO$;%ah;icX7*DUHxaQ(L_Hlew4j~ErRx(D z6Sb`xZ|L% zLL2?+JL^_*&jtnigmpg&&(~i6@$=Kq{Mo-kI3L5ZId~l)&W<(SzdZ_l1RYtPKg z3;{r;JH6Z(dc&t*89Q;lS$aEB=ep|a?VeY3!MrlW^OJFpzwKz(_M_R;6?eAj-){Zf zvqXk_C;WxS4lKO3vy#jcQ6Pk9GP4h@{brjPsEMd4rEFxmF{T65TI>2ZzVVIvW-YY8 zUMc{6mD@%}YP}>f9Iq@;~(aYz)ii+4Tc-FE`yC-WVw6$jtb{3oi@_Au5PiQA(AWxx&n4 zAw>CGbCvzio}YN)LeqXT1B%UZ&uQnD*DX6<`oCNYocp(1{$sllBU=Cf1Tr5U^J|YC zn7^>Q8qYIxfQUW-1kCJbkj~(oIcXxw$_=e`({Y@J5Tb#I_03+#dA(Esdg};bIh;Eo z-B}i6N8Y<_>}^jy?2hhyt7TX36t*=b6;!%Ii(Ab-8x$Ne1|jj~D5$*>UVZI*3*Y(l z*ZuE*VFs2@%UCuE8U_P&R)1YObLNaIrK}ob$^cMd=CYKs%*^z?LND^5HaR0W`K4z;`MUQq5n#y-ag066-__iU@&@!;Lh+a`>%h7()afXYQ7Tx7I_kR98PFzJZc#7rDB>43>3q~jXJ&Dq$$GNb1%T#8aSgD+J{=Z zB(4J9wZeHoSKO=JHHS7}fSB?Ma2CjL$F{Kc&Rw_*Ig3LxbLOah zDWz|W@e#2ZMUh{t)fy*GoVaO5XsksApl=pw4z9R0z3`Mz4B zwhs*Fjfg34uzMc!lJMAMWA-hRwaZggv&hUb07QrwtpMmOyb-iRnwf(P%$8;ONs{=p zv$M@xA)L`m1)y(j^@8(SAPqfo=yTi;&RKpfA)XU?2aTI;goIHe>>N z@et7i02csU0`R^$JG}4vbCoy#tnS=#*|&Fb$5N+D+Rz7sX>mQ>vjX7V)v$4BG`xCb z%%8hsB)BRB#>^ZubDV-XhcvWcoI@HBLjVYkF<}O1W)756ficD>qQJ5&KL`T$zV>Xdc#=+Tnz`z1v5k|fC?4H=wUj@lJMH~`=>v$K#` zL;p2BeDK_&xBs=KyXQ(M$2ncBb1e@Bm+CgpJ!fqM0#B9_|MuZ<;m*YK(Ivn53# zA|}k706@&l2_kmDIfFH%fJVfSh$10G9*<5#8zB(|8JLYR!SUnAo3}EUGZla?RA>Wq z>eMM~-@bh%rBnuE4**=PwWpMFjWHd77D718>;Qm+i1vJ>s^6Tc?0cnF+H*Ouc3cUq z(MBSw%+Paz(rb0kgHB38U>I>I9P=W7s*=*>U`1$=OjJO+;iOS zo)Ix3qTERvV{#|0lnUZF4lT)Y`s`;vE2pNWN<`#FQRKO< z>oT*OB#A4f%mK~Jj+D|NA{$ajGqa6|HULw>df(a}$5ujhlO_aqYIDXwi`-0RSU}(9CQAKqI1N zW}TK3BFaFWyJu*@I%4Kn2oVE74(Dz_Gjj+4q33zQ#fuk%C!TmB{8a*TrUKA~3T=Xz z8Nd78?|N|@yF}!MVdz?xs+dqA05UKl3b2vLk!%16$`-}Lwuy#q9gTY?7;%!AnHdo~9@sK72_Z}h zWrK)@h>Q>-UlA|`bMB(kJO+S-h+-i`Ohj?&oZ}46TI z#-J(yU8v9|i<#Bw)2B<7O2sk8IJRv&#+V$^aU5r`wv|#=25A7Wh{%#sDkACtG&4&f zgk)w30Fs#{A__zl%q*B$AR;oe000z105dQ9{--XLnVA8A5s|nhLPUdzL_`J=4FDKs z?f^8TkQPED%$z47L=NY+JTk__LWn4eqNq}-gtl#m&pr2Cc&m(KzFrl8E>!3SiJ9^A z>C?7lS#GIRavg>s2$dhn>%q)Z;tu;d$gN9aEBM);(DYY?X z8KiC7PQozM#+XEFomiF?HyVv32!iNlUc6|tRXFHEg|@hu8J~UjS!;ZJ+=iB8wx9)M z1ptbOazGQ2lv3vISxPC4F*%?!8Dmf>1;&`10a8lVT0=LWsdZ8^#*maU3vp1AB*t+Z z9k=$o(w^sOA%t!;8oE-cBt(=%QIu@uJ^!}43I|=N&=yzfuH!TTKx_D3rBYFz=UG~7 zSuU4ly3_Y zPzxbQ2w@z@F diff --git a/usr/share/icons/anon-icon-pack/IconApproved.png b/usr/share/icons/anon-icon-pack/IconApproved.png deleted file mode 100644 index 10e982a20bce86e6526821ac086ca1c19604e62f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125857 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 b)zAFv>+gHd!*}QW?mTh#_D?_fiI@Hl#wthH From a1a6af5a53371fe65c2ec5f5638d5d8cfa490c0a Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Tue, 8 Sep 2015 14:48:29 +0000 Subject: [PATCH 087/183] disable systemd-timesyncd by default as it conflicts with sdwdate - https://phabricator.whonix.org/T336 --- debian/sdwdate.postinst | 2 ++ .../system/systemd-timesyncd.service.d/40_sdwdate.conf | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 lib/systemd/system/systemd-timesyncd.service.d/40_sdwdate.conf diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 94a51820..1524327d 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -51,6 +51,8 @@ if [ -d /run/systemd/system ] ; then systemd-tmpfiles --create /usr/lib/tmpfiles.d/sdwdate.conf >/dev/null || true fi +service systemd-timesyncd stop >/dev/null 2>&1 || true + true "INFO: debhelper beginning here." #DEBHELPER# diff --git a/lib/systemd/system/systemd-timesyncd.service.d/40_sdwdate.conf b/lib/systemd/system/systemd-timesyncd.service.d/40_sdwdate.conf new file mode 100644 index 00000000..53e12388 --- /dev/null +++ b/lib/systemd/system/systemd-timesyncd.service.d/40_sdwdate.conf @@ -0,0 +1,6 @@ +## This file is part of Whonix. +## Copyright (C) 2012 - 2015 Patrick Schleizer +## See the file COPYING for copying conditions. + +[Unit] +ConditionPathExists=!/usr/lib/sdwdate From 64b86856b3af22480f497b31fbbc5c7f8e1b7dd9 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 10 Sep 2015 19:37:59 +0000 Subject: [PATCH 088/183] url comments in log --- usr/bin/sdwdate | 65 +++++++++++++------ .../python2.7/dist-packages/sdwdate/config.py | 53 ++++++++++----- 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index cf1ea94d..cae547ae 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -17,13 +17,18 @@ from sdwdate.timesanitycheck import timesanitycheck class Sdwdate(): def __init__(self): - self.pool_one, self.pool_two, self.pool_three = read_pools() + (self.pool_one_url, + self.pool_two_url, + self.pool_three_url, + self.pool_one_comment, + self.pool_two_comment, + self.pool_three_comment) = read_pools() self.iteration = 0 - self.range_pool_one = len(self.pool_one) - self.range_pool_two = len(self.pool_two) - self.range_pool_three = len(self.pool_three) + self.range_pool_one = len(self.pool_one_url) + self.range_pool_two = len(self.pool_two_url) + self.range_pool_three = len(self.pool_three_url) self.number_of_pools = 3 @@ -108,6 +113,20 @@ class Sdwdate(): logger.info(message) return False + def get_comment(self, remote): + ''' For logging the commnents, get the index of the url + to get it from pool_nnn_comment. + ''' + pools_url = [self.pool_one_url, self.pool_two_url, self.pool_three_url] + pools_comment = [self.pool_one_comment, self.pool_two_comment, self.pool_three_comment] + for i in range(len(pools_url)): + try: + url_index = pools_url[i].index(remote) + url_comment = pools_comment[i][url_index] + except ValueError: + pass + return url_comment + def time_sanity_check(self): status, time_one, time_two = timesanitycheck() @@ -285,42 +304,42 @@ class Sdwdate(): while url_index not in self.already_picked_index_pool_one: url_index = random.sample(range(self.range_pool_one), 1) - if len(self.already_picked_index_pool_one) >= len(self.pool_one): + if len(self.already_picked_index_pool_one) >= len(self.pool_one_url): self.message = ' Time is not set: no valid time returned from pool one' logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_one.append(url_index) - self.url_random_pool_one.append(self.pool_one[url_index[0]]) - self.url_random.append(self.pool_one[url_index[0]]) + self.url_random_pool_one.append(self.pool_one_url[url_index[0]]) + self.url_random.append(self.pool_one_url[url_index[0]]) if not self.pool_two_done: url_index = [] while url_index not in self.already_picked_index_pool_two: url_index = random.sample(range(self.range_pool_two), 1) - if len(self.already_picked_index_pool_two) >= len(self.pool_two): + if len(self.already_picked_index_pool_two) >= len(self.pool_two_url): self.message = ' Time is not set: no valid time returned from pool two' logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_two.append(url_index) - self.url_random_pool_two.append(self.pool_two[url_index[0]]) - self.url_random.append(self.pool_two[url_index[0]]) + self.url_random_pool_two.append(self.pool_two_url[url_index[0]]) + self.url_random.append(self.pool_two_url[url_index[0]]) if not self.pool_three_done: url_index = [] while url_index not in self.already_picked_index_pool_three: url_index = random.sample(range(self.range_pool_three), 1) - if len(self.already_picked_index_pool_three) >= len(self.pool_three): + if len(self.already_picked_index_pool_three) >= len(self.pool_three_url): self.message = 'Time is not set: no valid time returned from pool three' logger.warning(self.message) return self.error_icon, 'error' self.already_picked_index_pool_three.append(url_index) - self.url_random_pool_three.append(self.pool_three[url_index[0]]) - self.url_random.append(self.pool_three[url_index[0]]) + self.url_random_pool_three.append(self.pool_three_url[url_index[0]]) + self.url_random.append(self.pool_three_url[url_index[0]]) ## Fetch remotes. if len(self.url_random) > 0: @@ -403,13 +422,19 @@ class Sdwdate(): print(message) logger.info(message) - message = 'Valid urls %s' % (self.valid_urls) - print(message) - logger.info(message) - - message = 'Bad urls %s' % (self.invalid_urls) - print(message) - logger.info(message) + message = 'Valid urls:\n' + for i in range(len(self.valid_urls)): + url_comment = self.get_comment(self.valid_urls[i]) + message = message + '%s: "%s"\n' % (self.valid_urls[i], url_comment) + print(message.strip()) + logger.info(message.strip()) + + message = 'Bad urls:\n' + for i in range(len(self.invalid_urls)): + url_comment = self.get_comment(self.invalid_urls[i]) + message = message + '%s: "%s"\n' % (self.invalid_urls[i], url_comment) + print(message.strip()) + logger.info(message.strip()) message = 'Fetching remote times, end %s' % (time.time()) print(message) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index 49d921e3..a04127f8 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -10,15 +10,18 @@ def sort_pool(pool): number_of_pool_multi = 0 for i in range(len(pool)): if pool[i] == ('['): - number_of_pool_multi = number_of_pool_multi + 1 + number_of_pool_multi += 1 ## Dynamically create multi-line lists. - multi_list = [[] for i in range(number_of_pool_multi)] + multi_list_url = [[] for i in range(number_of_pool_multi)] + multi_list_comment = [[] for i in range(number_of_pool_multi)] ## Sort... multi_line = False multi_index = 0 - pool_single = [] + pool_single_url = [] + pool_single_comment = [] + for i in range(len(pool)): if multi_line and pool[i] == ']': multi_line = False @@ -27,7 +30,10 @@ def sort_pool(pool): elif multi_line and pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) if url != None: - multi_list[multi_index].append(url.group(1)) + multi_list_url[multi_index].append(url.group(1)) + comment = re.search(r'#(.*)"', pool[i]) + if comment != None: + multi_list_comment[multi_index].append(comment.group(1)) elif pool[i] == '[': multi_line = True @@ -35,15 +41,21 @@ def sort_pool(pool): elif pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) if url != None: - pool_single.append(url.group(1)) + pool_single_url.append(url.group(1)) + comment = re.search(r'#(.*)"', pool[i]) + if comment != None: + pool_single_comment.append(comment.group(1)) ## Pick a random url in each multi-line pool, ## append it to single url pool. for i in range(number_of_pool_multi): - single_url = multi_list[i][random.sample(range(len(multi_list[i])), 1)[0]] - pool_single.append(single_url) + single_ulr_index = random.sample(range(len(multi_list_url[i])), 1)[0] + single_url = multi_list_url[i][single_ulr_index] + single_comment = multi_list_comment[i][single_ulr_index] + pool_single_url.append(single_url) + pool_single_comment.append(single_comment) - return(pool_single) + return(pool_single_url, pool_single_comment) def read_pools(): SDWDATE_POOL_ONE = False @@ -54,9 +66,9 @@ def read_pools(): pool_two = [] pool_three = [] - pool_one_sorted = [] - pool_two_sorted = [] - pool_three_sorted = [] + pool_one_url = [] + pool_two_url = [] + pool_three_url = [] if os.path.exists('/etc/sdwdate-python.d/'): files = sorted(glob.glob('/etc/sdwdate-python.d/*')) @@ -99,15 +111,20 @@ def read_pools(): else: print('User configuration folder "/etc/sdwdate.d" does not exist.') - pool_one_sorted = sort_pool(pool_one) - pool_two_sorted = sort_pool(pool_two) - pool_three_sorted = sort_pool(pool_three) + pool_one_url, pool_one_comment = sort_pool(pool_one) + pool_two_url , pool_two_comment = sort_pool(pool_two) + pool_three_url, pool_three_comment = sort_pool(pool_three) - pool_one_sorted = list(set(pool_one_sorted)) - pool_two_sorted = list(set(pool_two_sorted)) - pool_three_sorted = list(set(pool_three_sorted)) + #pool_one_url = list(set(pool_one_url)) + #pool_two_url = list(set(pool_two_url)) + #pool_three_url = list(set(pool_three_url)) - return(pool_one_sorted, pool_two_sorted, pool_three_sorted) + return(pool_one_url, + pool_two_url, + pool_three_url, + pool_one_comment, + pool_two_comment, + pool_three_comment) if __name__ == "__main__": read_pools() From a05aa05cc56557919a284ee93e6228381c747f89 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 10 Sep 2015 20:30:08 +0000 Subject: [PATCH 089/183] calculations -> calcuations --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index cae547ae..1099c70c 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -164,7 +164,7 @@ class Sdwdate(): def add_subtract_nanoseconds(self): ''' - Could we replace this in sdwdate_loop pool_diff calcuations? + Could we replace this in sdwdate_loop pool_diff calculations? -> int(web_time) - old_unixtime ''' signs = ['+', '-'] From d4e6b015cd4c59d87747f781b9ac867e40bbd86b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 12 Sep 2015 15:23:36 +0000 Subject: [PATCH 090/183] url_to_unixtime returns all errors --- .../dist-packages/sdwdate/url_to_unixtime.py | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index b8dc0ba5..9f1ae3e1 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -10,7 +10,7 @@ from gevent import Timeout import socks from dateutil.parser import parse - +import re # Test import time urls = [] @@ -26,6 +26,9 @@ def unixtime_sanity_check(data, http_time, parsed_unixtime, url): print >> sys.stderr, 'http_time: %s' % (http_time) print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) print >> sys.stderr, 'parsed_unixtime not numeric!' + error = '%s' % (e) + urls.append(url) + unix_times.append(error) return unixtime_string_length_is = len(parsed_unixtime) @@ -39,6 +42,9 @@ def unixtime_sanity_check(data, http_time, parsed_unixtime, url): print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) print >> sys.stderr, 'parsed_unixtime has excessive string length!' + error = 'parsed_unixtime has excessive string length' + urls.append(url) + unix_times.append(error) return urls.append(url) @@ -56,15 +62,24 @@ def http_time_to_parsed_unixtime(data, http_time, url): print >> sys.stderr, 'HTTP header data:\n%s' % (data) print >> sys.stderr, 'http_time: %s' % (http_time) print >> sys.stderr, 'dateutil ValueError: %s' % (e) + error = '%s' % (e) + urls.append(url) + unix_times.append(error) return - #print(parsed_unixtime) + ## Tests ################################# + #parsed_unixtime = '%sA' % parsed_unixtime + #parsed_unixtime = '%s1' % parsed_unixtime + ########################################## unixtime_sanity_check(data, http_time, parsed_unixtime, url) def data_to_http_time(data, date_string_start_position, url): http_time = '' ## max accepted string length. http_time = data[date_string_start_position:date_string_start_position + 29].strip() + ## Test ################### + #http_time = http_time[:28] + ########################### http_time_string_length = len(http_time) @@ -74,12 +89,23 @@ def data_to_http_time(data, date_string_start_position, url): print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length print >> sys.stderr, 'HTTP header data:\n%s' % (data) print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) + error = 'HTTP header date string too short.' + urls.append(url) + unix_times.append(error) return + ## Test, replace current hour with 30 ####### + #http_time = re.sub('[hour]', '30', http_time) + ############################################# + with open('/var/run/sdwdate/http_time', 'a') as f: + f.write('%s\n' % http_time) #print http_time http_time_to_parsed_unixtime(data, http_time, url) def data_to_date_string_start_position(data, url): + ## Test ######################## + #data = re.sub('Date', 'Rate', data) + ################################ date_string_start_position = data.find('Date:') if date_string_start_position == -1: @@ -89,13 +115,15 @@ def data_to_date_string_start_position(data, url): if date_string_start_position == -1: ## "Date:" not found. print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) + error = 'Parsing HTTP header date failed' + urls.append(url) + unix_times.append(error) return else: date_string_start_position = date_string_start_position + 6 data_to_http_time(data, date_string_start_position, url) - def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): s = socks.socksocket() s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) @@ -105,16 +133,9 @@ def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): print 'CONNECTED "%s"' % url except IOError as e: - ## {{ wheezy compatibility - if str(e).startswith('__init__'): - urls.append(url) - unix_times.append('URL not found') - #print >> sys.stderr, 'connect error: URL "%s" not found.' % url - ## }} - else: - error = '%s' % (e) - urls.append(url) - unix_times.append(error) + error = '%s' % (e) + urls.append(url) + unix_times.append(error) return s.send('HEAD / HTTP/1.0\r\n\r\n') @@ -129,7 +150,6 @@ def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): data_to_date_string_start_position(data, url) - def url_to_unixtime(remotes): threads = [] timeout = gevent.Timeout() From 3e6afc9c236fddf5adb23fbbd6185a33c2505d56 Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Wed, 9 Sep 2015 17:16:40 +0000 Subject: [PATCH 091/183] timedatectl set-ntp false >/dev/null 2>&1 || true --- debian/sdwdate.postinst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 1524327d..63b2fd43 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -53,6 +53,8 @@ fi service systemd-timesyncd stop >/dev/null 2>&1 || true +timedatectl set-ntp false >/dev/null 2>&1 || true + true "INFO: debhelper beginning here." #DEBHELPER# From 888b3bea48c87ce2722576c77dcf3cfbf2cf3020 Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Wed, 9 Sep 2015 17:26:17 +0000 Subject: [PATCH 092/183] comment --- debian/sdwdate.postinst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 63b2fd43..7e43badb 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -53,6 +53,8 @@ fi service systemd-timesyncd stop >/dev/null 2>&1 || true +## Deletes /etc/system/sysinit.target.wants/systemd-timesyncd.service. +## Otherwise timedatectl still thinks systemd-timesyncd is enabled. timedatectl set-ntp false >/dev/null 2>&1 || true true "INFO: debhelper beginning here." From 8212c70133fbb306696e903116204791cfaf472f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 12 Sep 2015 17:16:54 +0000 Subject: [PATCH 093/183] parsed_unixtime string length too short in unixtime_sanity_check --- usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py index 9f1ae3e1..0c43a0e2 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py @@ -47,6 +47,12 @@ def unixtime_sanity_check(data, http_time, parsed_unixtime, url): unix_times.append(error) return + if unixtime_string_length_is < unixtime_string_length_max: + error = 'parsed_unixtime string length too short' + urls.append(url) + unix_times.append(error) + return + urls.append(url) unix_times.append(parsed_unixtime) @@ -70,6 +76,7 @@ def http_time_to_parsed_unixtime(data, http_time, url): ## Tests ################################# #parsed_unixtime = '%sA' % parsed_unixtime #parsed_unixtime = '%s1' % parsed_unixtime + #parsed_unixtime = parsed_unixtime[:9] ########################################## unixtime_sanity_check(data, http_time, parsed_unixtime, url) From c34494ecdbba5a0dd8c71a9460d9393034ece91e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 14 Sep 2015 14:55:10 +0000 Subject: [PATCH 094/183] detach url_to_unixtime (executable), new remote_times.py to get sever times asynchronously --- usr/bin/sdwdate | 6 +- .../dist-packages/sdwdate/remote_times.py | 46 +++++ .../dist-packages/sdwdate/url_to_unixtime.py | 191 ------------------ usr/lib/sdwdate/url_to_unixtime | 158 +++++++++++++++ 4 files changed, 207 insertions(+), 194 deletions(-) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/remote_times.py delete mode 100644 usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py create mode 100755 usr/lib/sdwdate/url_to_unixtime diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 1099c70c..5fdca462 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -10,7 +10,7 @@ from random import randint from subprocess import Popen, call, PIPE, check_output import pickle -from sdwdate.url_to_unixtime import url_to_unixtime +from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools from sdwdate.timesanitycheck import timesanitycheck @@ -347,12 +347,12 @@ class Sdwdate(): print(message) logger.info(message) - self.urls, self.returned_values = url_to_unixtime(self.url_random) + self.urls, self.returned_values = get_time_from_servers(self.url_random) if len(self.urls) == 0: if retrying_loop: retrying_loop = False - self.message = 'No values returned from url_to_unixtime.
      Internet connection might be down.' + self.message = 'No values returned from servers.
      Internet connection might be down.' return self.error_icon, 'error' else: retrying_loop = True diff --git a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py new file mode 100644 index 00000000..96b84cd3 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +## Copyright (C) 2015 troubadour +## Copyright (C) 2015 Patrick Schleizer +## See the file COPYING for copying conditions. + +import gevent +from gevent.subprocess import Popen, PIPE + +def get_time_from_servers(remotes): + url_to_unixtime_path = '/usr/lib/sdwdate/url_to_unixtime' + + threads = [] + urls = [] + unix_times = [] + seconds = 10 + + ### Clear lists. + del threads[:] + del urls[:] + del unix_times[:] + + for i in range(len(remotes)): + threads.append(Popen([url_to_unixtime_path, + '127.0.0.1', + '9050', + remotes[i], + '80', + '0'], stdout=PIPE)) + + for i in range(len(threads)): + gevent.wait([threads[i]], timeout=seconds) + + for i in range(len(threads)): + if threads[i].poll() is not None: + urls.append(remotes[i]) + reply = threads[i].stdout.read() + unix_times.append(reply.strip()) + else: + urls.append(remotes[i]) + unix_times.append('Timeout') + + return urls, unix_times + +if __name__ == "__main__": + get_time_from_servers(remotes) \ No newline at end of file diff --git a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py b/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py deleted file mode 100644 index 0c43a0e2..00000000 --- a/usr/lib/python2.7/dist-packages/sdwdate/url_to_unixtime.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python - -## Copyright (C) 2015 troubadour -## Copyright (C) 2015 Patrick Schleizer -## See the file COPYING for copying conditions. - -import sys -import gevent.monkey -gevent.monkey.patch_socket() -from gevent import Timeout -import socks -from dateutil.parser import parse -import re # Test -import time - -urls = [] -unix_times = [] - -def unixtime_sanity_check(data, http_time, parsed_unixtime, url): - try: - unixtime_digit = int(parsed_unixtime) - - except ValueError as e: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'parsed_unixtime not numeric!' - error = '%s' % (e) - urls.append(url) - unix_times.append(error) - return - - unixtime_string_length_is = len(parsed_unixtime) - unixtime_string_length_max = 10 - - if unixtime_string_length_is > unixtime_string_length_max: - print >> sys.stderr, 'parsed_unixtime conversion failed!' - print >> sys.stderr, 'data: %s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'parsed_unixtime: %s' % (parsed_unixtime) - print >> sys.stderr, 'unixtime_string_length_is: %s' % (unixtime_string_length_is) - print >> sys.stderr, 'unixtime_string_length_max: %s' % (unixtime_string_length_max) - print >> sys.stderr, 'parsed_unixtime has excessive string length!' - error = 'parsed_unixtime has excessive string length' - urls.append(url) - unix_times.append(error) - return - - if unixtime_string_length_is < unixtime_string_length_max: - error = 'parsed_unixtime string length too short' - urls.append(url) - unix_times.append(error) - return - - urls.append(url) - unix_times.append(parsed_unixtime) - -def http_time_to_parsed_unixtime(data, http_time, url): - try: - ## Thanks to: - ## eumiro - ## http://stackoverflow.com/a/3894047/2605155 - parsed_unixtime = parse(http_time).strftime('%s') - - except ValueError as e: - print >> sys.stderr, 'Parsing http_time from server failed!' - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'http_time: %s' % (http_time) - print >> sys.stderr, 'dateutil ValueError: %s' % (e) - error = '%s' % (e) - urls.append(url) - unix_times.append(error) - return - - ## Tests ################################# - #parsed_unixtime = '%sA' % parsed_unixtime - #parsed_unixtime = '%s1' % parsed_unixtime - #parsed_unixtime = parsed_unixtime[:9] - ########################################## - unixtime_sanity_check(data, http_time, parsed_unixtime, url) - -def data_to_http_time(data, date_string_start_position, url): - http_time = '' - ## max accepted string length. - http_time = data[date_string_start_position:date_string_start_position + 29].strip() - ## Test ################### - #http_time = http_time[:28] - ########################### - - http_time_string_length = len(http_time) - - ## min string length = max string length. - if http_time_string_length < 29: - print >> sys.stderr, 'HTTP header date string too short.' - print >> sys.stderr, 'HTTP header date length: %s' % http_time_string_length - print >> sys.stderr, 'HTTP header data:\n%s' % (data) - print >> sys.stderr, 'HTTP header date value: "%s"' % (http_time) - error = 'HTTP header date string too short.' - urls.append(url) - unix_times.append(error) - return - - ## Test, replace current hour with 30 ####### - #http_time = re.sub('[hour]', '30', http_time) - ############################################# - with open('/var/run/sdwdate/http_time', 'a') as f: - f.write('%s\n' % http_time) - #print http_time - http_time_to_parsed_unixtime(data, http_time, url) - -def data_to_date_string_start_position(data, url): - ## Test ######################## - #data = re.sub('Date', 'Rate', data) - ################################ - date_string_start_position = data.find('Date:') - - if date_string_start_position == -1: - ## not found, check if lowercase. - date_string_start_position = data.find('date:') - - if date_string_start_position == -1: - ## "Date:" not found. - print >> sys.stderr, 'Parsing HTTP header date failed: "%s"' % (url) - error = 'Parsing HTTP header date failed' - urls.append(url) - unix_times.append(error) - return - - else: - date_string_start_position = date_string_start_position + 6 - data_to_http_time(data, date_string_start_position, url) - -def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): - s = socks.socksocket() - s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) - - try: - s.connect((url, remote_port)) - print 'CONNECTED "%s"' % url - - except IOError as e: - error = '%s' % (e) - urls.append(url) - unix_times.append(error) - return - - s.send('HEAD / HTTP/1.0\r\n\r\n') - data = '' - buf = s.recv(1024) - print 'SENDING "%s"' % url - while len(buf): - data += buf - buf = s.recv(1024) - s.close() - print 'RECEIVED "%s"' % url - - data_to_date_string_start_position(data, url) - -def url_to_unixtime(remotes): - threads = [] - timeout = gevent.Timeout() - timer = [] - seconds = 10 - - ## Clear lists. - del urls[:] - del unix_times[:] - - print 'GEVENT started' - - for i in range(len(remotes)): - timer.append(timeout.start_new(seconds)) - args = (request_data_from_remote_server, '127.0.0.1', '9050', remotes[i], 80) - threads.append(gevent.spawn(*args)) - - for i in range(len(remotes)): - try: - threads[i].join(timeout=timer[i]) - except Timeout: - urls.append(threads[i].args[2]) - unix_times.append('Timeout') - gevent.kill(threads[i]) - - ## Cleanup before next round. - ## On fast retries, unfinished greenlets might be returned. - for i in range(len(threads)): - gevent.kill(threads[i]) - - print 'GEVENT exiting' - return urls, unix_times diff --git a/usr/lib/sdwdate/url_to_unixtime b/usr/lib/sdwdate/url_to_unixtime new file mode 100755 index 00000000..277c71ba --- /dev/null +++ b/usr/lib/sdwdate/url_to_unixtime @@ -0,0 +1,158 @@ +#!/usr/bin/python + +## Copyright (C) 2015 troubadour +## Copyright (C) 2015 Patrick Schleizer +## See the file COPYING for copying conditions. + +## Usage: +## /usr/lib/sdwdate/url_to_unixtime socket_ip socket_port url remote_port verbosity + +## Example: +## /usr/lib/sdwdate/url_to_unixtime 127.0.0.1 9050 check.torproject.org 80 true + +import sys, socks +from dateutil.parser import parse +import re # Test + +def data_to_date_string_start_position(data): + ## Test ######################## + #data = re.sub('Date', 'Rate', data) + ################################ + date_string_start_position = data.find('Date:') + + if date_string_start_position == -1: + ## not found, check if lowercase. + date_string_start_position = data.find('date:') + + if date_string_start_position == -1: + ## "Date:" not found. + print 'Parsing HTTP header date failed.' + print 'HTTP header data:\n%s' % (data) + sys.exit() + + date_string_start_position = date_string_start_position + 6 + return date_string_start_position + +def data_to_http_time(data, date_string_start_position): + http_time = '' + ## max accepted string length. + http_time = data[date_string_start_position:date_string_start_position + 29].strip() + ## Test ################### + #http_time = http_time[:28] + ########################### + http_time_string_length = len(http_time) + + ## min string length = max string length. + if http_time_string_length < 29: + print 'HTTP header date string too short.' + print 'HTTP header date length: %s' % http_time_string_length + print 'HTTP header data:\n%s' % (data) + print 'HTTP header date value: "%s"' % (http_time) + sys.exit() + + ## Test, replace current hour with 30 ####### + #http_time = re.sub('11', '30', http_time) + ############################################# + return http_time + +def unixtime_sanity_check(data, http_time, parsed_unixtime): + try: + unixtime_digit = int(parsed_unixtime) + + except ValueError as e: + print 'parsed_unixtime conversion failed!' + print 'data: %s' % (data) + print 'http_time: %s' % (http_time) + print 'parsed_unixtime: %s' % (parsed_unixtime) + print 'parsed_unixtime not numeric!' + sys.exit() + + unixtime_string_length_is = len(parsed_unixtime) + unixtime_string_length_max = 10 + + if unixtime_string_length_is > unixtime_string_length_max: + print 'parsed_unixtime conversion failed!' + print 'data: %s' % (data) + print 'http_time: %s' % (http_time) + print 'parsed_unixtime: %s' % (parsed_unixtime) + print 'unixtime_string_length_is: %s' % (unixtime_string_length_is) + print 'unixtime_string_length_max: %s' % (unixtime_string_length_max) + print 'parsed_unixtime has excessive string length!' + sys.exit() + + #print >> sys.stderr, parsed_unixtime + return parsed_unixtime + +def request_data_from_remote_server(socket_ip, socket_port, url, remote_port): + s = socks.socksocket() + s.setproxy(socks.PROXY_TYPE_SOCKS5, socket_ip, socket_port) + + try: + s.connect((url, remote_port)) + + except Exception as e: + print 'connect error: %s' % (e) + sys.exit() + + s.send('HEAD / HTTP/1.0\r\n\r\n') + + data = '' + buf = s.recv(1024) + while len(buf): + data += buf + buf = s.recv(1024) + s.close() + + return data + +def http_time_to_parsed_unixtime(data, http_time): + try: + ## Thanks to: + ## eumiro + ## http://stackoverflow.com/a/3894047/2605155 + parsed_unixtime = parse(http_time).strftime('%s') + + except ValueError as e: + print 'Parsing http_time from server failed!' + print 'HTTP header data:\n%s' % (data) + print 'http_time: %s' % (http_time) + print 'dateutil ValueError: %s' % (e) + sys.exit() + + ## Tests ################################# + #parsed_unixtime = '%sA' % parsed_unixtime + #parsed_unixtime = '%s1' % parsed_unixtime + ########################################## + return(parsed_unixtime) + +def parse_command_line_parameters(): + try: + socket_ip = sys.argv[1] + socket_port = int(sys.argv[2]) + url = sys.argv[3] + remote_port = int(sys.argv[4]) + verbosity = sys.argv[5] + + except (IndexError) as e: + print "Parsing command line parameter failed. | e: %s" % (e) + sys.exit() + + return(socket_ip, socket_port, url, remote_port, verbosity) + +def output_unixtime(data, http_time, parsed_unixtime, unixtime, verbosity): + if verbosity == "true": + print 'data: %s' % (data) + print 'http_time: %s' % (http_time) + print 'parsed_unixtime: %s' % (parsed_unixtime) + print '%s' % unixtime + +def main(): + socket_ip, socket_port, url, remote_port, verbosity = parse_command_line_parameters() + data = request_data_from_remote_server(socket_ip, socket_port, url, remote_port) + date_string_start_position = data_to_date_string_start_position(data) + http_time = data_to_http_time(data, date_string_start_position) + parsed_unixtime = http_time_to_parsed_unixtime(data, http_time) + unixtime = unixtime_sanity_check(data, http_time, parsed_unixtime) + output_unixtime(data, http_time, parsed_unixtime, unixtime, verbosity) + +main() From becab9fc52676fa274509f39c7aa269dd926e18d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 14 Sep 2015 20:28:31 +0000 Subject: [PATCH 095/183] reorganize logging and icon/message in main loop --- usr/bin/sdwdate | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 5fdca462..549c5040 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -9,6 +9,7 @@ import random from random import randint from subprocess import Popen, call, PIPE, check_output import pickle +import re from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools @@ -441,7 +442,7 @@ class Sdwdate(): logger.info(message) last_shell_date = check_output('date').strip() - self.message = 'Last run (on ' + last_shell_date + ') was successful.' + self.message = 'Last run (on ' + last_shell_date + ') was successful' retrying_loop = False return self.success_icon, 'success' @@ -492,26 +493,29 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - message = sdwdate.message + '
      Sleeping.' - sdwdate.write_status(icon, message) sleep_time = 10 + log_level = 'info' elif status == 'retry': - logger.warning(sdwdate.message) - print(sdwdate.message) - sdwdate.write_status(icon, sdwdate.message) - #print icon sleep_time = 0.1 + log_level = 'warning' elif status == 'error': - logger.warning(sdwdate.message) - print(sdwdate.message) - sdwdate.write_status(icon, sdwdate.message) sleep_time = 10 + log_level = 'warning' + + message = '%s.
      Sleeping for %s minutes.' % (sdwdate.message, sleep_time) + stripped_message = re.sub('<[^<]+?>', '', message) + + sdwdate.write_status(icon, message) + + if log_level == 'info': + logger.info(stripped_message) + elif log_level == 'warning': + logger.warning(stripped_message) + + print(stripped_message) - message = 'Sleeping for %s minutes' % (sleep_time) - print(message) - logger.info(message) time.sleep(sleep_time * 60) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() From 2d738b987274a48e1e0e8e16305005d33fd58641 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 15 Sep 2015 12:29:13 +0000 Subject: [PATCH 096/183] replace doulble-quotes by squares brackets in messages: double-quotes mess up argument parsing in dwdate-gui popup --- usr/bin/sdwdate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 549c5040..fa9d3603 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -134,10 +134,10 @@ class Sdwdate(): if status == 'sane': self.message = 'The clock is %s
      Current time "%s"' % (status, time_one) elif status == 'slow': - self.message = ('The clock is %s.
      Current time "%s" is less than the build timestamp "%s"' + self.message = ('The clock is %s.
      Current time [%s] is less than the build timestamp [%s]' % (status, time_one, time_two)) elif status == 'fast': - self.message = ('The clock is %s.
      Current time "%s" is greater than the expiration timestamp "%s"' + self.message = ('The clock is %s.
      Current time [%s] is greater than the expiration timestamp [%s]' % (status, time_one, time_two)) return status From 608f9c06d60b0c8d0e91ae13ceaa9bf5e0111bf1 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 15 Sep 2015 14:52:05 +0000 Subject: [PATCH 097/183] url_to_unixtime AppArmor profile --- etc/apparmor.d/usr.lib.sdwdate.url_to_unixtime | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 etc/apparmor.d/usr.lib.sdwdate.url_to_unixtime diff --git a/etc/apparmor.d/usr.lib.sdwdate.url_to_unixtime b/etc/apparmor.d/usr.lib.sdwdate.url_to_unixtime new file mode 100644 index 00000000..aba3034a --- /dev/null +++ b/etc/apparmor.d/usr.lib.sdwdate.url_to_unixtime @@ -0,0 +1,10 @@ +# Last Modified: Mon Sep 14 11:33:39 2015 +#include + +/usr/lib/sdwdate/url_to_unixtime { + #include + #include + + /usr/lib/sdwdate/url_to_unixtime r, + +} From d55196a1ed85c7f88e4b94d7d2a5b99c0895dc8e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 16 Sep 2015 15:25:16 +0000 Subject: [PATCH 098/183] strip HTML in sdwdate_loop time sanity check --- usr/bin/sdwdate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index fa9d3603..74dd51ad 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -132,7 +132,7 @@ class Sdwdate(): status, time_one, time_two = timesanitycheck() if status == 'sane': - self.message = 'The clock is %s
      Current time "%s"' % (status, time_one) + self.message = 'The clock is %s. Current time "%s".' % (status, time_one) elif status == 'slow': self.message = ('The clock is %s.
      Current time [%s] is less than the build timestamp [%s]' % (status, time_one, time_two)) @@ -274,6 +274,7 @@ class Sdwdate(): print self.message logger.info(self.message) else: + stripped_message = re.sub('<[^<]+?>', '', self.message) print self.message return self.error_icon, 'error' From 7fcf959452a35e909e595791683bac4fd5122dfa Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 16 Sep 2015 18:20:32 +0000 Subject: [PATCH 099/183] logging: readable date beside unixtime and webtime-oldtime difference in pools --- usr/bin/sdwdate | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 74dd51ad..e66ad7f9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -5,6 +5,7 @@ import logging import signal import os import time +from datetime import datetime import random from random import randint from subprocess import Popen, call, PIPE, check_output @@ -394,9 +395,13 @@ class Sdwdate(): ## Values are returned randomly. Get the index of the url. index = self.valid_urls.index(valid_url) ## Pool matching web time. - web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - int(old_unixtime)) - message = 'Pool one: last_url %s, web_time %s' % (valid_url, web_time) + web_unixtime = int(self.unixtimes[index]) + web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + pool_one_diff = int(web_unixtime) - int(old_unixtime) + self.pools_diff.append(pool_one_diff) + message = ('Pool one: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (valid_url, web_unixtime, web_time, pool_one_diff)) print(message) logger.info(message) @@ -406,9 +411,13 @@ class Sdwdate(): if self.pool_two_done: valid_url = self.url_random_pool_two[i] index = self.valid_urls.index(valid_url) - web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - int(old_unixtime)) - message = 'Pool two: last_url %s, web_time %s' % (valid_url, web_time) + web_unixtime = int(self.unixtimes[index]) + web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + pool_two_diff = int(web_unixtime) - int(old_unixtime) + self.pools_diff.append(int(web_unixtime) - int(old_unixtime)) + message = ('Pool two: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (valid_url, web_unixtime, web_time, pool_two_diff)) print(message) logger.info(message) @@ -418,9 +427,13 @@ class Sdwdate(): if self.pool_three_done: valid_url = self.url_random_pool_three[i] index = self.valid_urls.index(valid_url) - web_time = self.unixtimes[index] - self.pools_diff.append(int(web_time) - int(old_unixtime)) - message = 'Pool three: last_url %s, web_time %s' % (valid_url, web_time) + web_unixtime = int(self.unixtimes[index]) + web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + pool_three_diff = int(web_unixtime) - int(old_unixtime) + self.pools_diff.append(int(web_unixtime) - int(old_unixtime)) + message = ('Pool three: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (valid_url, web_unixtime, web_time, pool_three_diff)) print(message) logger.info(message) From 82e989090b5edaa646a0b0fa0ac76e965497c4be Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 16 Sep 2015 18:41:18 +0000 Subject: [PATCH 100/183] readable date in "Fetching remote times start/end" --- usr/bin/sdwdate | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e66ad7f9..c98ce177 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -80,10 +80,6 @@ class Sdwdate(): self.message = '' - message = 'Fetching remote times, start %s' % (time.time()) - print(message) - logger.info(message) - def general_proxy_error(self, pools): ''' This error occurs (at least) when Tor is not running. @@ -270,6 +266,14 @@ class Sdwdate(): Append valid urls if time is returned, otherwise restart a cycle with a new random url, until every pool has a time value. ''' + start_unixtime = time.time() + start_time = (datetime.strftime(datetime.fromtimestamp(start_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + message = ('Fetching remote times, start %s (unixtime %s)' + % (start_time, start_unixtime)) + print(message) + logger.info(message) + time_sanity_check = self.time_sanity_check() if time_sanity_check == 'sane': print self.message @@ -451,7 +455,11 @@ class Sdwdate(): print(message.strip()) logger.info(message.strip()) - message = 'Fetching remote times, end %s' % (time.time()) + end_unixtime = time.time() + end_time = (datetime.strftime(datetime.fromtimestamp(start_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + message = ('Fetching remote times, end %s (unixtime %s)' + % (end_time, end_unixtime)) print(message) logger.info(message) From f24cdf57c29525b327e1ff7ee8b5830e6202ddd4 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 16 Sep 2015 19:10:34 +0000 Subject: [PATCH 101/183] logging: more verbose when stting time with date or sclockadj --- usr/bin/sdwdate | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index c98ce177..b8ced88e 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -215,7 +215,7 @@ class Sdwdate(): sclockadj = Popen(cmd) self.sclockadj_pid = sclockadj.pid - message = 'Running sclockaj, PID=%s' % (self.sclockadj_pid) + message = 'Gradually adjusting the time by running sclockaj, PID=%s' % (self.sclockadj_pid) print(message) logger.info(message) @@ -235,10 +235,6 @@ class Sdwdate(): call(cmd, shell=True) def set_time_using_date(self): - message = 'Setting time using date.' - print(message) - logger.info(message) - old_unixtime = float('%.9f' % (time.time())) message = 'Old unixttime: %s' % (str(old_unixtime)) print(message) @@ -253,6 +249,10 @@ class Sdwdate(): cmd = 'sudo /bin/date --set @' + str(new_unixtime) call(cmd, shell=True) + message = 'Instantly setting the time by using command "%s"' % cmd + print(message) + logger.info(message) + def write_status(self, *args): self.status['icon'] = args[0] self.status['message'] = args[1] From a886f971dc497062669a83a1ee7316270779d1ec Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 17 Sep 2015 20:23:01 +0000 Subject: [PATCH 102/183] typo --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index b8ced88e..9a0ccb53 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -215,7 +215,7 @@ class Sdwdate(): sclockadj = Popen(cmd) self.sclockadj_pid = sclockadj.pid - message = 'Gradually adjusting the time by running sclockaj, PID=%s' % (self.sclockadj_pid) + message = 'Gradually adjusting the time by running sclockadj, PID=%s' % (self.sclockadj_pid) print(message) logger.info(message) From bcdd11cfaf0df472769721c9df04edfa2c593bbb Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 17 Sep 2015 20:34:47 +0000 Subject: [PATCH 103/183] reachable unreachable url --- usr/bin/sdwdate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 9a0ccb53..075e1f1c 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -441,14 +441,14 @@ class Sdwdate(): print(message) logger.info(message) - message = 'Valid urls:\n' + message = 'Reachable urls:\n' for i in range(len(self.valid_urls)): url_comment = self.get_comment(self.valid_urls[i]) message = message + '%s: "%s"\n' % (self.valid_urls[i], url_comment) print(message.strip()) logger.info(message.strip()) - message = 'Bad urls:\n' + message = 'Unreachable urls:\n' for i in range(len(self.invalid_urls)): url_comment = self.get_comment(self.invalid_urls[i]) message = message + '%s: "%s"\n' % (self.invalid_urls[i], url_comment) From a1be36291c2372eb0e067d0816195576cec57faa Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 19 Sep 2015 20:18:56 +0000 Subject: [PATCH 104/183] remove while loop in pool append block --- usr/bin/sdwdate | 51 +++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 075e1f1c..b110e8ef 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -308,45 +308,42 @@ class Sdwdate(): if not self.pool_one_done: url_index = [] - while url_index not in self.already_picked_index_pool_one: - url_index = random.sample(range(self.range_pool_one), 1) + url_index = random.sample(range(self.range_pool_one), 1) - if len(self.already_picked_index_pool_one) >= len(self.pool_one_url): - self.message = ' Time is not set: no valid time returned from pool one' - logger.warning(self.message) - return self.error_icon, 'error' + if len(self.already_picked_index_pool_one) >= len(self.pool_one_url): + self.message = ' Time is not set: no valid time returned from pool one' + logger.warning(self.message) + return self.error_icon, 'error' - self.already_picked_index_pool_one.append(url_index) - self.url_random_pool_one.append(self.pool_one_url[url_index[0]]) - self.url_random.append(self.pool_one_url[url_index[0]]) + self.already_picked_index_pool_one.append(url_index) + self.url_random_pool_one.append(self.pool_one_url[url_index[0]]) + self.url_random.append(self.pool_one_url[url_index[0]]) if not self.pool_two_done: url_index = [] - while url_index not in self.already_picked_index_pool_two: - url_index = random.sample(range(self.range_pool_two), 1) + url_index = random.sample(range(self.range_pool_two), 1) - if len(self.already_picked_index_pool_two) >= len(self.pool_two_url): - self.message = ' Time is not set: no valid time returned from pool two' - logger.warning(self.message) - return self.error_icon, 'error' + if len(self.already_picked_index_pool_two) >= len(self.pool_two_url): + self.message = ' Time is not set: no valid time returned from pool two' + logger.warning(self.message) + return self.error_icon, 'error' - self.already_picked_index_pool_two.append(url_index) - self.url_random_pool_two.append(self.pool_two_url[url_index[0]]) - self.url_random.append(self.pool_two_url[url_index[0]]) + self.already_picked_index_pool_two.append(url_index) + self.url_random_pool_two.append(self.pool_two_url[url_index[0]]) + self.url_random.append(self.pool_two_url[url_index[0]]) if not self.pool_three_done: url_index = [] - while url_index not in self.already_picked_index_pool_three: - url_index = random.sample(range(self.range_pool_three), 1) + url_index = random.sample(range(self.range_pool_three), 1) - if len(self.already_picked_index_pool_three) >= len(self.pool_three_url): - self.message = 'Time is not set: no valid time returned from pool three' - logger.warning(self.message) - return self.error_icon, 'error' + if len(self.already_picked_index_pool_three) >= len(self.pool_three_url): + self.message = 'Time is not set: no valid time returned from pool three' + logger.warning(self.message) + return self.error_icon, 'error' - self.already_picked_index_pool_three.append(url_index) - self.url_random_pool_three.append(self.pool_three_url[url_index[0]]) - self.url_random.append(self.pool_three_url[url_index[0]]) + self.already_picked_index_pool_three.append(url_index) + self.url_random_pool_three.append(self.pool_three_url[url_index[0]]) + self.url_random.append(self.pool_three_url[url_index[0]]) ## Fetch remotes. if len(self.url_random) > 0: From 4211395262ac163d4cc90ddb12628b82856399d9 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 12:01:47 +0000 Subject: [PATCH 105/183] modify config.py to return data per pool (not all pools at once) --- .../python2.7/dist-packages/sdwdate/config.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index a04127f8..cea7c676 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -57,7 +57,7 @@ def sort_pool(pool): return(pool_single_url, pool_single_comment) -def read_pools(): +def read_pools(pool): SDWDATE_POOL_ONE = False SDWDATE_POOL_TWO = False SDWDATE_POOL_THREE = False @@ -115,16 +115,12 @@ def read_pools(): pool_two_url , pool_two_comment = sort_pool(pool_two) pool_three_url, pool_three_comment = sort_pool(pool_three) - #pool_one_url = list(set(pool_one_url)) - #pool_two_url = list(set(pool_two_url)) - #pool_three_url = list(set(pool_three_url)) + pool_url = [pool_one_url, pool_two_url, pool_three_url] + pool_comment = [pool_one_comment, pool_two_comment, pool_three_comment] + + return(pool_url[pool], + pool_comment[pool]) - return(pool_one_url, - pool_two_url, - pool_three_url, - pool_one_comment, - pool_two_comment, - pool_three_comment) if __name__ == "__main__": read_pools() From 34a5b970b1199d801f6fc7313efa2c9805bf59ac Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 12:11:42 +0000 Subject: [PATCH 106/183] create Pool class --- usr/bin/sdwdate | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index b110e8ef..aea813bc 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -16,6 +16,17 @@ from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools from sdwdate.timesanitycheck import timesanitycheck +class Pool: + def __init__(self, pool): + self.pool_url, self.pool_comment = read_pools(pool) + self.url_random = [] + self.already_picked_index = [] + self.done = False + + @property + def url_range(self): + return len(self.pool_url) + class Sdwdate(): def __init__(self): From 6f81099b00e5548e8418c1438dfde434bad78859 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 12:15:10 +0000 Subject: [PATCH 107/183] modify Sdwdate __init__ with new Pool class --- usr/bin/sdwdate | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index aea813bc..d3fc9199 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -30,40 +30,15 @@ class Pool: class Sdwdate(): def __init__(self): - (self.pool_one_url, - self.pool_two_url, - self.pool_three_url, - self.pool_one_comment, - self.pool_two_comment, - self.pool_three_comment) = read_pools() - self.iteration = 0 - - self.range_pool_one = len(self.pool_one_url) - self.range_pool_two = len(self.pool_two_url) - self.range_pool_three = len(self.pool_three_url) - self.number_of_pools = 3 - self.pool_one_done = False - self.pool_two_done = False - self.pool_three_done = False - - self.already_picked_index_pool_one = [] - self.already_picked_index_pool_two = [] - self.already_picked_index_pool_three = [] - + self.pools = [Pool(pool) for pool in range(self.number_of_pools)] self.urls = [] self.url_random = [] - - self.url_random_pool_one = [] - self.url_random_pool_two = [] - self.url_random_pool_three = [] - self.valid_urls = [] self.unixtimes = [] self.pools_diff = [] - self.invalid_urls = [] self.url_errors = [] From 5708ddf429b96156271da528177d1ea4b7d8a6b2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 12:31:43 +0000 Subject: [PATCH 108/183] url append -> single code block [for pool in self.pools] --- usr/bin/sdwdate | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index d3fc9199..4af57149 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -292,44 +292,21 @@ class Sdwdate(): self.urls[:] = [] self.url_random[:] = [] - if not self.pool_one_done: - url_index = [] - url_index = random.sample(range(self.range_pool_one), 1) - - if len(self.already_picked_index_pool_one) >= len(self.pool_one_url): - self.message = ' Time is not set: no valid time returned from pool one' - logger.warning(self.message) - return self.error_icon, 'error' - - self.already_picked_index_pool_one.append(url_index) - self.url_random_pool_one.append(self.pool_one_url[url_index[0]]) - self.url_random.append(self.pool_one_url[url_index[0]]) - - if not self.pool_two_done: - url_index = [] - url_index = random.sample(range(self.range_pool_two), 1) + for pool in self.pools: + if not pool.done: + url_index = random.randrange(0, pool.url_range) - if len(self.already_picked_index_pool_two) >= len(self.pool_two_url): - self.message = ' Time is not set: no valid time returned from pool two' - logger.warning(self.message) - return self.error_icon, 'error' - - self.already_picked_index_pool_two.append(url_index) - self.url_random_pool_two.append(self.pool_two_url[url_index[0]]) - self.url_random.append(self.pool_two_url[url_index[0]]) - - if not self.pool_three_done: - url_index = [] - url_index = random.sample(range(self.range_pool_three), 1) + if len(pool.already_picked_index) >= len(pool.pool_url): + self.message = ' Time is not set: no valid time returned from pool one' + logger.warning(self.message) + return self.error_icon, 'error' - if len(self.already_picked_index_pool_three) >= len(self.pool_three_url): - self.message = 'Time is not set: no valid time returned from pool three' - logger.warning(self.message) - return self.error_icon, 'error' + pool.already_picked_index.append(url_index) + pool.url_random.append(pool.pool_url[url_index]) + self.url_random.append(pool.pool_url[url_index]) - self.already_picked_index_pool_three.append(url_index) - self.url_random_pool_three.append(self.pool_three_url[url_index[0]]) - self.url_random.append(self.pool_three_url[url_index[0]]) + print self.url_random + sys.exit() ## Fetch remotes. if len(self.url_random) > 0: From 4dc67e350953382c3ba7425af0dff5656aa1780e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 14:17:18 +0000 Subject: [PATCH 109/183] get pool diff -> single code block [for pool in self.pools] --- usr/bin/sdwdate | 72 ++++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 52 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 4af57149..1af78e50 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -305,9 +305,6 @@ class Sdwdate(): pool.url_random.append(pool.pool_url[url_index]) self.url_random.append(pool.pool_url[url_index]) - print self.url_random - sys.exit() - ## Fetch remotes. if len(self.url_random) > 0: message = 'Requested urls %s' % (self.url_random) @@ -351,55 +348,26 @@ class Sdwdate(): old_unixtime = (time.time()) - if not self.pool_one_done: - for i in range(len(self.url_random_pool_one)): - self.pool_one_done = self.url_random_pool_one[i] in self.valid_urls - if self.pool_one_done: - valid_url = self.url_random_pool_one[i] - ## Values are returned randomly. Get the index of the url. - index = self.valid_urls.index(valid_url) - ## Pool matching web time. - web_unixtime = int(self.unixtimes[index]) - web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) - pool_one_diff = int(web_unixtime) - int(old_unixtime) - self.pools_diff.append(pool_one_diff) - message = ('Pool one: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (valid_url, web_unixtime, web_time, pool_one_diff)) - print(message) - logger.info(message) - - if not self.pool_two_done: - for i in range(len(self.url_random_pool_two)): - self.pool_two_done = self.url_random_pool_two[i] in self.valid_urls - if self.pool_two_done: - valid_url = self.url_random_pool_two[i] - index = self.valid_urls.index(valid_url) - web_unixtime = int(self.unixtimes[index]) - web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) - pool_two_diff = int(web_unixtime) - int(old_unixtime) - self.pools_diff.append(int(web_unixtime) - int(old_unixtime)) - message = ('Pool two: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (valid_url, web_unixtime, web_time, pool_two_diff)) - print(message) - logger.info(message) - - if not self.pool_three_done: - for i in range(len(self.url_random_pool_three)): - self.pool_three_done = self.url_random_pool_three[i] in self.valid_urls - if self.pool_three_done: - valid_url = self.url_random_pool_three[i] - index = self.valid_urls.index(valid_url) - web_unixtime = int(self.unixtimes[index]) - web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) - pool_three_diff = int(web_unixtime) - int(old_unixtime) - self.pools_diff.append(int(web_unixtime) - int(old_unixtime)) - message = ('Pool three: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (valid_url, web_unixtime, web_time, pool_three_diff)) - print(message) - logger.info(message) + for pool in self.pools: + if not pool.done: + for url_random in pool.url_random: + pool.done = url_random in self.valid_urls + if pool.done: + valid_url = url_random + ## Values are returned randomly. Get the index of the url. + index = self.valid_urls.index(valid_url) + ## Pool matching web time. + web_unixtime = int(self.unixtimes[index]) + web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + pool_diff = int(web_unixtime) - int(old_unixtime) + self.pools_diff.append(pool_diff) + message = ('Pool one: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (valid_url, web_unixtime, web_time, pool_diff)) + print(message) + logger.info(message) + + sys.exit() message = 'Reachable urls:\n' for i in range(len(self.valid_urls)): From ec9576064a4e1c25e2f054e3cda4c2b8adbab5f4 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 14:53:32 +0000 Subject: [PATCH 110/183] modify get_comment() function [for url in self.pools] --- usr/bin/sdwdate | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 1af78e50..8633d2e3 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -99,14 +99,12 @@ class Sdwdate(): def get_comment(self, remote): ''' For logging the commnents, get the index of the url - to get it from pool_nnn_comment. + to get it from pool.comment. ''' - pools_url = [self.pool_one_url, self.pool_two_url, self.pool_three_url] - pools_comment = [self.pool_one_comment, self.pool_two_comment, self.pool_three_comment] - for i in range(len(pools_url)): + for url in self.pools: try: - url_index = pools_url[i].index(remote) - url_comment = pools_comment[i][url_index] + url_index = url.pool_url.index(remote) + url_comment = url.pool_comment[url_index] except ValueError: pass return url_comment @@ -367,8 +365,6 @@ class Sdwdate(): print(message) logger.info(message) - sys.exit() - message = 'Reachable urls:\n' for i in range(len(self.valid_urls)): url_comment = self.get_comment(self.valid_urls[i]) From 1f25d0d37d2f2d7705c6863df6e8b7893f2d17e1 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 14:59:25 +0000 Subject: [PATCH 111/183] Pool.pool_url -> Pool.url --- usr/bin/sdwdate | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 8633d2e3..751f4485 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -18,14 +18,14 @@ from sdwdate.timesanitycheck import timesanitycheck class Pool: def __init__(self, pool): - self.pool_url, self.pool_comment = read_pools(pool) + self.url, self.comment = read_pools(pool) self.url_random = [] self.already_picked_index = [] self.done = False @property def url_range(self): - return len(self.pool_url) + return len(self.url) class Sdwdate(): @@ -103,8 +103,8 @@ class Sdwdate(): ''' for url in self.pools: try: - url_index = url.pool_url.index(remote) - url_comment = url.pool_comment[url_index] + url_index = url.url.index(remote) + url_comment = url.comment[url_index] except ValueError: pass return url_comment @@ -294,14 +294,14 @@ class Sdwdate(): if not pool.done: url_index = random.randrange(0, pool.url_range) - if len(pool.already_picked_index) >= len(pool.pool_url): + if len(pool.already_picked_index) >= len(pool.url): self.message = ' Time is not set: no valid time returned from pool one' logger.warning(self.message) return self.error_icon, 'error' pool.already_picked_index.append(url_index) - pool.url_random.append(pool.pool_url[url_index]) - self.url_random.append(pool.pool_url[url_index]) + pool.url_random.append(pool.url[url_index]) + self.url_random.append(pool.url[url_index]) ## Fetch remotes. if len(self.url_random) > 0: From fff4ba0df392713f9855706769cc2c5e72003d0c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 15:06:45 +0000 Subject: [PATCH 112/183] get_comment callong blocks -> [for url in self.valid_urls] --- usr/bin/sdwdate | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 751f4485..e2712ab1 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -366,16 +366,16 @@ class Sdwdate(): logger.info(message) message = 'Reachable urls:\n' - for i in range(len(self.valid_urls)): - url_comment = self.get_comment(self.valid_urls[i]) - message = message + '%s: "%s"\n' % (self.valid_urls[i], url_comment) + for url in self.valid_urls: + url_comment = self.get_comment(url) + message = message + '%s: "%s"\n' % (url, url_comment) print(message.strip()) logger.info(message.strip()) message = 'Unreachable urls:\n' - for i in range(len(self.invalid_urls)): - url_comment = self.get_comment(self.invalid_urls[i]) - message = message + '%s: "%s"\n' % (self.invalid_urls[i], url_comment) + for url in self.invalid_urls: + url_comment = self.get_comment(url) + message = message + '%s: "%s"\n' % (url, url_comment) print(message.strip()) logger.info(message.strip()) From 1d08e7fb7c3f474664923cf762408d736aa1cd78 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 16:19:23 +0000 Subject: [PATCH 113/183] for url_random in pool.url_random: -> for url in pool.url_random: --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e2712ab1..8ce77424 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -348,10 +348,10 @@ class Sdwdate(): for pool in self.pools: if not pool.done: - for url_random in pool.url_random: - pool.done = url_random in self.valid_urls + for url in pool.url_random: + pool.done = url in self.valid_urls if pool.done: - valid_url = url_random + valid_url = url ## Values are returned randomly. Get the index of the url. index = self.valid_urls.index(valid_url) ## Pool matching web time. From 1d77e524b97890cacf767df5271039d02ea66376 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 20 Sep 2015 18:19:06 +0000 Subject: [PATCH 114/183] logging pool number --- usr/bin/sdwdate | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 8ce77424..c73f5f42 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -360,8 +360,9 @@ class Sdwdate(): '%a %b %d %H:%M:%S UTC %Y')) pool_diff = int(web_unixtime) - int(old_unixtime) self.pools_diff.append(pool_diff) - message = ('Pool one: last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (valid_url, web_unixtime, web_time, pool_diff)) + pool_number = self.pools.index(pool) + 1 + message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) print(message) logger.info(message) From 61c60229f2e593ca42bcb8f6c9d05380069578b1 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 21 Sep 2015 18:22:18 +0000 Subject: [PATCH 115/183] logging pool nmber in "no valid time returned from pool" --- usr/bin/sdwdate | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index c73f5f42..95ecb579 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -292,10 +292,13 @@ class Sdwdate(): for pool in self.pools: if not pool.done: - url_index = random.randrange(0, pool.url_range) + pool_range = pool.url_range + url_index = random.randrange(0, pool_range) - if len(pool.already_picked_index) >= len(pool.url): - self.message = ' Time is not set: no valid time returned from pool one' + if len(pool.already_picked_index) >= pool_range: + pool_number = self.pools.index(pool) + 1 + self.message = (' Time is not set: no valid time returned from pool %s' + % (pool_number)) logger.warning(self.message) return self.error_icon, 'error' From 01b97dff39768bca8d3130499d56b1eb5d748d6b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 21 Sep 2015 19:08:47 +0000 Subject: [PATCH 116/183] general_prosy_error -> modifief message from url_to_unixtime own if block --- usr/bin/sdwdate | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 95ecb579..e9dde066 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -70,10 +70,11 @@ class Sdwdate(): ''' This error occurs (at least) when Tor is not running. ''' + returned_error = 'connect error: Connection closed unexpectedly' try: - if (pools[0] == 'Connection closed unexpectedly' and - pools[1] == 'Connection closed unexpectedly' and - pools[2] == 'Connection closed unexpectedly'): + if (pools[0] == returned_error and + pools[1] == returned_error and + pools[2] == returned_error): return True # If tor is stopping during a cycle, some urls are not returned, # raising an error. @@ -334,19 +335,19 @@ class Sdwdate(): print(message) return self.error_icon, 'error' - if not self.general_proxy_error(self.returned_values): - for i in range(len(self.urls)): - if self.check_remote(self.urls[i], self.returned_values[i]): - self.valid_urls.append(self.urls[i]) - self.unixtimes.append(self.returned_values[i]) - else: - self.invalid_urls.append(self.urls[i]) - self.url_errors.append(self.returned_values[i]) - else: + if self.general_proxy_error(self.returned_values): self.message = 'General Proxy Error. Is Tor running?' print(message) return self.error_icon, 'error' + for i in range(len(self.urls)): + if self.check_remote(self.urls[i], self.returned_values[i]): + self.valid_urls.append(self.urls[i]) + self.unixtimes.append(self.returned_values[i]) + else: + self.invalid_urls.append(self.urls[i]) + self.url_errors.append(self.returned_values[i]) + old_unixtime = (time.time()) for pool in self.pools: From 6aab0de9020959d06ae60c3adcd5e68088495ce2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 21 Sep 2015 19:51:30 +0000 Subject: [PATCH 117/183] general_timeout_error --- usr/bin/sdwdate | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e9dde066..af0854b3 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -82,6 +82,22 @@ class Sdwdate(): return True return False + def general_timeout_error(self, pools): + ''' + This error occurs (at least) when internet connection is down. + ''' + returned_error = 'Timeout' + try: + if (pools[0] == returned_error and + pools[1] == returned_error and + pools[2] == returned_error): + return True + # If tor is stopping during a cycle, some urls are not returned, + # raising an error. + except IndexError: + return True + return False + def check_remote(self, remote, value): ''' Check returned value. True if numeric. @@ -340,6 +356,11 @@ class Sdwdate(): print(message) return self.error_icon, 'error' + if self.general_timeout_error(self.returned_values): + self.message = 'General Timeout Error. Internet might be down' + print(message) + return self.error_icon, 'error' + for i in range(len(self.urls)): if self.check_remote(self.urls[i], self.returned_values[i]): self.valid_urls.append(self.urls[i]) From f93bddb2bad0102f3ba2ca1041327f061c39718e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 21 Sep 2015 20:30:00 +0000 Subject: [PATCH 118/183] remove retrying loop --- usr/bin/sdwdate | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index af0854b3..1e2e977d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -284,13 +284,8 @@ class Sdwdate(): print self.message return self.error_icon, 'error' - global retrying_loop - if self.success: - if not retrying_loop: - ## Update tool tip. - ## Update icon after SIGTERM. - self.write_status(self.success_icon, 'Fetching remote times...') + self.write_status(self.success_icon, 'Fetching remote times...') else: if not self.first_success: self.write_status(self.busy_icon, 'No internet. Fetching remote times...') @@ -332,14 +327,9 @@ class Sdwdate(): self.urls, self.returned_values = get_time_from_servers(self.url_random) if len(self.urls) == 0: - if retrying_loop: - retrying_loop = False - self.message = 'No values returned from servers.
      Internet connection might be down.' - return self.error_icon, 'error' - else: - retrying_loop = True - self.message = 'No values returned from url_to_unixtime.
      Retrying...' - return self.busy_icon, 'retry' + self.message = 'No values returned from servers.
      Please report this bug.' + print message + return self.error_icon, 'error' message = 'Returned urls "%s"' % (self.urls) print(message) @@ -468,10 +458,6 @@ if __name__ == "__main__": sleep_time = 10 log_level = 'info' - elif status == 'retry': - sleep_time = 0.1 - log_level = 'warning' - elif status == 'error': sleep_time = 10 log_level = 'warning' From e10f5ad86a16794e94f248f9b7b548c9bacecf71 Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Mon, 21 Sep 2015 23:57:07 +0000 Subject: [PATCH 119/183] Make sure /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service really gets really deleted even if timedatectl does not work. --- debian/sdwdate.postinst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 7e43badb..4f60d8e6 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -53,9 +53,11 @@ fi service systemd-timesyncd stop >/dev/null 2>&1 || true -## Deletes /etc/system/sysinit.target.wants/systemd-timesyncd.service. +## Deletes /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service. ## Otherwise timedatectl still thinks systemd-timesyncd is enabled. timedatectl set-ntp false >/dev/null 2>&1 || true +## Make sure it gets really deleted even if timedatectl does not work. +rm --force /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service true "INFO: debhelper beginning here." From b371d65a83b490fed22666a9b37f0ea3783b0df4 Mon Sep 17 00:00:00 2001 From: Patrick Schleizer Date: Tue, 22 Sep 2015 11:54:58 +0000 Subject: [PATCH 120/183] use cleaner solution 'Conflicts: systemd-timesyncd' rather than 'service systemd-timesyncd stop' --- debian/sdwdate.postinst | 2 -- lib/systemd/system/sdwdate.service | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/debian/sdwdate.postinst b/debian/sdwdate.postinst index 4f60d8e6..c2808a9e 100644 --- a/debian/sdwdate.postinst +++ b/debian/sdwdate.postinst @@ -51,8 +51,6 @@ if [ -d /run/systemd/system ] ; then systemd-tmpfiles --create /usr/lib/tmpfiles.d/sdwdate.conf >/dev/null || true fi -service systemd-timesyncd stop >/dev/null 2>&1 || true - ## Deletes /etc/systemd/system/sysinit.target.wants/systemd-timesyncd.service. ## Otherwise timedatectl still thinks systemd-timesyncd is enabled. timedatectl set-ntp false >/dev/null 2>&1 || true diff --git a/lib/systemd/system/sdwdate.service b/lib/systemd/system/sdwdate.service index ae3e936f..7fe33abc 100644 --- a/lib/systemd/system/sdwdate.service +++ b/lib/systemd/system/sdwdate.service @@ -9,6 +9,7 @@ After=network.target Wants=rinetd.service Wants=bootclockrandomization.service Wants=tor.service +Conflicts=systemd-timesyncd.service [Service] Type=simple From 40701d13b317be8afef300649b4351d346ba3662 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 22 Sep 2015 15:32:52 +0000 Subject: [PATCH 121/183] reinstate while loop in url append code block --- usr/bin/sdwdate | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 1e2e977d..40fecbc0 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -304,19 +304,21 @@ class Sdwdate(): for pool in self.pools: if not pool.done: - pool_range = pool.url_range - url_index = random.randrange(0, pool_range) + url_index = 0 + while url_index not in pool.already_picked_index: + pool_range = pool.url_range + url_index = random.randrange(0, pool_range) - if len(pool.already_picked_index) >= pool_range: - pool_number = self.pools.index(pool) + 1 - self.message = (' Time is not set: no valid time returned from pool %s' - % (pool_number)) - logger.warning(self.message) - return self.error_icon, 'error' + if len(pool.already_picked_index) >= pool_range: + pool_number = self.pools.index(pool) + 1 + self.message = (' Time is not set: no valid time returned from pool %s' + % (pool_number)) + logger.warning(self.message) + return self.error_icon, 'error' - pool.already_picked_index.append(url_index) - pool.url_random.append(pool.url[url_index]) - self.url_random.append(pool.url[url_index]) + pool.already_picked_index.append(url_index) + pool.url_random.append(pool.url[url_index]) + self.url_random.append(pool.url[url_index]) ## Fetch remotes. if len(self.url_random) > 0: @@ -377,7 +379,7 @@ class Sdwdate(): self.pools_diff.append(pool_diff) pool_number = self.pools.index(pool) + 1 message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) + % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) print(message) logger.info(message) From f2f6e7187455d7ce3c625222fcebc9f7b895d689 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 25 Sep 2015 19:33:38 +0000 Subject: [PATCH 122/183] cleanup remove "retrying_loop = False" --- usr/bin/sdwdate | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 40fecbc0..9f8c214d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -379,7 +379,7 @@ class Sdwdate(): self.pools_diff.append(pool_diff) pool_number = self.pools.index(pool) + 1 message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) + % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) print(message) logger.info(message) @@ -437,8 +437,6 @@ if __name__ == "__main__": handler.setFormatter(formatter) logger.addHandler(handler) - retrying_loop = False - while True: sdwdate = Sdwdate() icon, status = sdwdate.sdwdate_loop() From 7cf09224d45274932ec8d3ad8588d012735b5c58 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 25 Sep 2015 19:39:50 +0000 Subject: [PATCH 123/183] logger name sdwdate_log -> sdwdate.log --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 9f8c214d..4ff9a7a9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -430,7 +430,7 @@ if __name__ == "__main__": signal.signal(signal.SIGTERM, signal_sigterm_handler) - logger = logging.getLogger('sdwdate_log') + logger = logging.getLogger('sdwdate.log') logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler = logging.FileHandler('/var/log/sdwdate.log') From ff9d6d911a7c7a4d903a762fdb85a563ea3fe89e Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 25 Sep 2015 19:42:35 +0000 Subject: [PATCH 124/183] log own pid at start --- usr/bin/sdwdate | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 4ff9a7a9..0dab6f32 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -437,6 +437,11 @@ if __name__ == "__main__": handler.setFormatter(formatter) logger.addHandler(handler) + self_pid = os.getpid() + pid_message = 'sdwdate started. PID %s' % self_pid + print pid_message + logger.info(pid_message) + while True: sdwdate = Sdwdate() icon, status = sdwdate.sdwdate_loop() From 7ba7bb6dd31bf723562c0f4347e258e1af1a8be7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 30 Sep 2015 16:30:10 +0000 Subject: [PATCH 125/183] comments in general_proxy_error, general_timeout_error --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 0dab6f32..6114c08a 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -76,7 +76,7 @@ class Sdwdate(): pools[1] == returned_error and pools[2] == returned_error): return True - # If tor is stopping during a cycle, some urls are not returned, + # If tor stops during a cycle, some urls are not returned, # raising an error. except IndexError: return True @@ -92,8 +92,8 @@ class Sdwdate(): pools[1] == returned_error and pools[2] == returned_error): return True - # If tor is stopping during a cycle, some urls are not returned, - # raising an error. + # If internet disconnects during a cycle, some urls are + # not returned, raising an error. except IndexError: return True return False From f9a735a26d2a872a105e7d599d1a89e5f55d0e62 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 1 Oct 2015 21:03:31 +0000 Subject: [PATCH 126/183] new file sdwdate.yaml --- usr/share/translations/sdwdate.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 usr/share/translations/sdwdate.yaml diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml new file mode 100644 index 00000000..aaf46d50 --- /dev/null +++ b/usr/share/translations/sdwdate.yaml @@ -0,0 +1,27 @@ +sdwdate: + en: + fetching: 'Fetching remote times...' + restricted: '
      Outgoing internet access is still restricted because initial time fetching is not done yet' + user_kill: 'Signal SIGTERM received. sdwdate stopped by user. ' + sleeping: '
      Sleeping for ' + minutes: ' minutes.' + success_1: 'Last run (on ' + success_2: ') was successful.
      ' + + tsc_1: 'The clock is ' + tsc_2: '
      Current time ' + slow_clock: ' is less than
      the build timestamp ' + fast_clock: ' is greater than
      the expiration timestamp ' + cause: '

      Possible causes:
      + - the host clock is wrong -> shut down the VM, fix the clock in the host and restart the VM.
      + - the VM clok is wrong -> manually fix the clock. Restart tor if necessary.
      + - a host clock attack succeeded.
      + - a bios clock failure.
      ' + + no_valid_time: 'Time is not set: no valid time returned from pool ' + no_value_returned: 'No values returned from servers.' + list_not_built: 'Something is wrong. sdwdate could not build a list or urls.' + restart: '
      Restart sdwdate. If the problem persists, please report this bug.
      ' + + general_proxy_error: 'General Proxy Error. Is Tor running?' + general_timeout_error: 'General Timeout Error. Internet might be down' From 75f20cb991344f5ba86abc0dd756d8d70be43002 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 1 Oct 2015 21:07:17 +0000 Subject: [PATCH 127/183] use external yaml file for logging / sdwdate-gui messages --- usr/bin/sdwdate | 71 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 6114c08a..92b3a72e 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -16,6 +16,9 @@ from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools from sdwdate.timesanitycheck import timesanitycheck +from guimessages.translations import _translations + + class Pool: def __init__(self, pool): self.url, self.comment = read_pools(pool) @@ -63,9 +66,12 @@ class Sdwdate(): self.success = os.path.exists(self.success_path) self.status = {'icon' : '', 'message' : ''} - self.message = '' + translations_path ='/usr/share/translations/sdwdate.yaml' + translation = _translations(translations_path, 'sdwdate') + self._ = translation.gettext + def general_proxy_error(self, pools): ''' This error occurs (at least) when Tor is not running. @@ -130,13 +136,14 @@ class Sdwdate(): status, time_one, time_two = timesanitycheck() if status == 'sane': - self.message = 'The clock is %s. Current time "%s".' % (status, time_one) + self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one) + #'The clock is %s. Current time "%s".' % (status, time_one) elif status == 'slow': - self.message = ('The clock is %s.
      Current time [%s] is less than the build timestamp [%s]' - % (status, time_one, time_two)) + self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one + + self._('slow_clock') + time_two) + self._('cause') elif status == 'fast': - self.message = ('The clock is %s.
      Current time [%s] is greater than the expiration timestamp [%s]' - % (status, time_one, time_two)) + self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one + + self._('fast_clock') + time_two)+ self._('cause') return status def build_median(self): @@ -254,6 +261,12 @@ class Sdwdate(): print(message) logger.info(message) + def strip_html(self, message): + ## New line for log. + tmp_message = re.sub('
      ', '\n', message) + ## Strip rmaining HTML. + return(re.sub('<[^<]+?>', '', tmp_message)) + def write_status(self, *args): self.status['icon'] = args[0] self.status['message'] = args[1] @@ -276,21 +289,28 @@ class Sdwdate(): logger.info(message) time_sanity_check = self.time_sanity_check() + stripped_message = self.strip_html(self.message) + print stripped_message + if time_sanity_check == 'sane': - print self.message - logger.info(self.message) + logger.info(stripped_message) else: - stripped_message = re.sub('<[^<]+?>', '', self.message) - print self.message return self.error_icon, 'error' + fetching_msg = self._('fetching') + restricted_msg = self._('restricted') + print restricted_msg + print fetching_msg if self.success: - self.write_status(self.success_icon, 'Fetching remote times...') + self.write_status(self.success_icon, fetching_msg) + logger.info(fetching_msg) else: if not self.first_success: - self.write_status(self.busy_icon, 'No internet. Fetching remote times...') + self.write_status(self.busy_icon, (fetching_msg + restricted_msg)) + logger.info(fetching_msg + restricted_msg) else: - self.write_status(self.success_icon, 'Fetching remote times...') + self.write_status(self.success_icon, fetching_msg) + logger.info(fetching_msg) while len(self.valid_urls) < self.number_of_pools: self.iteration = self.iteration + 1 @@ -311,8 +331,7 @@ class Sdwdate(): if len(pool.already_picked_index) >= pool_range: pool_number = self.pools.index(pool) + 1 - self.message = (' Time is not set: no valid time returned from pool %s' - % (pool_number)) + self.message = self._('no_valid_time') + str(pool_number) + self._('restart') logger.warning(self.message) return self.error_icon, 'error' @@ -329,7 +348,7 @@ class Sdwdate(): self.urls, self.returned_values = get_time_from_servers(self.url_random) if len(self.urls) == 0: - self.message = 'No values returned from servers.
      Please report this bug.' + self.message = self._('no_value_returned') + self._('restart') print message return self.error_icon, 'error' @@ -338,18 +357,17 @@ class Sdwdate(): logger.info(message) else: - self.message = ('Something is wrong. sdwdate loop could not build a list or urls.
      ' - ' Please report this bug') + self.message = self._('list_not_built') + self._('restart') print(message) return self.error_icon, 'error' if self.general_proxy_error(self.returned_values): - self.message = 'General Proxy Error. Is Tor running?' + self.message = self._('general_proxy_error') print(message) return self.error_icon, 'error' if self.general_timeout_error(self.returned_values): - self.message = 'General Timeout Error. Internet might be down' + self.message = self._('general_timeout_error') print(message) return self.error_icon, 'error' @@ -406,7 +424,7 @@ class Sdwdate(): logger.info(message) last_shell_date = check_output('date').strip() - self.message = 'Last run (on ' + last_shell_date + ') was successful' + self.message = self._('success_1') + last_shell_date + self._('success_2') retrying_loop = False return self.success_icon, 'success' @@ -414,13 +432,14 @@ class Sdwdate(): def signal_sigterm_handler(signum, frame): # Inform sdwdate-gui icon = sdwdate.error_icon - message = 'sdwdate stopped, signal SIGTERM received' + message = sdwdate._('user_kill') + stripped_message = re.sub('<[^<]+?>', '', message) sdwdate.write_status(icon, message) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() - logger.info('Signal SIGTERM received. Exiting.') + logger.info(stripped_message) sys.exit(143) if __name__ == "__main__": @@ -467,8 +486,10 @@ if __name__ == "__main__": sleep_time = 10 log_level = 'warning' - message = '%s.
      Sleeping for %s minutes.' % (sdwdate.message, sleep_time) - stripped_message = re.sub('<[^<]+?>', '', message) + + message = (sdwdate.message + sdwdate._('sleeping') + + str(sleep_time) + sdwdate._('minutes')) + stripped_message = sdwdate.strip_html(message) sdwdate.write_status(icon, message) From c331bef0e360b70646857385fd4575932db7f167 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 3 Oct 2015 20:26:11 +0000 Subject: [PATCH 128/183] timesanitycheck each pool after fetching times --- usr/bin/sdwdate | 36 +++++++++++-------- .../dist-packages/sdwdate/timesanitycheck.py | 7 ++-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 92b3a72e..fb7abb4d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -132,8 +132,8 @@ class Sdwdate(): pass return url_comment - def time_sanity_check(self): - status, time_one, time_two = timesanitycheck() + def time_sanity_check(self, unixtime): + status, time_one, time_two = timesanitycheck(unixtime) if status == 'sane': self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one) @@ -288,7 +288,8 @@ class Sdwdate(): print(message) logger.info(message) - time_sanity_check = self.time_sanity_check() + current_unixtime = time.time() + time_sanity_check = self.time_sanity_check(current_unixtime) stripped_message = self.strip_html(self.message) print stripped_message @@ -386,20 +387,27 @@ class Sdwdate(): for url in pool.url_random: pool.done = url in self.valid_urls if pool.done: - valid_url = url ## Values are returned randomly. Get the index of the url. - index = self.valid_urls.index(valid_url) + index = self.valid_urls.index(url) ## Pool matching web time. web_unixtime = int(self.unixtimes[index]) - web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) - pool_diff = int(web_unixtime) - int(old_unixtime) - self.pools_diff.append(pool_diff) - pool_number = self.pools.index(pool) + 1 - message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (pool_number, valid_url, web_unixtime, web_time, pool_diff)) - print(message) - logger.info(message) + if self.time_sanity_check(web_unixtime) == 'sane': + web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), + '%a %b %d %H:%M:%S UTC %Y')) + pool_diff = int(web_unixtime) - int(old_unixtime) + self.pools_diff.append(pool_diff) + pool_number = self.pools.index(pool) + 1 + message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' + % (pool_number, url, web_unixtime, web_time, pool_diff)) + print(message) + logger.info(message) + else: + pool.done = False + del self.valid_urls[index] + self.invalid_urls.append(url) + message = '%s: time sanity check failed' % url + print message + logger.warning(message) message = 'Reachable urls:\n' for url in self.valid_urls: diff --git a/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py index 9f541321..efce1b39 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/timesanitycheck.py @@ -1,7 +1,8 @@ +import sys import os, time from datetime import datetime -def timesanitycheck(): +def timesanitycheck(unixtime): whonix_build_file = '/usr/share/whonix/build_timestamp' anondist_build_file = '/var/lib/anon-dist/build_version' spare_file = '/usr/share/zoneinfo/UTC' @@ -13,8 +14,8 @@ def timesanitycheck(): else: build_timestamp_file = spare_file - current_time = datetime.strftime(datetime.now(), '%a %b %d %H:%M:%S UTC %Y') - current_unixtime = time.mktime(datetime.strptime(current_time, '%a %b %d %H:%M:%S UTC %Y').timetuple()) + current_unixtime = unixtime + current_time = datetime.strftime(datetime.fromtimestamp(unixtime), '%a %b %d %H:%M:%S UTC %Y') build_time = time.strftime('%a %b %d %H:%M:%S UTC %Y', time.gmtime(os.path.getmtime(build_timestamp_file))) build_unixtime = time.mktime(datetime.strptime(build_time, '%a %b %d %H:%M:%S UTC %Y').timetuple()) From cd74072afb1932a0ce1b01e884d8b4ca4db3a93c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 3 Oct 2015 20:56:19 +0000 Subject: [PATCH 129/183] log slow or fast if pool timesanitycheck fails --- usr/bin/sdwdate | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index fb7abb4d..78895c00 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -391,7 +391,8 @@ class Sdwdate(): index = self.valid_urls.index(url) ## Pool matching web time. web_unixtime = int(self.unixtimes[index]) - if self.time_sanity_check(web_unixtime) == 'sane': + sanitycheck_status = self.time_sanity_check(web_unixtime) + if sanitycheck_status == 'sane': web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), '%a %b %d %H:%M:%S UTC %Y')) pool_diff = int(web_unixtime) - int(old_unixtime) @@ -405,7 +406,8 @@ class Sdwdate(): pool.done = False del self.valid_urls[index] self.invalid_urls.append(url) - message = '%s: time sanity check failed' % url + message = ('%s: time sanity check failed, server time is %s' + % (url, sanitycheck_status)) print message logger.warning(message) From 8280a228eea523ff3f43dc98b64de769f5d5994d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 4 Oct 2015 18:42:00 +0000 Subject: [PATCH 130/183] debian/control Depends: python-guimessages --- debian/control | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 7d460568..6811b489 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,8 @@ Architecture: all Depends: sudo, logrotate, bc, ruby | ruby-interpreter, ruby-inline, ruby-dev, ruby-all-dev, gcc, libc6-dev, python-dateutil, python-gevent, - python-pysocks | python-socksipy, python, tor, ${misc:Depends} + python-pysocks | python-socksipy, python-guimessages, python, tor, + ${misc:Depends} Recommends: vbox-disable-timesync, timesanitycheck, bootclockrandomization, timesync Suggests: sdwdate-plugin-anon-shared-con-check, From 9bf8b5057edd94acdce7575f7c372bbfeeabec04 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Mon, 5 Oct 2015 20:37:30 +0000 Subject: [PATCH 131/183] fix bug in pool append loop -> set url_index = -1 as picked url index might be zero --- usr/bin/sdwdate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 78895c00..e995796d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -325,7 +325,8 @@ class Sdwdate(): for pool in self.pools: if not pool.done: - url_index = 0 + ## Set to -1 as the first picked url index might be zero. + url_index = -1 while url_index not in pool.already_picked_index: pool_range = pool.url_range url_index = random.randrange(0, pool_range) From c896c2f527844a70e43f5c0b130c65a326a63141 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 6 Oct 2015 19:06:32 +0000 Subject: [PATCH 132/183] stream isolation settings On branch python --- .../python2.7/dist-packages/sdwdate/remote_times.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py index 96b84cd3..e1f5c0ad 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py @@ -4,6 +4,8 @@ ## Copyright (C) 2015 Patrick Schleizer ## See the file COPYING for copying conditions. +import os +from subprocess import check_output import gevent from gevent.subprocess import Popen, PIPE @@ -20,10 +22,17 @@ def get_time_from_servers(remotes): del urls[:] del unix_times[:] + if os.path.exists('/usr/share/anon-gw-base-files/gateway'): + ip_address = '127.0.0.1' + elif os.path.exists('/usr/lib/qubes-whonix'): + ip_address = check_output(['qubesdb-read', '/qubes-gateway']) + else: + ip_address = '10.152.152.10' + for i in range(len(remotes)): threads.append(Popen([url_to_unixtime_path, - '127.0.0.1', - '9050', + ip_address, + '9108', remotes[i], '80', '0'], stdout=PIPE)) From 19503b03984370c040877a5b576e3d0a5a44e01c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 8 Oct 2015 19:24:32 +0000 Subject: [PATCH 133/183] + prerequisite check --- usr/bin/sdwdate | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e995796d..e29841df 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -8,6 +8,7 @@ import time from datetime import datetime import random from random import randint +import subprocess from subprocess import Popen, call, PIPE, check_output import pickle import re @@ -453,6 +454,23 @@ def signal_sigterm_handler(signum, frame): logger.info(stripped_message) sys.exit(143) +def prerequisite_check(): + prerequisite_path = ['/usr/lib/anon-shared-helper-scripts/te_pe_tb_check'] + temp_dir = check_output(["mktemp", "--directory"]) + os.environ["TEMP_DIR"] = temp_dir + tor_status = False + while not tor_status: + prerequisite_status = Popen(prerequisite_path, + stdin=PIPE, + stderr=PIPE) + value, error = prerequisite_status.communicate() + if error == '': + tor_status = True + else: + print error + logger.warning(error) + time.sleep(5) + if __name__ == "__main__": ## When restarted from sdwdate-gui, allow sufficient time between ## status changes. See related comment in sdwdate-gui. @@ -472,6 +490,8 @@ if __name__ == "__main__": print pid_message logger.info(pid_message) + prerequisite_check() + while True: sdwdate = Sdwdate() icon, status = sdwdate.sdwdate_loop() From 345f861fc3ee061a5b3ec6b3cd2d4d8519945403 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 18:10:35 +0000 Subject: [PATCH 134/183] prerequisite_check: stdout instead of stdin, use value instead of error --- usr/bin/sdwdate | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index e29841df..7354fc51 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -461,14 +461,14 @@ def prerequisite_check(): tor_status = False while not tor_status: prerequisite_status = Popen(prerequisite_path, - stdin=PIPE, + stdout=PIPE, stderr=PIPE) value, error = prerequisite_status.communicate() - if error == '': + if value == '': tor_status = True else: - print error - logger.warning(error) + print value + logger.warning(value) time.sleep(5) if __name__ == "__main__": From 92f1101fbb7ff8d65bf1a48a5e81f6b7b2479837 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 18:14:57 +0000 Subject: [PATCH 135/183] prerequisite_check: strip new line in returned values --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 7354fc51..fcdcc159 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -457,7 +457,7 @@ def signal_sigterm_handler(signum, frame): def prerequisite_check(): prerequisite_path = ['/usr/lib/anon-shared-helper-scripts/te_pe_tb_check'] temp_dir = check_output(["mktemp", "--directory"]) - os.environ["TEMP_DIR"] = temp_dir + os.environ["TEMP_DIR"] = temp_dir.strip() tor_status = False while not tor_status: prerequisite_status = Popen(prerequisite_path, @@ -467,8 +467,8 @@ def prerequisite_check(): if value == '': tor_status = True else: - print value - logger.warning(value) + print value.strip() + logger.warning(value.strip()) time.sleep(5) if __name__ == "__main__": From 8f3077c9da2184cf3bf92df4048d5397410bdbff Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 18:42:52 +0000 Subject: [PATCH 136/183] prerequisite_check: write error to sdwdate-gui --- usr/bin/sdwdate | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index fcdcc159..6a36efb6 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -469,6 +469,10 @@ def prerequisite_check(): else: print value.strip() logger.warning(value.strip()) + sdwdate = Sdwdate() + icon = sdwdate.error_icon + sdwdate.write_status(icon, value) + time.sleep(5) if __name__ == "__main__": From c406471c3e32ed15ec3ba2a7756025d948937d2a Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 18:44:45 +0000 Subject: [PATCH 137/183] prerequisite_check: time sleep 10 --- usr/bin/sdwdate | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 6a36efb6..36d9872d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -472,8 +472,7 @@ def prerequisite_check(): sdwdate = Sdwdate() icon = sdwdate.error_icon sdwdate.write_status(icon, value) - - time.sleep(5) + time.sleep(10) if __name__ == "__main__": ## When restarted from sdwdate-gui, allow sufficient time between From 37db6bed72e4ffcb47336ad23e6168fe32e00e10 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 20:10:42 +0000 Subject: [PATCH 138/183] etc/sdwdate-python.d -> etc/sdwdate.d --- etc/sdwdate.d/30_sdwdate_default | 328 ------------------ .../30_sdwdate_default.conf | 0 .../python2.7/dist-packages/sdwdate/config.py | 4 +- 3 files changed, 2 insertions(+), 330 deletions(-) delete mode 100644 etc/sdwdate.d/30_sdwdate_default rename etc/{sdwdate-python.d => sdwdate.d}/30_sdwdate_default.conf (100%) diff --git a/etc/sdwdate.d/30_sdwdate_default b/etc/sdwdate.d/30_sdwdate_default deleted file mode 100644 index c6e3b87e..00000000 --- a/etc/sdwdate.d/30_sdwdate_default +++ /dev/null @@ -1,328 +0,0 @@ -## This file is part of Whonix. -## Copyright (C) 2012 - 2014 Patrick Schleizer -## See the file COPYING for copying conditions. - -## Please use "/etc/sdwdate.d/50_sdwdate_user" for your custom -## configuration, which will override the defaults found here. -## When sdwdate is updated, this file may be overwritten. - -## Bash Fragment. - -## Enable/disable debugging. -## 1 enabled. -## 2 disabled. -DEBUG=0 - -## Run as the following user name. -## Not implemented. Has no effect. -USER="" - -## Do or do not actually change the date/time after successfully fetching it. -## 0 set date. -## 1 do not set date. -DONT_SET_DATE=0 - -## do not move the time forward -## 0 disabled -## 1 enabled -NO_MOVE_FORWARD=0 - -## do not move the time backwards -## 0 disabled -## 1 enabled -NO_MOVE_BACKWARDS=0 - -## update hardware clock -## 0 disabled -## 1 enabled -SYSTOHC=0 - -## Log file. -LOG_FILE=/var/log/sdwdate.log - -## Done file. Will be created after run no matter if failure or success. -DONE_FILE=/var/run/sdwdate/done - -## Success file. Will only be created after a success. -SUCCESS_FILE=/var/run/sdwdate/success - -## First success file. Will be created after the first success. -FIRST_SUCCESS_FILE=/var/run/sdwdate/first_success - -## How many members per pool are allowed to fail. -## If too many members are not reachable, time will not be adjusted. -ALLOWED_PER_POOL_FAILURE_RATIO=0.34 - -## Temporary directory for file downloads. -## When not set, default to: TEMP_DIR="$(mktemp --directory)" -#TEMP_DIR="" - -## Cache dir. Must not include spaces. -SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" - -## proxy IP -PROXY_IP="127.0.0.1" - -## proxy port -PROXY_PORT="9050" - -## How often sdwdate should run in minutes. -## 0 disables it and sdwdate exits after run. -INTERVAL="180" - -## How many minutes should be waited before running sdwdate again. -## Only has an effect when RANDOMIZE is set to 1 as well. -MIN_INTERVAL="60" - -## Randomize the interval above. -## Minimum 60 minutes. -## Maximum $INTERVAL minutes. -## 0 disabled. -## 1 enabled. -RANDOMIZE="1" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## starting up. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 0 -SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## re-starting up when sdwdate succeeded at least once after boot. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 1 -SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## in daemon mode. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 1 -SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" - -## sclockadj verbose logging or not -## --no-verbose -## --verbose -## defaults to: --no-verbose -SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" - -## sclockadj change date or not -## --no-debug -## --debug -## defaults to: --no-debug (change date) -SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" - -## If sclockadj should wait before its first iteration. -## --no-first-wait -## --first-wait -## default to: --no-first-wait -SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" - -## Move clock minimum nanoseconds (except rest). -## defaults to: 500000 -## (500000 ns = 0.5 ms = 0.0005 s) -SDWDATE_SCLOCKADJ_MOVE_MIN="500000" - -## Move clock maximum nanoseconds (except rest). -## defaults to: 500000 -## (500000 ns = 0.5 ms = 0.0005 s) -SDWDATE_SCLOCKADJ_MOVE_MAX="500000" - -## Wait nanoseconds minimum before next iteration. -## defaults to: 1000000000 -## (1000000000 ns = 1000 ms = 1 s) -SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" - -## Wait nanoseconds maximum before next iteration. -## defaults to: 1000000000 -## (1000000000 ns = 1000 ms = 1 s) -SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" - -## This command will be `eval`uated before DISPATCH_PREREQUISITE and before running url to unixtime tool. -## sdwdate provides the $SDW_MODE variable, which is either set to -## - startup (when the sdwdate process is started) -## - daemon (when the sdwdate process finished one loop and will continue) -## When set to "", it will be skipped. -DISPATCH_PRE="" - -## Prerequisite before trying to connect to servers. -## This is supposed to be a command to be `eval`uated and to exit with code -## - 0, if sdwdate should continue. -## - 1, if sdwdate should terminate itself due to an expected error. -## - 2, if sdwdate should wait 10 seconds and then run the command again. -## - Anything else, if sdwdate should terminate itself due to an unexpected error. -## It may be useful to check if the network is already reachable. -## When set to "", it will be skipped. -DISPATCH_PREREQUISITE="" - -## This command will be `eval`uated when an unexpected error (bug) in sdwdate has been caught. -## sdwdate will provide the $error_message and $DONE_FILE variable. -## Remember to escape variables either using \$ or '$variable'. -## When set to "", it will be skipped. -DISPATCH_POST_ERROR="" - -## Create $DONE_FILE on error. -## 1 enabled. -## 0 disabled. -SDW_TOUCH_DONE_FILE_ON_ERROR="1" - -## Exit 1 on error. This will stop the daemon from running. -## 1 enabled. -## 0 disabled. -SDW_EXIT_ON_ERROR="1" - -## echo remote unix time even when using --quiet -## true - enabled. -## false - disabled. -ECHO_UNIX_TIME="false" - -## This command will be `eval`uated when sdwdate succeeded. -## When set to "", it will be skipped. -DISPATCH_POST_SUCCESS="" - -## This command will be `eval`uated when sdwdate failed. -## When set to "", it will be skipped. -DISPATCH_POST_FAILURE="" - -## This command will be `eval`uated before trying to connect to the pool one. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="" - -## This command will be `eval`uated before after connecting to the pool one. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="" - -## This command will be `eval`uated before trying to connect to the pool two. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="" - -## This command will be `eval`uated before after connecting to the pool two. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="" - -## This command will be `eval`uated before trying to connect to pool three. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="" - -## This command will be `eval`uated before trying after connecting to the pool three. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" - -## pool syntax -## "url.onion[:port]#comment" -## " -## url.onion[:port]#comment -## [url.onion[:port]#comment] -## [url.onion[:port]#comment] -## [...] -## " -## "url.onion[:port]#comment" -## ... - -## pool one. -## SecureDrop List -## info: -## Last updated Thu Oct 23 16:15:00 PDT 2014 -## Organization Landing Page Tor Hidden Service Address -## in use: -## https://freedom.press/securedrop/directory -## https://freedom.press/sites/default/files/securedrop_list.txt -## https://freedom.press/sites/default/files/securedrop_list.txt.asc -## https://freedom.press/sites/default/files/securedrop.asc -## removed because down: -## "bczjr6ciiblco5ti.onion#Forbes https://safesource.forbes.com bczjr6ciiblco5ti.onion" -## "l7rt5kabupal7eo7.onion#BayLeaks https://bayleaks.com l7rt5kabupal7eo7.onion" -## individual websites -SDWDATE_POOL_ONE=( - "dtsxnd3ykn32ywv6.onion#BalkanLeaks https://www.balkanleaks.eu dtsxnd3ykn32ywv6.onion" - "znig4bc5rlwyj4mz.onion#ExposeFacts https://exposefacts.org znig4bc5rlwyj4mz.onion" - "vtjkwwcq5osuo6uq.onion#Greenpeace New Zealand https://www.safesource.org.nz vtjkwwcq5osuo6uq.onion" - "33y6fjyhs3phzfjj.onion#The Guardian https://securedrop.theguardian.com 33y6fjyhs3phzfjj.onion" - "y6xjgkgwj47us5ca.onion#The Intercept https://firstlook.org/theintercept/securedrop y6xjgkgwj47us5ca.onion" - "strngbxhwyuu37a3.onion#The New Yorker https://projects.newyorker.com/strongbox strngbxhwyuu37a3.onion" - "swdi5ymnwmrqhycl.onion#NRKbeta https://nrkbeta.no/tips swdi5ymnwmrqhycl.onion" - "dqeasamlf3jld2kz.onion#Project On Gov't Oversight (POGO) https://securedrop.pogo.org dqeasamlf3jld2kz.onion" - "pubdrop4dw6rk3aq.onion#ProPublica https://securedrop.propublica.org pubdrop4dw6rk3aq.onion" - "hkjpnjbvhrxjvikd.onion#Radio24syv https://securedrop.radio24syv.dk hkjpnjbvhrxjvikd.onion" - "v6gdwmm7ed4oifvd.onion#Barton Gellman https://tcfmailvault.info v6gdwmm7ed4oifvd.onion" - "vbmwh445kf3fs2v4.onion#The Washington Post https://ssl.washingtonpost.com/securedrop vbmwh445kf3fs2v4.onion" - "poulsensqiv6ocq4.onion#Wired's Kevin Poulsen https://pressfreedomfoundation.org/about/tech/kevin-poulsen poulsensqiv6ocq4.onion" - "tigas3l7uusztiqu.onion#https://mike.tig.as tinkerer at ProPublica in New York" -) - -## pool two. -## Hosted by Thomas White List -## -## GlobalLeaks List -## info: -## https://en.wikipedia.org/wiki/GlobaLeaks#Implementations -## http://www.webcitation.org/6WBrtPlrq -## Name of organization Implementation date Category Tor Url Tor2web Url Country -## removed because down: -## Perun[23] 2012-April-7 Investigative Journalism Closed Closed Serbia -## "jeuhrnvdyr3xyqz3.onion#Internet Governance Transparency Initiative 2014-April-5 Transparency Activism jeuhrnvdyr3xyqz3.onion https://jeuhrnvdyr3xyqz3.tor2web.org Unknown" -## "ea433ils4wtprqbv.onion#EcuadorTransparente 2014-June-19 Transparency Activism ea433ils4wtprqbv.onion https://ea433ils4wtprqbv.tor2web.org/ Ecuador" -## "3qnry3qqjvc2u3c4.onion#ManxLeaks 2014-July-07 Transparency Activism 3qnry3qqjvc2u3c4.onion https://3qnry3qqjvc2u3c4.tor2web.org Isle of Man" -SDWDATE_POOL_TWO=( - " - atlas777hhh7mcs7.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - compass6vpxj32p3.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - globe223ezvh6bps.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - bbbbbb6qtmqg65g6.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - pppppptkftqqnfsq.onion:80#Hosted by Thomas White. https://www.whonix.org/pipermail/whonix-devel/2015-February/000297.html - " - "w6csjytbrl273che.onion#Ljost[24][25] 2012-September-30 Transparency Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Iceland" - "ak2uqfavwgmjrvtu.onion#MagyarLeaks[26] 2013-July-7 Investigative Journalism ak2uqfavwgmjrvtu.onion https://ak2uqfavwgmjrvtu.tor2web.org Hungary" - "yn6ocmvu4ok3k3al.onion#Publeaks [27][28] 2013-September-9 +40 National/Local Media Consortium yn6ocmvu4ok3k3al.onion https://secure.publeaks.nl Netherlands" - "acabtd4btrxjjrvr.onion#Pistajka 2013-September Anticorruption activism acabtd4btrxjjrvr.onion https://acabtd4btrxjjrvr.tor2web.org Serbia" - "5r4bjnjug3apqdii.onion#Irpileaks[29][30] 2013-October-7 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "2dermafialks7aai.onion#Mafialeaks [31][32][33] 2013-November-5 Anti Mafia Activism 2dermafialks7aai.onion https://secure.mafialeaks.org Italy" - "ymi7h25hgp3bj63v.onion#InfodioLeaks 2014-January-28 Anticorruption Activism ymi7h25hgp3bj63v.onion https://ymi7h25hgp3bj63v.tor2web.org Venezuela" - "ppdz5djzpo3w5k2z.onion#WildLeaks [34][35][36][37][38][39] 2014-February-7 WildLife Crime Activism ppdz5djzpo3w5k2z.onion https://secure.wildleaks.org United States/Africa" - "pltloztihmfrg2sw.onion#Salzburger-Piratenpartei 2014-March-4 Activism pltloztihmfrg2sw.onion https://pltloztihmfrg2sw.tor2web.org Austria" - "ur5b2b4brz427ygh.onion#Nawaatleaks [40] 2014-March-27 Activism ur5b2b4brz427ygh.onion https://ur5b2b4brz427ygh.tor2web.org Tunisia" - "w6csjytbrl273che.onion#Filtrala [41][42] 2014-April-23 Anticorruption Activism w6csjytbrl273che.onion https://w6csjytbrl273che.tor2web.org/ Spain" - "abkjckdgoabr7bmm.onion#MediaDirect [43] 2014-May-11 Transparency Activism abkjckdgoabr7bmm.onion https://abkjckdgoabr7bmm.tor2web.org Australia" - "5r4bjnjug3apqdii.onion#ExpoLeaks[44] [45] [46] 2014-June-10 Investigative Journalism 5r4bjnjug3apqdii.onion https://5r4bjnjug3apqdii.tor2web.org/ Italy" - "bqs3dobnazs7h4u4.onion#ExtremeLeaks 2014-June-18 Investigative Journalism bqs3dobnazs7h4u4.onion https://www.extremeleaks.org/ Norway" - "fkut2p37apcg6l7f.onion#Allerta Anticorruzione[47][48] 2014-October-14 Anticorruption Activism fkut2p37apcg6l7f.onion https://alac.transparency.it Italy" - "6iolddfbfinntq2b.onion#Brussels Leaks 2014-October 24 Europe Focus Anticorruption Transparency Activism 6iolddfbfinntq2b.onion https://6iolddfbfinntq2b.tor2web.org Belgium" -) - -## pool three. -## info: -## individual websites -## riseup.net List -## https://help.riseup.net/en/tor#riseups-tor-hidden-services -## removed because down: -## "suw74isz7wqzpmgu.onion:80#https://www.wikileaks.org/wiki/WikiLeaks:Tor" -## removed because no http: -## "4cjw6cwpeaeppfqz.onion#xmpp.riseup.net: 4cjw6cwpeaeppfqz.onion (ports 5222, 5269)" -SDWDATE_POOL_THREE=( - "3g2upl4pq6kufc4m.onion:80#https://duck.co/forum/thread/1762/is-the-duckduckgo-hidden-service-legitimate" - "dju2peblv7upfz3q.onion:80#https://guardianproject.info/2014/10/16/reducing-metadata-leakage-from-software-updates/" - "msydqstlz2kzerdg.onion:80#https://ahmia.fi/address/msydqstlz2kzerdg" - "uj3wazyk5u4hnvtk.onion:80#https://thepiratebay.se/blog/238" - "bitmailendavkbec.onion:80#https://bitmessage.org/forum/index.php?topic=1556.0" - "wi7qkxyrdpu5cmvr.onion#Austici www.autistici.org/en/stuff/man_anon/tor.html" - "ic6au7wa3f6naxjq.onion#https://lists.gnupg.org/pipermail/gnupg-users/2014-April/049578.html" - " - nzh3fv6jc6jskki3#riseup.net: nzh3fv6jc6jskki3.onion (port 443) - nzh3fv6jc6jskki3.onion#help.riseup.net: nzh3fv6jc6jskki3.onion (port 443) - cwoiopiifrlzcuos.onion#black.riseup.net: cwoiopiifrlzcuos.onion (port 443) - zsolxunfmbfuq7wf.onion#imap.riseup.net: zsolxunfmbfuq7wf.onion (port 993) - yfm6sdhnfbulplsw.onion#labs.riseup.net: yfm6sdhnfbulplsw.onion (port 80, 443) - xpgylzydxykgdqyg.onion#lists.riseup.net: xpgylzydxykgdqyg.onion (port 80, 443) - zsolxunfmbfuq7wf.onion#mail.riseup.net: zsolxunfmbfuq7wf.onion (ports 443, 465, 587) - 5jp7xtmox6jyoqd5:443#pad.riseup.net: 5jp7xtmox6jyoqd5.onion (port 443) (note: only works with https://5jp7xtmox6jyoqd5.onion) - zsolxunfmbfuq7wf.onion#pop.riseup.net: zsolxunfmbfuq7wf.onion (port 995) - zsolxunfmbfuq7wf.onion#smtp.riseup.net: zsolxunfmbfuq7wf.onion (ports 465, 587) - j6uhdvbhz74oefxf.onion#user.riseup.net: j6uhdvbhz74oefxf.onion (port 80, 443) - 7lvd7fa5yfbdqaii.onion#we.riseup.net: 7lvd7fa5yfbdqaii.onion (port 443) - " - "timaq4ygg2iegci7.onion#https://github.com/meejah/txtorcon http://txtorcon.readthedocs.org" - "344c6kbnjnljjzlz.onion#VFEmail https://www.vfemail.net" - "fncuwbiisyh6ak3i.onion#https://keybase.io/docs/command_line/tor" -) diff --git a/etc/sdwdate-python.d/30_sdwdate_default.conf b/etc/sdwdate.d/30_sdwdate_default.conf similarity index 100% rename from etc/sdwdate-python.d/30_sdwdate_default.conf rename to etc/sdwdate.d/30_sdwdate_default.conf diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index cea7c676..ea9bee04 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -70,8 +70,8 @@ def read_pools(pool): pool_two_url = [] pool_three_url = [] - if os.path.exists('/etc/sdwdate-python.d/'): - files = sorted(glob.glob('/etc/sdwdate-python.d/*')) + if os.path.exists('/etc/sdwdate.d/'): + files = sorted(glob.glob('/etc/sdwdate.d/*')) if files: conf_found = False From cd5a79813d9ec8bdff5c0a14773b3687fa41a83c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 9 Oct 2015 20:39:25 +0000 Subject: [PATCH 139/183] cleanup etc/sdwdate.d/30_sdwdate_default.conf --- etc/sdwdate.d/30_sdwdate_default.conf | 208 -------------------------- 1 file changed, 208 deletions(-) diff --git a/etc/sdwdate.d/30_sdwdate_default.conf b/etc/sdwdate.d/30_sdwdate_default.conf index e1b413cb..09870071 100644 --- a/etc/sdwdate.d/30_sdwdate_default.conf +++ b/etc/sdwdate.d/30_sdwdate_default.conf @@ -2,214 +2,6 @@ ## Copyright (C) 2012 - 2014 Patrick Schleizer ## See the file COPYING for copying conditions. -## Please use "/etc/sdwdate.d/50_sdwdate_user" for your custom -## configuration, which will override the defaults found here. -## When sdwdate is updated, this file may be overwritten. - -## Bash Fragment. - -## Enable/disable debugging. -## 1 enabled. -## 2 disabled. -DEBUG=0 - -## Run as the following user name. -## Not implemented. Has no effect. -USER="" - -## Do or do not actually change the date/time after successfully fetching it. -## 0 set date. -## 1 do not set date. -DONT_SET_DATE=0 - -## do not move the time forward -## 0 disabled -## 1 enabled -NO_MOVE_FORWARD=0 - -## do not move the time backwards -## 0 disabled -## 1 enabled -NO_MOVE_BACKWARDS=0 - -## update hardware clock -## 0 disabled -## 1 enabled -SYSTOHC=0 - -## Log file. -LOG_FILE=/var/log/sdwdate.log - -## Done file. Will be created after run no matter if failure or success. -DONE_FILE=/var/run/sdwdate/done - -## Success file. Will only be created after a success. -SUCCESS_FILE=/var/run/sdwdate/success - -## First success file. Will be created after the first success. -FIRST_SUCCESS_FILE=/var/run/sdwdate/first_success - -## How many members per pool are allowed to fail. -## If too many members are not reachable, time will not be adjusted. -ALLOWED_PER_POOL_FAILURE_RATIO=0.34 - -## Temporary directory for file downloads. -## When not set, default to: TEMP_DIR="$(mktemp --directory)" -#TEMP_DIR="" - -## Cache dir. Must not include spaces. -SDW_CACHE_DIR="/var/cache/sdwdate/sclockadj" - -## proxy IP -PROXY_IP="127.0.0.1" - -## proxy port -PROXY_PORT="9050" - -## How often sdwdate should run in minutes. -## 0 disables it and sdwdate exits after run. -INTERVAL="180" - -## How many minutes should be waited before running sdwdate again. -## Only has an effect when RANDOMIZE is set to 1 as well. -MIN_INTERVAL="60" - -## Randomize the interval above. -## Minimum 60 minutes. -## Maximum $INTERVAL minutes. -## 0 disabled. -## 1 enabled. -RANDOMIZE="1" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## starting up. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 0 -SDWDATE_USE_SCLOCKADJ_WHEN_STARTUP="0" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## re-starting up when sdwdate succeeded at least once after boot. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 1 -SDWDATE_USE_SCLOCKADJ_WHEN_RESTARTUP="1" - -## Use sclockadj instead of /bin/date (which would produce clock jumps) when -## in daemon mode. -## 0 sclockadj disabled -## 1 sclockadj enabled -## defaults to: 1 -SDWDATE_USE_SCLOCKADJ_WHEN_DAEMON="1" - -## sclockadj verbose logging or not -## --no-verbose -## --verbose -## defaults to: --no-verbose -SDWDATE_SCLOCKADJ_VERBOSE="--no-verbose" - -## sclockadj change date or not -## --no-debug -## --debug -## defaults to: --no-debug (change date) -SDWDATE_SCLOCKADJ_CHANGE_DATE="--no-debug" - -## If sclockadj should wait before its first iteration. -## --no-first-wait -## --first-wait -## default to: --no-first-wait -SDWDATE_SCLOCKADJ_FIRST_WAIT="--no-first-wait" - -## Move clock minimum nanoseconds (except rest). -## defaults to: 500000 -## (500000 ns = 0.5 ms = 0.0005 s) -SDWDATE_SCLOCKADJ_MOVE_MIN="500000" - -## Move clock maximum nanoseconds (except rest). -## defaults to: 500000 -## (500000 ns = 0.5 ms = 0.0005 s) -SDWDATE_SCLOCKADJ_MOVE_MAX="500000" - -## Wait nanoseconds minimum before next iteration. -## defaults to: 1000000000 -## (1000000000 ns = 1000 ms = 1 s) -SDWDATE_SCLOCKADJ_WAIT_MIN="1000000000" - -## Wait nanoseconds maximum before next iteration. -## defaults to: 1000000000 -## (1000000000 ns = 1000 ms = 1 s) -SDWDATE_SCLOCKADJ_WAIT_MAX="1000000000" - -## This command will be `eval`uated before DISPATCH_PREREQUISITE and before running url to unixtime tool. -## sdwdate provides the $SDW_MODE variable, which is either set to -## - startup (when the sdwdate process is started) -## - daemon (when the sdwdate process finished one loop and will continue) -## When set to "", it will be skipped. -DISPATCH_PRE="" - -## Prerequisite before trying to connect to servers. -## This is supposed to be a command to be `eval`uated and to exit with code -## - 0, if sdwdate should continue. -## - 1, if sdwdate should terminate itself due to an expected error. -## - 2, if sdwdate should wait 10 seconds and then run the command again. -## - Anything else, if sdwdate should terminate itself due to an unexpected error. -## It may be useful to check if the network is already reachable. -## When set to "", it will be skipped. -DISPATCH_PREREQUISITE="" - -## This command will be `eval`uated when an unexpected error (bug) in sdwdate has been caught. -## sdwdate will provide the $error_message and $DONE_FILE variable. -## Remember to escape variables either using \$ or '$variable'. -## When set to "", it will be skipped. -DISPATCH_POST_ERROR="" - -## Create $DONE_FILE on error. -## 1 enabled. -## 0 disabled. -SDW_TOUCH_DONE_FILE_ON_ERROR="1" - -## Exit 1 on error. This will stop the daemon from running. -## 1 enabled. -## 0 disabled. -SDW_EXIT_ON_ERROR="1" - -## echo remote unix time even when using --quiet -## true - enabled. -## false - disabled. -ECHO_UNIX_TIME="false" - -## This command will be `eval`uated when sdwdate succeeded. -## When set to "", it will be skipped. -DISPATCH_POST_SUCCESS="" - -## This command will be `eval`uated when sdwdate failed. -## When set to "", it will be skipped. -DISPATCH_POST_FAILURE="" - -## This command will be `eval`uated before trying to connect to the pool one. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_ONE]="" - -## This command will be `eval`uated before after connecting to the pool one. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_ONE]="" - -## This command will be `eval`uated before trying to connect to the pool two. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_TWO]="" - -## This command will be `eval`uated before after connecting to the pool two. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_TWO]="" - -## This command will be `eval`uated before trying to connect to pool three. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_PRE[SDWDATE_POOL_THREE]="" - -## This command will be `eval`uated before trying after connecting to the pool three. -## When set to "", it will be skipped. -SDWDATE_TIME_TOOL_DISPATCH_POST[SDWDATE_POOL_THREE]="" - ## pool syntax ## "url.onion[:port]#comment" ## " From 061da44790f42f50fe1d2d504e4d16eb23e644c7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 10 Oct 2015 17:05:49 +0000 Subject: [PATCH 140/183] prerequisite_check: strip message --- usr/bin/sdwdate | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 36d9872d..d88b9e88 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -467,11 +467,13 @@ def prerequisite_check(): if value == '': tor_status = True else: - print value.strip() - logger.warning(value.strip()) sdwdate = Sdwdate() + message = value.strip() + stripped_message = sdwdate.strip_html(message) + print stripped_message + logger.warning(stripped_message) icon = sdwdate.error_icon - sdwdate.write_status(icon, value) + sdwdate.write_status(icon, message) time.sleep(10) if __name__ == "__main__": @@ -520,7 +522,6 @@ if __name__ == "__main__": sleep_time = 10 log_level = 'warning' - message = (sdwdate.message + sdwdate._('sleeping') + str(sleep_time) + sdwdate._('minutes')) stripped_message = sdwdate.strip_html(message) From 956bcd78a96e9cd0e3d32422babc106ea5cc1cf9 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 10 Oct 2015 21:37:43 +0000 Subject: [PATCH 141/183] comment in etc/sdwdate.d/30_sdwdate_default.conf --- etc/sdwdate.d/30_sdwdate_default.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/sdwdate.d/30_sdwdate_default.conf b/etc/sdwdate.d/30_sdwdate_default.conf index 09870071..0ce1bf54 100644 --- a/etc/sdwdate.d/30_sdwdate_default.conf +++ b/etc/sdwdate.d/30_sdwdate_default.conf @@ -2,6 +2,10 @@ ## Copyright (C) 2012 - 2014 Patrick Schleizer ## See the file COPYING for copying conditions. +## Please use "/etc/sdwdate.d/50_user" for your custom +## configuration, which will override the defaults found here. +## When sdwdate is updated, this file may be overwritten. + ## pool syntax ## "url.onion[:port]#comment" ## " From 013e273c0fe84512c3f40fbde1172fc5e46483b7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 10 Oct 2015 21:50:03 +0000 Subject: [PATCH 142/183] renamed: etc/sdwdate.d/30_sdwdate_default.conf -> etc/sdwdate.d/30_default.conf --- etc/sdwdate.d/{30_sdwdate_default.conf => 30_default.conf} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/sdwdate.d/{30_sdwdate_default.conf => 30_default.conf} (100%) diff --git a/etc/sdwdate.d/30_sdwdate_default.conf b/etc/sdwdate.d/30_default.conf similarity index 100% rename from etc/sdwdate.d/30_sdwdate_default.conf rename to etc/sdwdate.d/30_default.conf From 07317969d82c08a67f6415b12a899df7a35f8c0d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 10 Oct 2015 22:22:08 +0000 Subject: [PATCH 143/183] prerequisite_check: one shot logging --- usr/bin/sdwdate | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index d88b9e88..59e80fd1 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -459,6 +459,8 @@ def prerequisite_check(): temp_dir = check_output(["mktemp", "--directory"]) os.environ["TEMP_DIR"] = temp_dir.strip() tor_status = False + message = '' + previous_messsage = '' while not tor_status: prerequisite_status = Popen(prerequisite_path, stdout=PIPE, @@ -469,11 +471,13 @@ def prerequisite_check(): else: sdwdate = Sdwdate() message = value.strip() - stripped_message = sdwdate.strip_html(message) - print stripped_message - logger.warning(stripped_message) - icon = sdwdate.error_icon - sdwdate.write_status(icon, message) + if message != previous_messsage: + previous_messsage = message + stripped_message = sdwdate.strip_html(message) + print stripped_message + logger.warning(stripped_message) + icon = sdwdate.error_icon + sdwdate.write_status(icon, message) time.sleep(10) if __name__ == "__main__": From ac5df6dacaf061458358abf75c74a1f122196ca8 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 17:58:22 +0000 Subject: [PATCH 144/183] remove debugging print statements --- usr/bin/sdwdate | 3 --- 1 file changed, 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 59e80fd1..a4acd051 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -292,7 +292,6 @@ class Sdwdate(): current_unixtime = time.time() time_sanity_check = self.time_sanity_check(current_unixtime) stripped_message = self.strip_html(self.message) - print stripped_message if time_sanity_check == 'sane': logger.info(stripped_message) @@ -301,8 +300,6 @@ class Sdwdate(): fetching_msg = self._('fetching') restricted_msg = self._('restricted') - print restricted_msg - print fetching_msg if self.success: self.write_status(self.success_icon, fetching_msg) logger.info(fetching_msg) From 81e9e6b9a00b120b88f4aba3870ef2c70e77f9c8 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 18:16:41 +0000 Subject: [PATCH 145/183] check for general errors only on first fetching iteration (three urls returned) --- usr/bin/sdwdate | 48 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index a4acd051..23fcd20c 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -78,15 +78,10 @@ class Sdwdate(): This error occurs (at least) when Tor is not running. ''' returned_error = 'connect error: Connection closed unexpectedly' - try: - if (pools[0] == returned_error and - pools[1] == returned_error and - pools[2] == returned_error): - return True - # If tor stops during a cycle, some urls are not returned, - # raising an error. - except IndexError: - return True + if (pools[0] == returned_error and + pools[1] == returned_error and + pools[2] == returned_error): + return True return False def general_timeout_error(self, pools): @@ -94,15 +89,10 @@ class Sdwdate(): This error occurs (at least) when internet connection is down. ''' returned_error = 'Timeout' - try: - if (pools[0] == returned_error and - pools[1] == returned_error and - pools[2] == returned_error): - return True - # If internet disconnects during a cycle, some urls are - # not returned, raising an error. - except IndexError: - return True + if (pools[0] == returned_error and + pools[1] == returned_error and + pools[2] == returned_error): + return True return False def check_remote(self, remote, value): @@ -361,15 +351,15 @@ class Sdwdate(): print(message) return self.error_icon, 'error' - if self.general_proxy_error(self.returned_values): - self.message = self._('general_proxy_error') - print(message) - return self.error_icon, 'error' - - if self.general_timeout_error(self.returned_values): - self.message = self._('general_timeout_error') - print(message) - return self.error_icon, 'error' + if len(self.returned_values) > 2: + if self.general_proxy_error(self.returned_values): + self.message = self._('general_proxy_error') + print(message) + return self.error_icon, 'error' + if self.general_timeout_error(self.returned_values): + self.message = self._('general_timeout_error') + print(message) + return self.error_icon, 'error' for i in range(len(self.urls)): if self.check_remote(self.urls[i], self.returned_values[i]): @@ -516,11 +506,11 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - sleep_time = 10 + sleep_time = 0.1 log_level = 'info' elif status == 'error': - sleep_time = 10 + sleep_time = 0.1 log_level = 'warning' message = (sdwdate.message + sdwdate._('sleeping') + From c5d9144caf698c619b374183f20d8f4cd84dff42 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 18:33:18 +0000 Subject: [PATCH 146/183] new comments in "no valid time returned from pool", most likely happening if tor or internet fails during a sdwdate cycle --- usr/share/translations/sdwdate.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index aaf46d50..2c4d9c00 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -18,10 +18,11 @@ sdwdate: - a host clock attack succeeded.
      - a bios clock failure.
      ' - no_valid_time: 'Time is not set: no valid time returned from pool ' + no_valid_time: 'Time is not set: no valid time returned from pool
      + Possible causes::
      - internet connection is down<.br>- tor is not running' no_value_returned: 'No values returned from servers.' list_not_built: 'Something is wrong. sdwdate could not build a list or urls.' restart: '
      Restart sdwdate. If the problem persists, please report this bug.
      ' general_proxy_error: 'General Proxy Error. Is Tor running?' - general_timeout_error: 'General Timeout Error. Internet might be down' + general_timeout_error: 'General Timeout Error. Internet connection might be down' From d2420410156af54b9ad92a0eb2df983c7e7164b3 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 20:23:52 +0000 Subject: [PATCH 147/183] sdwdate.yaml typo --- usr/share/translations/sdwdate.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index 2c4d9c00..3bd51c8c 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -19,7 +19,8 @@ sdwdate: - a bios clock failure.
      ' no_valid_time: 'Time is not set: no valid time returned from pool
      - Possible causes::
      - internet connection is down<.br>- tor is not running' + Possible causes:
      - internet connection is down +
      - tor is not running' no_value_returned: 'No values returned from servers.' list_not_built: 'Something is wrong. sdwdate could not build a list or urls.' restart: '
      Restart sdwdate. If the problem persists, please report this bug.
      ' From f0884c91cddd065337e0edd97581f2f62584aa72 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 20:25:59 +0000 Subject: [PATCH 148/183] new file: usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py --- .../dist-packages/sdwdate/proxy_settings.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py diff --git a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py new file mode 100644 index 00000000..39c91633 --- /dev/null +++ b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py @@ -0,0 +1,38 @@ +import os +import glob +import re + + +def proxy_settings(): + ip_address = '' + port_number = '' + + if os.path.exists('/etc/sdwdate.d/'): + files = sorted(glob.glob('/etc/sdwdate.d/*')) + for f in files: + with open(f) as conf: + lines = conf.readlines() + for line in lines: + if line.startswith('PROXY_IP'): + ip_address = re.search(r'=(.*)', line).group(1) + if line.startswith('PROXY_PORT'): + port_number = re.search(r'=(.*)', line).group(1) + + if ip_address == '': + if os.path.exists('/usr/share/anon-gw-base-files/gateway'): + ip_address = '127.0.0.1' + elif os.path.exists('/usr/lib/qubes-whonix'): + ip_address = check_output(['qubesdb-read', '/qubes-gateway']) + else: + ip_address = '10.152.152.10' + + if port_number == '': + port_number = '9108' + + print 'ip %s port %s' % (ip_address, port_number) + return ip_address, port_number + +if __name__ == "__main__": + proxy_settings() + + From 9fc7092ce3ff52cb626a17a0899ac91862346b57 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 20:28:12 +0000 Subject: [PATCH 149/183] etc/sdwdate.d/30_default.conf -> proxy settings for non anonymous distributions --- etc/sdwdate.d/30_default.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/sdwdate.d/30_default.conf b/etc/sdwdate.d/30_default.conf index 0ce1bf54..1744e156 100644 --- a/etc/sdwdate.d/30_default.conf +++ b/etc/sdwdate.d/30_default.conf @@ -6,6 +6,11 @@ ## configuration, which will override the defaults found here. ## When sdwdate is updated, this file may be overwritten. +## Proxy settings for non anonymous distributions. +## Uncomment for standard tor configuration (no stream isolation). +#PROXY_IP=127.0.0.1 +#PROXY_PORT=9050 + ## pool syntax ## "url.onion[:port]#comment" ## " From 9daf3d60e359529e585e7434af8f380c894f5f53 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 11 Oct 2015 20:33:41 +0000 Subject: [PATCH 150/183] proxy settings passed from sdwdate_loop to remote times --- usr/bin/sdwdate | 7 ++++++- .../python2.7/dist-packages/sdwdate/remote_times.py | 11 ++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 23fcd20c..dfff40c9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -16,6 +16,7 @@ import re from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools from sdwdate.timesanitycheck import timesanitycheck +from sdwdate.proxy_settings import proxy_settings from guimessages.translations import _translations @@ -73,6 +74,8 @@ class Sdwdate(): translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext + self.proxy_ip, self.proxy_port = proxy_settings() + def general_proxy_error(self, pools): ''' This error occurs (at least) when Tor is not running. @@ -335,7 +338,9 @@ class Sdwdate(): print(message) logger.info(message) - self.urls, self.returned_values = get_time_from_servers(self.url_random) + self.urls, self.returned_values = get_time_from_servers(self.url_random, + self.proxy_ip, + self.proxy_port) if len(self.urls) == 0: self.message = self._('no_value_returned') + self._('restart') diff --git a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py index e1f5c0ad..3e671099 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py @@ -9,7 +9,7 @@ import gevent from gevent.subprocess import Popen, PIPE -def get_time_from_servers(remotes): +def get_time_from_servers(remotes, ip_address, port_number): url_to_unixtime_path = '/usr/lib/sdwdate/url_to_unixtime' threads = [] @@ -22,17 +22,10 @@ def get_time_from_servers(remotes): del urls[:] del unix_times[:] - if os.path.exists('/usr/share/anon-gw-base-files/gateway'): - ip_address = '127.0.0.1' - elif os.path.exists('/usr/lib/qubes-whonix'): - ip_address = check_output(['qubesdb-read', '/qubes-gateway']) - else: - ip_address = '10.152.152.10' - for i in range(len(remotes)): threads.append(Popen([url_to_unixtime_path, ip_address, - '9108', + port_number, remotes[i], '80', '0'], stdout=PIPE)) From 9e42a51c9a2da19041858f290bde54a4a9ccc103 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 14 Oct 2015 19:55:08 +0000 Subject: [PATCH 151/183] run prerequisite_check if /usr/lib/anon-shared-helper-scripts/te_pe_tb_check exists --- usr/bin/sdwdate | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index dfff40c9..005e69d6 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -74,6 +74,10 @@ class Sdwdate(): translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext + self.prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' + if os.path.exists(self.prerequisite_path): + self.run_prerequisite = True + self.proxy_ip, self.proxy_port = proxy_settings() def general_proxy_error(self, pools): @@ -447,21 +451,20 @@ def signal_sigterm_handler(signum, frame): sys.exit(143) def prerequisite_check(): - prerequisite_path = ['/usr/lib/anon-shared-helper-scripts/te_pe_tb_check'] temp_dir = check_output(["mktemp", "--directory"]) os.environ["TEMP_DIR"] = temp_dir.strip() tor_status = False message = '' previous_messsage = '' + sdwdate = Sdwdate() while not tor_status: - prerequisite_status = Popen(prerequisite_path, + prerequisite_status = Popen(sdwdate.prerequisite_path, stdout=PIPE, stderr=PIPE) value, error = prerequisite_status.communicate() if value == '': tor_status = True else: - sdwdate = Sdwdate() message = value.strip() if message != previous_messsage: previous_messsage = message @@ -491,7 +494,9 @@ if __name__ == "__main__": print pid_message logger.info(pid_message) - prerequisite_check() + sdwdate = Sdwdate() + if sdwdate.run_prerequisite: + prerequisite_check() while True: sdwdate = Sdwdate() From 2378b2e9f4274c90186214078a5a697933ddb1f2 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 14 Oct 2015 20:07:12 +0000 Subject: [PATCH 152/183] create temp dir for te_pe_tb_check in sdwdate.__init__ --- usr/bin/sdwdate | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 005e69d6..9a0c6bc9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -74,8 +74,12 @@ class Sdwdate(): translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext + self.run_prerequisite = False self.prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' if os.path.exists(self.prerequisite_path): + ## Create temp dir for te_pe_tb_check + temp_dir = check_output(["mktemp", "--directory"]) + os.environ["TEMP_DIR"] = temp_dir.strip() self.run_prerequisite = True self.proxy_ip, self.proxy_port = proxy_settings() @@ -451,8 +455,6 @@ def signal_sigterm_handler(signum, frame): sys.exit(143) def prerequisite_check(): - temp_dir = check_output(["mktemp", "--directory"]) - os.environ["TEMP_DIR"] = temp_dir.strip() tor_status = False message = '' previous_messsage = '' From b15a38b2878808c7469f400c9665657ee6a1f234 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Wed, 14 Oct 2015 20:45:22 +0000 Subject: [PATCH 153/183] Revert "create temp dir for te_pe_tb_check in sdwdate.__init__" This reverts commit 2378b2e9f4274c90186214078a5a697933ddb1f2. --- usr/bin/sdwdate | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 9a0c6bc9..005e69d6 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -74,12 +74,8 @@ class Sdwdate(): translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext - self.run_prerequisite = False self.prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' if os.path.exists(self.prerequisite_path): - ## Create temp dir for te_pe_tb_check - temp_dir = check_output(["mktemp", "--directory"]) - os.environ["TEMP_DIR"] = temp_dir.strip() self.run_prerequisite = True self.proxy_ip, self.proxy_port = proxy_settings() @@ -455,6 +451,8 @@ def signal_sigterm_handler(signum, frame): sys.exit(143) def prerequisite_check(): + temp_dir = check_output(["mktemp", "--directory"]) + os.environ["TEMP_DIR"] = temp_dir.strip() tor_status = False message = '' previous_messsage = '' From 9f3a4a632bc01c9d178a23c477867991faf45c8a Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 15 Oct 2015 11:00:16 +0000 Subject: [PATCH 154/183] class PreCheckVariables: set run_prerequite true and create temp_dir if /usr/lib/anon-shared-helper-scripts/te_pe_tb_check exists --- usr/bin/sdwdate | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 005e69d6..230ddfa4 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -12,6 +12,7 @@ import subprocess from subprocess import Popen, call, PIPE, check_output import pickle import re +from shutil import rmtree from sdwdate.remote_times import get_time_from_servers from sdwdate.config import read_pools @@ -21,6 +22,16 @@ from sdwdate.proxy_settings import proxy_settings from guimessages.translations import _translations +class PreCheckVariables: + run_prerequisite = False + prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' + if os.path.exists(prerequisite_path): + ## Create tenp dir for te_pe_tb_check. + temp_dir = check_output(["mktemp", "--directory"]).strip() + os.environ["TEMP_DIR"] = temp_dir + run_prerequisite = True + + class Pool: def __init__(self, pool): self.url, self.comment = read_pools(pool) @@ -74,10 +85,6 @@ class Sdwdate(): translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext - self.prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' - if os.path.exists(self.prerequisite_path): - self.run_prerequisite = True - self.proxy_ip, self.proxy_port = proxy_settings() def general_proxy_error(self, pools): @@ -438,6 +445,7 @@ class Sdwdate(): def signal_sigterm_handler(signum, frame): + tmp_dir = PreCheckVariables.temp_dir # Inform sdwdate-gui icon = sdwdate.error_icon message = sdwdate._('user_kill') @@ -446,19 +454,21 @@ def signal_sigterm_handler(signum, frame): if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() + ## Remove te_pe_tb_check temp directory. + rmtree(tmp_dir) logger.info(stripped_message) sys.exit(143) + def prerequisite_check(): - temp_dir = check_output(["mktemp", "--directory"]) - os.environ["TEMP_DIR"] = temp_dir.strip() + pre_check = PreCheckVariables() tor_status = False message = '' previous_messsage = '' sdwdate = Sdwdate() while not tor_status: - prerequisite_status = Popen(sdwdate.prerequisite_path, + prerequisite_status = Popen(pre_check.prerequisite_path, stdout=PIPE, stderr=PIPE) value, error = prerequisite_status.communicate() @@ -480,6 +490,7 @@ if __name__ == "__main__": ## status changes. See related comment in sdwdate-gui. time.sleep(0.2) + pre_check = PreCheckVariables() signal.signal(signal.SIGTERM, signal_sigterm_handler) logger = logging.getLogger('sdwdate.log') @@ -494,8 +505,7 @@ if __name__ == "__main__": print pid_message logger.info(pid_message) - sdwdate = Sdwdate() - if sdwdate.run_prerequisite: + if pre_check.run_prerequisite: prerequisite_check() while True: From 1dfcc8da2f3bd58d17893bd8d8cb5095ceedee6c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 15 Oct 2015 11:52:31 +0000 Subject: [PATCH 155/183] check run_prerequisite before removing temp_dir --- usr/bin/sdwdate | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 230ddfa4..cd71c444 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -445,7 +445,6 @@ class Sdwdate(): def signal_sigterm_handler(signum, frame): - tmp_dir = PreCheckVariables.temp_dir # Inform sdwdate-gui icon = sdwdate.error_icon message = sdwdate._('user_kill') @@ -455,7 +454,9 @@ def signal_sigterm_handler(signum, frame): if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() ## Remove te_pe_tb_check temp directory. - rmtree(tmp_dir) + pre_check = PreCheckVariables() + if pre_check.run_prerequisite: + rmtree(pre_check.temp_dir) logger.info(stripped_message) sys.exit(143) From 23ff604be643705f4955c3958e28c911b3b1751a Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 15 Oct 2015 20:21:52 +0000 Subject: [PATCH 156/183] check "usr/share/whonix", check configuration PROXY settings, default to 127.0.0.1:9050 --- .../dist-packages/sdwdate/proxy_settings.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py index 39c91633..b0003c7e 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py @@ -18,16 +18,26 @@ def proxy_settings(): if line.startswith('PROXY_PORT'): port_number = re.search(r'=(.*)', line).group(1) - if ip_address == '': + if os.path.exists('/usr/share/whonix'): if os.path.exists('/usr/share/anon-gw-base-files/gateway'): ip_address = '127.0.0.1' elif os.path.exists('/usr/lib/qubes-whonix'): ip_address = check_output(['qubesdb-read', '/qubes-gateway']) else: ip_address = '10.152.152.10' + elif ip_address != '': + ## ip_address = PROXY_IP + pass + else: + ip_address = '1270.0.1' - if port_number == '': + if os.path.exists('/usr/share/whonix'): port_number = '9108' + elif port_number != '': + ## port_number = PROXY_PORT + pass + else: + port_number = '9050' print 'ip %s port %s' % (ip_address, port_number) return ip_address, port_number From ad96d7fcacebf3c2f4c81f46a2c0fd72bb7aa25d Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 15 Oct 2015 20:43:42 +0000 Subject: [PATCH 157/183] prerequisite_check: check if file is executable --- usr/bin/sdwdate | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index cd71c444..a834024c 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -25,7 +25,7 @@ from guimessages.translations import _translations class PreCheckVariables: run_prerequisite = False prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' - if os.path.exists(prerequisite_path): + if os.access(prerequisite_path, os.X_OK): ## Create tenp dir for te_pe_tb_check. temp_dir = check_output(["mktemp", "--directory"]).strip() os.environ["TEMP_DIR"] = temp_dir @@ -476,7 +476,7 @@ def prerequisite_check(): if value == '': tor_status = True else: - message = value.strip() + message = 'Prerequsite check:
      %s' % value.strip() if message != previous_messsage: previous_messsage = message stripped_message = sdwdate.strip_html(message) @@ -527,11 +527,11 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - sleep_time = 0.1 + sleep_time = 10 log_level = 'info' elif status == 'error': - sleep_time = 0.1 + sleep_time = 10 log_level = 'warning' message = (sdwdate.message + sdwdate._('sleeping') + From c5af008288e490195a0020820eb3e81c234c49b3 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 15 Oct 2015 21:38:56 +0000 Subject: [PATCH 158/183] missing dot --- usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py index b0003c7e..22064ecf 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py @@ -29,7 +29,7 @@ def proxy_settings(): ## ip_address = PROXY_IP pass else: - ip_address = '1270.0.1' + ip_address = '127.0.0.1' if os.path.exists('/usr/share/whonix'): port_number = '9108' From 1b84af9c8199865d9e302466120216893f09e9b5 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 16 Oct 2015 21:37:01 +0000 Subject: [PATCH 159/183] rewrite pool append loop --- usr/bin/sdwdate | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index a834024c..d5190b3f 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -327,21 +327,20 @@ class Sdwdate(): for pool in self.pools: if not pool.done: - ## Set to -1 as the first picked url index might be zero. - url_index = -1 - while url_index not in pool.already_picked_index: - pool_range = pool.url_range + pool_range = pool.url_range + url_index = random.randrange(0, pool_range) + while url_index in pool.already_picked_index: url_index = random.randrange(0, pool_range) - if len(pool.already_picked_index) >= pool_range: - pool_number = self.pools.index(pool) + 1 - self.message = self._('no_valid_time') + str(pool_number) + self._('restart') - logger.warning(self.message) - return self.error_icon, 'error' + pool.already_picked_index.append(url_index) + if len(pool.already_picked_index) >= pool_range: + pool_number = self.pools.index(pool) + 1 + self.message = self._('no_valid_time') + str(pool_number) + self._('restart') + logger.warning(self.message) + return self.error_icon, 'error' - pool.already_picked_index.append(url_index) - pool.url_random.append(pool.url[url_index]) - self.url_random.append(pool.url[url_index]) + pool.url_random.append(pool.url[url_index]) + self.url_random.append(pool.url[url_index]) ## Fetch remotes. if len(self.url_random) > 0: From a0ac1b866c9bd05be606ff10a074da43d1e5c128 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 16 Oct 2015 22:07:40 +0000 Subject: [PATCH 160/183] + pool allowed failure ratio --- usr/bin/sdwdate | 11 +++++++++++ usr/share/translations/sdwdate.yaml | 2 ++ 2 files changed, 13 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index d5190b3f..abecf62d 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -37,6 +37,9 @@ class Pool: self.url, self.comment = read_pools(pool) self.url_random = [] self.already_picked_index = [] + allowed_failure_ratio = 0.34 + self.invalid_urls = 0 + self.allowed_failures = int(len(self.url) * allowed_failure_ratio) self.done = False @property @@ -383,6 +386,14 @@ class Sdwdate(): else: self.invalid_urls.append(self.urls[i]) self.url_errors.append(self.returned_values[i]) + for pool in self.pools: + if self.urls[i] in pool.url_random: + pool.invalid_urls += 1 + if pool.invalid_urls >= pool.allowed_failures: + self.message = (self._('max_pool_failures') + + str(self.pools.index(pool) + 1) + + ' (' + str(pool.invalid_urls) + ')
      ') + return self.error_icon, 'error' old_unixtime = (time.time()) diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index 3bd51c8c..5c43022e 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -27,3 +27,5 @@ sdwdate: general_proxy_error: 'General Proxy Error. Is Tor running?' general_timeout_error: 'General Timeout Error. Internet connection might be down' + + max_pool_failures: 'Maximum number of allowed failures reached in pool ' From 8f40791e24a656211600168b75751266975291cb Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 16 Oct 2015 22:25:35 +0000 Subject: [PATCH 161/183] + random sleep time --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index abecf62d..7cf52c77 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -537,7 +537,7 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - sleep_time = 10 + sleep_time = randint(60, 180) log_level = 'info' elif status == 'error': From 064f27675ff44396d2911ae3c265f5a7cc9ed445 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 17 Oct 2015 20:40:57 +0000 Subject: [PATCH 162/183] get proxy settings from /usr/lib/anon-shared-helper-scripts/settings_echo --- .../dist-packages/sdwdate/proxy_settings.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py index 22064ecf..cced1c23 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py @@ -1,11 +1,12 @@ import os import glob import re - +from subprocess import check_output def proxy_settings(): ip_address = '' port_number = '' + settings_path = '/usr/lib/anon-shared-helper-scripts/settings_echo' if os.path.exists('/etc/sdwdate.d/'): files = sorted(glob.glob('/etc/sdwdate.d/*')) @@ -18,13 +19,10 @@ def proxy_settings(): if line.startswith('PROXY_PORT'): port_number = re.search(r'=(.*)', line).group(1) - if os.path.exists('/usr/share/whonix'): - if os.path.exists('/usr/share/anon-gw-base-files/gateway'): - ip_address = '127.0.0.1' - elif os.path.exists('/usr/lib/qubes-whonix'): - ip_address = check_output(['qubesdb-read', '/qubes-gateway']) - else: - ip_address = '10.152.152.10' + if (os.path.exists('/usr/share/whonix') and + os.access(settings_path, os.X_OK)): + proxy_settings = check_output(settings_path) + ip_address = re.search(r'"(.*)"', proxy_settings).group(1) elif ip_address != '': ## ip_address = PROXY_IP pass @@ -39,7 +37,7 @@ def proxy_settings(): else: port_number = '9050' - print 'ip %s port %s' % (ip_address, port_number) + #print 'ip %s port %s' % (ip_address, port_number) return ip_address, port_number if __name__ == "__main__": From 0bbd549a00c3c6ab1112a3cb6f8b6c80036d9a5f Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 18:21:17 +0000 Subject: [PATCH 163/183] redundant print statements --- usr/bin/sdwdate | 3 --- 1 file changed, 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 7cf52c77..56e3bf67 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -357,7 +357,6 @@ class Sdwdate(): if len(self.urls) == 0: self.message = self._('no_value_returned') + self._('restart') - print message return self.error_icon, 'error' message = 'Returned urls "%s"' % (self.urls) @@ -366,7 +365,6 @@ class Sdwdate(): else: self.message = self._('list_not_built') + self._('restart') - print(message) return self.error_icon, 'error' if len(self.returned_values) > 2: @@ -376,7 +374,6 @@ class Sdwdate(): return self.error_icon, 'error' if self.general_timeout_error(self.returned_values): self.message = self._('general_timeout_error') - print(message) return self.error_icon, 'error' for i in range(len(self.urls)): From 18d882b86a987b397029520e9e7aece7060b139c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:22:13 +0000 Subject: [PATCH 164/183] augmented statements --- usr/bin/sdwdate | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 56e3bf67..cd6161a1 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -319,7 +319,7 @@ class Sdwdate(): logger.info(fetching_msg) while len(self.valid_urls) < self.number_of_pools: - self.iteration = self.iteration + 1 + self.iteration += 1 message = 'Running sdwdate loop, iteration %s' % (self.iteration) print(message) logger.info(message) @@ -426,14 +426,14 @@ class Sdwdate(): message = 'Reachable urls:\n' for url in self.valid_urls: url_comment = self.get_comment(url) - message = message + '%s: "%s"\n' % (url, url_comment) + message += '%s: "%s"\n' % (url, url_comment) print(message.strip()) logger.info(message.strip()) message = 'Unreachable urls:\n' for url in self.invalid_urls: url_comment = self.get_comment(url) - message = message + '%s: "%s"\n' % (url, url_comment) + message += '%s: "%s"\n' % (url, url_comment) print(message.strip()) logger.info(message.strip()) From af304467d14bbf01a1850d2af207e0ffc32e1925 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:27:14 +0000 Subject: [PATCH 165/183] __init__ method in PreCheckVariables class --- usr/bin/sdwdate | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index cd6161a1..325306b6 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -23,6 +23,9 @@ from guimessages.translations import _translations class PreCheckVariables: + def __init__(self): + pass + run_prerequisite = False prerequisite_path = '/usr/lib/anon-shared-helper-scripts/te_pe_tb_check' if os.access(prerequisite_path, os.X_OK): From 09123374a4c8cb6f175823d96261a431fdeff3ab Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:34:34 +0000 Subject: [PATCH 166/183] static methods --- usr/bin/sdwdate | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 325306b6..d3552845 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -93,7 +93,8 @@ class Sdwdate(): self.proxy_ip, self.proxy_port = proxy_settings() - def general_proxy_error(self, pools): + @staticmethod + def general_proxy_error(pools): ''' This error occurs (at least) when Tor is not running. ''' @@ -104,7 +105,8 @@ class Sdwdate(): return True return False - def general_timeout_error(self, pools): + @staticmethod + def general_timeout_error(pools): ''' This error occurs (at least) when internet connection is down. ''' @@ -115,7 +117,8 @@ class Sdwdate(): return True return False - def check_remote(self, remote, value): + @staticmethod + def check_remote(remote, value): ''' Check returned value. True if numeric. ''' From 63205ecb3ba4595c98db64ccd200e4d485d71f2b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:38:41 +0000 Subject: [PATCH 167/183] redundant parentheses --- usr/bin/sdwdate | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index d3552845..da998de2 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -50,7 +50,7 @@ class Pool: return len(self.url) -class Sdwdate(): +class Sdwdate: def __init__(self): self.iteration = 0 self.number_of_pools = 3 @@ -124,7 +124,7 @@ class Sdwdate(): ''' try: n = int(value) - message = 'Remote status "%s", True' % (remote) + message = 'Remote status "%s", True' % remote print(message) logger.info(message) return True @@ -237,7 +237,7 @@ class Sdwdate(): sclockadj = Popen(cmd) self.sclockadj_pid = sclockadj.pid - message = 'Gradually adjusting the time by running sclockadj, PID=%s' % (self.sclockadj_pid) + message = 'Gradually adjusting the time by running sclockadj, PID=%s' % self.sclockadj_pid print(message) logger.info(message) @@ -279,7 +279,7 @@ class Sdwdate(): ## New line for log. tmp_message = re.sub('
      ', '\n', message) ## Strip rmaining HTML. - return(re.sub('<[^<]+?>', '', tmp_message)) + return re.sub('<[^<]+?>', '', tmp_message) def write_status(self, *args): self.status['icon'] = args[0] @@ -326,7 +326,7 @@ class Sdwdate(): while len(self.valid_urls) < self.number_of_pools: self.iteration += 1 - message = 'Running sdwdate loop, iteration %s' % (self.iteration) + message = 'Running sdwdate loop, iteration %s' % self.iteration print(message) logger.info(message) @@ -353,7 +353,7 @@ class Sdwdate(): ## Fetch remotes. if len(self.url_random) > 0: - message = 'Requested urls %s' % (self.url_random) + message = 'Requested urls %s' % self.url_random print(message) logger.info(message) @@ -365,7 +365,7 @@ class Sdwdate(): self.message = self._('no_value_returned') + self._('restart') return self.error_icon, 'error' - message = 'Returned urls "%s"' % (self.urls) + message = 'Returned urls "%s"' % self.urls print(message) logger.info(message) From 38661f00c92317b3b193397f08100f37414aea92 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:49:38 +0000 Subject: [PATCH 168/183] may return unbound variable url_comment --- usr/bin/sdwdate | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index da998de2..21fa8b98 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -84,10 +84,10 @@ class Sdwdate: self.first_success = os.path.exists(self.first_success_path) self.success = os.path.exists(self.success_path) - self.status = {'icon' : '', 'message' : ''} + self.status = {'icon': '', 'message': ''} self.message = '' - translations_path ='/usr/share/translations/sdwdate.yaml' + translations_path = '/usr/share/translations/sdwdate.yaml' translation = _translations(translations_path, 'sdwdate') self._ = translation.gettext @@ -100,9 +100,9 @@ class Sdwdate: ''' returned_error = 'connect error: Connection closed unexpectedly' if (pools[0] == returned_error and - pools[1] == returned_error and - pools[2] == returned_error): - return True + pools[1] == returned_error and + pools[2] == returned_error): + return True return False @staticmethod @@ -112,9 +112,9 @@ class Sdwdate: ''' returned_error = 'Timeout' if (pools[0] == returned_error and - pools[1] == returned_error and - pools[2] == returned_error): - return True + pools[1] == returned_error and + pools[2] == returned_error): + return True return False @staticmethod @@ -138,6 +138,7 @@ class Sdwdate: ''' For logging the commnents, get the index of the url to get it from pool.comment. ''' + url_comment = '' for url in self.pools: try: url_index = url.url.index(remote) @@ -151,13 +152,13 @@ class Sdwdate: if status == 'sane': self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one) - #'The clock is %s. Current time "%s".' % (status, time_one) + # 'The clock is %s. Current time "%s".' % (status, time_one) elif status == 'slow': self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one + self._('slow_clock') + time_two) + self._('cause') elif status == 'fast': self.message = (self._('tsc_1') + status + self._('tsc_2') + time_one - + self._('fast_clock') + time_two)+ self._('cause') + + self._('fast_clock') + time_two) + self._('cause') return status def build_median(self): @@ -199,7 +200,7 @@ class Sdwdate: self.newdiff_nanoseconds = int(self.new_diff * 1000000000) - #print 'nanoseconds %s' % nanoseconds + # print 'nanoseconds %s' % nanoseconds message = 'Median time difference: %s' % self.median print(message) logger.info(message) @@ -243,12 +244,12 @@ class Sdwdate: ## Running sclockadj_debug_helper, in case... ## May be read the last line to ensure sclockadj is running. - #cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] + # cmd = ["sudo", "/usr/lib/sdwdate/sclockadj_debug_helper"] ### Pipe stdout in subprocess. - #helper = Popen(cmd, stdout=PIPE) + # helper = Popen(cmd, stdout=PIPE) ### Read the output. - #line = helper.stdout.read() - #print line + # line = helper.stdout.read() + # print line def kill_sclockadj(self): ''' @@ -394,8 +395,8 @@ class Sdwdate: pool.invalid_urls += 1 if pool.invalid_urls >= pool.allowed_failures: self.message = (self._('max_pool_failures') + - str(self.pools.index(pool) + 1) + - ' (' + str(pool.invalid_urls) + ')
      ') + str(self.pools.index(pool) + 1) + + ' (' + str(pool.invalid_urls) + ')
      ') return self.error_icon, 'error' old_unixtime = (time.time()) @@ -412,12 +413,12 @@ class Sdwdate: sanitycheck_status = self.time_sanity_check(web_unixtime) if sanitycheck_status == 'sane': web_time = (datetime.strftime(datetime.fromtimestamp(web_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) + '%a %b %d %H:%M:%S UTC %Y')) pool_diff = int(web_unixtime) - int(old_unixtime) self.pools_diff.append(pool_diff) pool_number = self.pools.index(pool) + 1 message = ('Pool %s last url: %s, web unixtime: %s, web time: %s, diff: %s seconds' - % (pool_number, url, web_unixtime, web_time, pool_diff)) + % (pool_number, url, web_unixtime, web_time, pool_diff)) print(message) logger.info(message) else: @@ -425,7 +426,7 @@ class Sdwdate: del self.valid_urls[index] self.invalid_urls.append(url) message = ('%s: time sanity check failed, server time is %s' - % (url, sanitycheck_status)) + % (url, sanitycheck_status)) print message logger.warning(message) @@ -445,7 +446,7 @@ class Sdwdate: end_unixtime = time.time() end_time = (datetime.strftime(datetime.fromtimestamp(start_unixtime), - '%a %b %d %H:%M:%S UTC %Y')) + '%a %b %d %H:%M:%S UTC %Y')) message = ('Fetching remote times, end %s (unixtime %s)' % (end_time, end_unixtime)) print(message) @@ -499,6 +500,7 @@ def prerequisite_check(): sdwdate.write_status(icon, message) time.sleep(10) + if __name__ == "__main__": ## When restarted from sdwdate-gui, allow sufficient time between ## status changes. See related comment in sdwdate-gui. From f8308b1854bfcb4bdf7992be2ecb474f5e7b0094 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sun, 18 Oct 2015 19:59:43 +0000 Subject: [PATCH 169/183] unused imports --- usr/lib/python2.7/dist-packages/sdwdate/remote_times.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py index 3e671099..9579fd4a 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/remote_times.py @@ -4,8 +4,6 @@ ## Copyright (C) 2015 Patrick Schleizer ## See the file COPYING for copying conditions. -import os -from subprocess import check_output import gevent from gevent.subprocess import Popen, PIPE From 236627f994938080e24995118cfe4572e47b0c72 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 20:43:57 +0000 Subject: [PATCH 170/183] config.py -> production/test modes --- .../python2.7/dist-packages/sdwdate/config.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/config.py b/usr/lib/python2.7/dist-packages/sdwdate/config.py index ea9bee04..ae027af9 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/config.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/config.py @@ -5,7 +5,7 @@ import re import random -def sort_pool(pool): +def sort_pool(pool, mode): ## Check number of multi-line pool. number_of_pool_multi = 0 for i in range(len(pool)): @@ -30,10 +30,16 @@ def sort_pool(pool): elif multi_line and pool[i].startswith('"'): url = re.search(r'"(.*)#', pool[i]) if url != None: - multi_list_url[multi_index].append(url.group(1)) + if mode == 'production': + multi_list_url[multi_index].append(url.group(1)) + elif mode == 'test': + pool_single_url.append(url.group(1)) comment = re.search(r'#(.*)"', pool[i]) if comment != None: - multi_list_comment[multi_index].append(comment.group(1)) + if mode == 'production': + multi_list_comment[multi_index].append(comment.group(1)) + elif mode == 'test': + pool_single_comment.append(comment.group(1)) elif pool[i] == '[': multi_line = True @@ -49,15 +55,16 @@ def sort_pool(pool): ## Pick a random url in each multi-line pool, ## append it to single url pool. for i in range(number_of_pool_multi): - single_ulr_index = random.sample(range(len(multi_list_url[i])), 1)[0] - single_url = multi_list_url[i][single_ulr_index] - single_comment = multi_list_comment[i][single_ulr_index] - pool_single_url.append(single_url) - pool_single_comment.append(single_comment) + if mode == 'production': + single_ulr_index = random.sample(range(len(multi_list_url[i])), 1)[0] + single_url = multi_list_url[i][single_ulr_index] + single_comment = multi_list_comment[i][single_ulr_index] + pool_single_url.append(single_url) + pool_single_comment.append(single_comment) return(pool_single_url, pool_single_comment) -def read_pools(pool): +def read_pools(pool, mode): SDWDATE_POOL_ONE = False SDWDATE_POOL_TWO = False SDWDATE_POOL_THREE = False @@ -111,9 +118,9 @@ def read_pools(pool): else: print('User configuration folder "/etc/sdwdate.d" does not exist.') - pool_one_url, pool_one_comment = sort_pool(pool_one) - pool_two_url , pool_two_comment = sort_pool(pool_two) - pool_three_url, pool_three_comment = sort_pool(pool_three) + pool_one_url, pool_one_comment = sort_pool(pool_one, mode) + pool_two_url , pool_two_comment = sort_pool(pool_two, mode) + pool_three_url, pool_three_comment = sort_pool(pool_three, mode) pool_url = [pool_one_url, pool_two_url, pool_three_url] pool_comment = [pool_one_comment, pool_two_comment, pool_three_comment] From d91192b01f46a9a07d0dd74735395e6b89824397 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 20:48:12 +0000 Subject: [PATCH 171/183] unused import --- usr/bin/sdwdate | 1 - 1 file changed, 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 21fa8b98..98313d12 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -8,7 +8,6 @@ import time from datetime import datetime import random from random import randint -import subprocess from subprocess import Popen, call, PIPE, check_output import pickle import re From 2fab852c3d5e84d75204a732b722ec48db6bdb25 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 20:50:03 +0000 Subject: [PATCH 172/183] sdwdate -> read pools in production mode --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 98313d12..a88882a9 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -36,7 +36,7 @@ class PreCheckVariables: class Pool: def __init__(self, pool): - self.url, self.comment = read_pools(pool) + self.url, self.comment = read_pools(pool, 'production') self.url_random = [] self.already_picked_index = [] allowed_failure_ratio = 0.34 From 9f86c29e103bd49a57e1cd5698a05f7006155ced Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 20:55:23 +0000 Subject: [PATCH 173/183] new file: usr/share/sdwdate/unit_test: get time from all sdwdate remotes, create a timestamped file (unixtime) with the results --- usr/share/sdwdate/unit_test | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 usr/share/sdwdate/unit_test diff --git a/usr/share/sdwdate/unit_test b/usr/share/sdwdate/unit_test new file mode 100644 index 00000000..4c73cba0 --- /dev/null +++ b/usr/share/sdwdate/unit_test @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +import time +from sdwdate.remote_times import get_time_from_servers +from sdwdate.config import read_pools +from sdwdate.proxy_settings import proxy_settings + + +class Pool: + def __init__(self, pool): + self.urls, self.comments = read_pools(pool, 'test') + + +class CheckRemotes: + def __init__(self): + self.number_of_pools = 3 + self.pools = [Pool(pool) for pool in range(self.number_of_pools)] + self.urls = [] + self.returned_values = [] + self.proxy_ip, self.proxy_port = proxy_settings() + + def loop(self): + time_stamp = str(int(time.time())) + file_name = 'sdwate_remotes_check-%s' % (time_stamp) + f = open(file_name, 'a') + print 'Starting remotes check...' + for pool in self.pools: + self.urls, self.returned_values = get_time_from_servers(pool.urls, + self.proxy_ip, + self.proxy_port) + for url in range(len(self.urls)): + msg = 'pool %s url %s: %s' % (self.pools.index(pool) + 1, self.urls[url], self.returned_values[url]) + print msg + f.write('%s\n' % msg) + f.close() + +if __name__ == '__main__': + remotes = CheckRemotes() + remotes.loop() + From 6e411d29135a4423933e48614f141df072363f01 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 21:01:49 +0000 Subject: [PATCH 174/183] make usr/share/sdwdate/unit_test executalbe --- usr/share/sdwdate/unit_test | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 usr/share/sdwdate/unit_test diff --git a/usr/share/sdwdate/unit_test b/usr/share/sdwdate/unit_test old mode 100644 new mode 100755 From 09b21f2dbb4bdf091943c5e38b7f777bd9173e40 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Tue, 20 Oct 2015 21:57:19 +0000 Subject: [PATCH 175/183] proxy_settings.py -> search for TOR_CONTROL_HOST variable --- usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py index cced1c23..13657778 100644 --- a/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py +++ b/usr/lib/python2.7/dist-packages/sdwdate/proxy_settings.py @@ -22,7 +22,7 @@ def proxy_settings(): if (os.path.exists('/usr/share/whonix') and os.access(settings_path, os.X_OK)): proxy_settings = check_output(settings_path) - ip_address = re.search(r'"(.*)"', proxy_settings).group(1) + ip_address = re.search(r'TOR_CONTROL_HOST="(.*)"', proxy_settings).group(1) elif ip_address != '': ## ip_address = PROXY_IP pass From 744ed92008eeb695900dd62e987759fb8185d167 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Thu, 22 Oct 2015 18:11:46 +0000 Subject: [PATCH 176/183] fix: end fetching time was same as start fetching time --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index a88882a9..54c57ed3 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -444,7 +444,7 @@ class Sdwdate: logger.info(message.strip()) end_unixtime = time.time() - end_time = (datetime.strftime(datetime.fromtimestamp(start_unixtime), + end_time = (datetime.strftime(datetime.fromtimestamp(end_unixtime), '%a %b %d %H:%M:%S UTC %Y')) message = ('Fetching remote times, end %s (unixtime %s)' % (end_time, end_unixtime)) From d2ae04588d5512ba7cea01cac30cf87e97b862d6 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 23 Oct 2015 07:21:06 +0000 Subject: [PATCH 177/183] sleep time -> use sh sleep in place of python's time.sleep(), because the latter uses system clock and get confused when sclokadj is running --- usr/bin/sdwdate | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 54c57ed3..fe3ed1cc 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -561,6 +561,12 @@ if __name__ == "__main__": print(stripped_message) - time.sleep(sleep_time * 60) + seconds_to_sleep = sleep_time * 60 + ## Using sh sleep in place of python's time.sleep(). + ## The latter uses the system clock for its inactive state time. + ## It becomes utterly confused when sclockadj is running. + cmd = 'sleep %s' % seconds_to_sleep + call(cmd, shell=True) + #time.sleep(seconds_to_sleep) if sdwdate.sclockadj_pid != 0: sdwdate.kill_sclockadj() From 112d21e4c1d906b93816aa18d2e9cb121089100b Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 23 Oct 2015 16:03:52 +0000 Subject: [PATCH 178/183] logging proxy settings --- usr/bin/sdwdate | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index fe3ed1cc..51bb5beb 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -519,6 +519,11 @@ if __name__ == "__main__": pid_message = 'sdwdate started. PID %s' % self_pid print pid_message logger.info(pid_message) + sdwdate = Sdwdate() + proxy_message = ('Tor control host: %s Tor control port: %s' + % (sdwdate.proxy_ip, sdwdate.proxy_port)) + print proxy_message + logger.info(proxy_message) if pre_check.run_prerequisite: prerequisite_check() From d442166b36618b86406b6ca182c8d67fb0a56a3a Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 23 Oct 2015 18:38:15 +0000 Subject: [PATCH 179/183] expanded max_pool_failures message --- usr/bin/sdwdate | 8 +++++--- usr/share/translations/sdwdate.yaml | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 51bb5beb..46d3d775 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -393,9 +393,11 @@ class Sdwdate: if self.urls[i] in pool.url_random: pool.invalid_urls += 1 if pool.invalid_urls >= pool.allowed_failures: - self.message = (self._('max_pool_failures') + + self.message = (self._('max_pool_failures_1') + str(self.pools.index(pool) + 1) + - ' (' + str(pool.invalid_urls) + ')
      ') + ' (' + str(pool.invalid_urls) + ' of ' + + str(len(pool.url)) + ')' + + self._('max_pool_failures_2') + '
      ') return self.error_icon, 'error' old_unixtime = (time.time()) @@ -546,7 +548,7 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - sleep_time = randint(60, 180) + sleep_time = 0.2 #randint(60, 180) log_level = 'info' elif status == 'error': diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index 5c43022e..7abe25a3 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -28,4 +28,6 @@ sdwdate: general_proxy_error: 'General Proxy Error. Is Tor running?' general_timeout_error: 'General Timeout Error. Internet connection might be down' - max_pool_failures: 'Maximum number of allowed failures reached in pool ' + max_pool_failures_1: 'Maximum number of allowed failures reached in pool ' + max_pool_failures_2: '. Giving up.
      If the problem occurs too frequently, please report it.
      + You may increase MAX_FAILURE_RATIO in /etc/sdwdate.d/30_default.conf.' From 8c028f1d6dc8e5baed1a9c355294cf991f8ff025 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 23 Oct 2015 20:58:02 +0000 Subject: [PATCH 180/183] MAX_FAILURE_RATIO in /etc/sdwdate.d/30_default.conf --- etc/sdwdate.d/30_default.conf | 10 ++++++++-- usr/bin/sdwdate | 23 ++++++++++++++++++++--- usr/share/translations/sdwdate.yaml | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/etc/sdwdate.d/30_default.conf b/etc/sdwdate.d/30_default.conf index 1744e156..a01aed3e 100644 --- a/etc/sdwdate.d/30_default.conf +++ b/etc/sdwdate.d/30_default.conf @@ -2,14 +2,20 @@ ## Copyright (C) 2012 - 2014 Patrick Schleizer ## See the file COPYING for copying conditions. -## Please use "/etc/sdwdate.d/50_user" for your custom +## Please use "/etc/sdwdate.d/50_user.conf" for your custom ## configuration, which will override the defaults found here. ## When sdwdate is updated, this file may be overwritten. ## Proxy settings for non anonymous distributions. ## Uncomment for standard tor configuration (no stream isolation). #PROXY_IP=127.0.0.1 -#PROXY_PORT=9050 +#PROXY_PORT=9053 + +## Allowed percentage of url failures common to every pool. +## If sdwdate frequently stops with "Maximum allowed number of failures" error, +## create a file "/etc/sdwdate.d/50_user.conf" overriding MAX_FAILURE_RATIO +## with a higher figure. +MAX_FAILURE_RATIO=0.34 ## pool syntax ## "url.onion[:port]#comment" diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 46d3d775..53096858 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -4,6 +4,7 @@ import sys import logging import signal import os +import glob import time from datetime import datetime import random @@ -39,15 +40,29 @@ class Pool: self.url, self.comment = read_pools(pool, 'production') self.url_random = [] self.already_picked_index = [] - allowed_failure_ratio = 0.34 self.invalid_urls = 0 - self.allowed_failures = int(len(self.url) * allowed_failure_ratio) self.done = False @property def url_range(self): return len(self.url) + @property + def allowed_failures(self): + if os.path.exists('/etc/sdwdate.d/'): + files = sorted(glob.glob('/etc/sdwdate.d/*')) + for f in files: + with open(f) as conf: + lines = conf.readlines() + for line in lines: + if line.startswith('MAX_FAILURE_RATIO'): + failure_ratio = re.search(r'=(.*)', line).group(1) + return int(len(self.url) * float(failure_ratio)) + else: + return int(len(self.url) * 0.34) + return int(len(self.url) * 0.34) + + class Sdwdate: def __init__(self): @@ -55,6 +70,8 @@ class Sdwdate: self.number_of_pools = 3 self.pools = [Pool(pool) for pool in range(self.number_of_pools)] + ## Could get it here to prevent reading the file in sdwdate_loop for each Unreachable url. + #self.allowed_failures = [Pool(pool).allowed_failures for pool in range(self.number_of_pools)] self.urls = [] self.url_random = [] self.valid_urls = [] @@ -548,7 +565,7 @@ if __name__ == "__main__": f = open(sdwdate.success_path, 'w') f.close() - sleep_time = 0.2 #randint(60, 180) + sleep_time = randint(60, 180) log_level = 'info' elif status == 'error': diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index 7abe25a3..43ec0fad 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -28,6 +28,6 @@ sdwdate: general_proxy_error: 'General Proxy Error. Is Tor running?' general_timeout_error: 'General Timeout Error. Internet connection might be down' - max_pool_failures_1: 'Maximum number of allowed failures reached in pool ' + max_pool_failures_1: 'Maximum allowed number of failures reached in pool ' max_pool_failures_2: '. Giving up.
      If the problem occurs too frequently, please report it.
      - You may increase MAX_FAILURE_RATIO in /etc/sdwdate.d/30_default.conf.' + You may increase MAX_FAILURE_RATIO (see /etc/sdwdate.d/30_default.conf).' From 3882c4c330852c8b5501ffd01d4b3c32761f1f6c Mon Sep 17 00:00:00 2001 From: troubadoour Date: Fri, 23 Oct 2015 21:02:20 +0000 Subject: [PATCH 181/183] typo in PROXY_PORT --- etc/sdwdate.d/30_default.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/sdwdate.d/30_default.conf b/etc/sdwdate.d/30_default.conf index a01aed3e..81413411 100644 --- a/etc/sdwdate.d/30_default.conf +++ b/etc/sdwdate.d/30_default.conf @@ -9,7 +9,7 @@ ## Proxy settings for non anonymous distributions. ## Uncomment for standard tor configuration (no stream isolation). #PROXY_IP=127.0.0.1 -#PROXY_PORT=9053 +#PROXY_PORT=9050 ## Allowed percentage of url failures common to every pool. ## If sdwdate frequently stops with "Maximum allowed number of failures" error, From 0941fa436e121dd74d4401c2e85bc0e6008bbccf Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 24 Oct 2015 15:38:40 +0000 Subject: [PATCH 182/183] Tor control -> Tor socks --- usr/bin/sdwdate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usr/bin/sdwdate b/usr/bin/sdwdate index 53096858..9d49b341 100755 --- a/usr/bin/sdwdate +++ b/usr/bin/sdwdate @@ -539,7 +539,7 @@ if __name__ == "__main__": print pid_message logger.info(pid_message) sdwdate = Sdwdate() - proxy_message = ('Tor control host: %s Tor control port: %s' + proxy_message = ('Tor socks host: %s Tor socks port: %s' % (sdwdate.proxy_ip, sdwdate.proxy_port)) print proxy_message logger.info(proxy_message) From fa1d3178d1a71f0e1a01c558f187765fb0e7ace7 Mon Sep 17 00:00:00 2001 From: troubadoour Date: Sat, 24 Oct 2015 16:32:55 +0000 Subject: [PATCH 183/183] remove "You may increase MAX_FAILURE_RATIO" in max_pool_failures message --- usr/share/translations/sdwdate.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usr/share/translations/sdwdate.yaml b/usr/share/translations/sdwdate.yaml index 43ec0fad..f219018f 100644 --- a/usr/share/translations/sdwdate.yaml +++ b/usr/share/translations/sdwdate.yaml @@ -29,5 +29,4 @@ sdwdate: general_timeout_error: 'General Timeout Error. Internet connection might be down' max_pool_failures_1: 'Maximum allowed number of failures reached in pool ' - max_pool_failures_2: '. Giving up.
      If the problem occurs too frequently, please report it.
      - You may increase MAX_FAILURE_RATIO (see /etc/sdwdate.d/30_default.conf).' + max_pool_failures_2: '. Giving up.
      If the problem occurs too frequently, please report it.'