diff --git a/CHANGES b/CHANGES index daef528..b32a02b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,8 @@ -October 17th, 2013 +October 30th, 2013 +v1.11 -- Improved the way session files are garbage-collected. This + closes a potential DoS-attack vector. +October 17th, 2013 v1.10 -- Fixed a serious security problem in the WAP-application. -- Disabled "django.middleware.gzip.GZipMiddleware" because of diff --git a/INSTALL b/INSTALL index 1a26fcd..78daaed 100644 --- a/INSTALL +++ b/INSTALL @@ -29,11 +29,11 @@ source code in your */usr/local/share/* directory:: # cd /usr/local/share/ # wget http://sourceforge.net/projects/cmb/files/tarballs/\ - cmbarter-1.10.tar.gz/download -O cmbarter-1.10.tar.gz + cmbarter-1.11.tar.gz/download -O cmbarter-1.11.tar.gz ... - # tar -xzf cmbarter-1.10.tar.gz - # mv cmbarter-1.10 cmbarter + # tar -xzf cmbarter-1.11.tar.gz + # mv cmbarter-1.11 cmbarter Also, make sure a *Python 2.x* interpreter (version 2.6 at least) is installed on your server. @@ -253,8 +253,8 @@ Here are the configuration steps that you should perform: 0,10,20,30,40,50 * * * * sudo -u cmbarter python /usr/local/share... /cmbarter/execute_turn.py - 0,10,20,30,40,50 * * * * sudo -u cmbarter python /usr/local/share... - /cmbarter/check_sessions.py + * * * * * sudo -u cmbarter python /usr/local/share/cmbarter/check... + _sessions.py --repeat-interval=5 * * * * * sudo -u cmbarter python /usr/local/share/cmbarter/proce... ss_emails.py @@ -331,11 +331,11 @@ Here are the installation steps that you should perform: $ cd ~ $ wget http://sourceforge.net/projects/cmb/files/tarballs/\ - cmbarter-1.10.tar.gz/download -O cmbarter-1.10.tar.gz + cmbarter-1.11.tar.gz/download -O cmbarter-1.11.tar.gz ... - $ tar -xzf cmbarter-1.10.tar.gz - $ mv cmbarter-1.10 cmbarter + $ tar -xzf cmbarter-1.11.tar.gz + $ mv cmbarter-1.11 cmbarter 3. Restrict access to those source files that may contain sensitive information:: @@ -434,7 +434,7 @@ Here are the configuration steps that you should perform: 0,10,20,30,40,50 * * * * python ~/cmbarter/execute_turn.py - 0,10,20,30,40,50 * * * * python ~/cmbarter/check_sessions.py + * * * * * python ~/cmbarter/check_sessions.py * * * * * python ~/cmbarter/process_emails.py --smtp='mailserve... rname' --smtp-username='noreply@yourdomainname.foo' --smtp-pa... diff --git a/check_sessions.py b/check_sessions.py index 5bd9a25..3da68e8 100755 --- a/check_sessions.py +++ b/check_sessions.py @@ -28,16 +28,31 @@ ## This file implements the garbage collection of stale sessions. ## import sys, os, os.path, getopt, time -from cmbarter.settings import CMBARTER_SESSION_DIR +from fcntl import lockf, LOCK_EX, LOCK_NB +from cmbarter.settings import CMBARTER_SESSION_DIR, CMBARTER_PROJECT_DIR USAGE = """Usage: check_sessions.py [OPTIONS] Delete old session files. +Will exit immediately if another copy of the program is already +running. In this case, the process ID of the currently running process +can be found in the ./cmbarter/check_sessions.lock file. + -h, --help display this help and exit --dir=DIRECTORY give the session directory explicitly --prefix=PREFIX specify the session file prefix (default: 'sessionid') - --maxage=SECONDS specify the maximum session age (default: 3600 seconds) + --maxage=SECONDS specify the maximum session age (default: 2400 seconds) + --maxfiles=NUMBER specify the maximum number of files in a directory + that the OS can handle without serious performance + degradation (default: 50000 files) + + --repeat-interval=N if N=0 (the default), the program will exit after one + check is done; + + if N>0, the program will continue to execute checks + "ad infinitum" with idle intervals of N seconds between + the checks. Example: $ ./check_sessions.py --maxage=10000 @@ -45,9 +60,10 @@ def parse_args(argv): - global prefix, directory, duration + global prefix, directory, duration, maxfiles, repeat_interval try: - opts, args = getopt.gnu_getopt(argv, 'h', ['prefix=', 'dir=', 'maxage=', 'help']) + opts, args = getopt.gnu_getopt(argv, 'h', ['prefix=', 'dir=', 'maxage=', + 'maxfiles=', 'repeat-interval=', 'help']) except getopt.GetoptError: print(USAGE) sys.exit(2) @@ -64,27 +80,66 @@ def parse_args(argv): prefix = arg elif opt == '--dir': directory = arg + elif opt == '--maxfiles': + try: + maxfiles = int(arg) + except ValueError: + print(USAGE) + sys.exit(2) + elif opt == '--repeat-interval': + try: + repeat_interval = int(arg) + except ValueError: + print(USAGE) + sys.exit(2) elif opt == '--maxage': try: duration = float(arg) except ValueError: print(USAGE) - sys.exit() + sys.exit(2) + + +def examine_sessions(): + while True: + current_time = time.time() + fname_list = os.listdir(directory) + aggressive = len(fname_list) > maxfiles + for fname in fname_list: + if fname.startswith(prefix): + fabsname = os.path.join(directory, fname) + try: + if ( (aggressive and os.path.getsize(fabsname) < 1000) or + (current_time - os.path.getmtime(fabsname) > duration) ): + os.unlink(fabsname) + except OSError: + pass + if repeat_interval == 0: + break + else: + time.sleep(repeat_interval) if __name__ == "__main__": prefix = 'sessionid' directory = CMBARTER_SESSION_DIR - duration = 3600.0 + duration = 2400.0 + maxfiles = 50000 + repeat_interval = 0 parse_args(sys.argv[1:]) - currtime = time.time() - for fname in os.listdir(directory): - if fname.startswith(prefix): - fabsname = os.path.join(directory, fname) - try: - mtime = os.path.getmtime(fabsname) - if currtime - mtime > duration: - os.unlink(fabsname) - except OSError: - sys.exit() + # try to obtain exclusive lock on ./cmbarter/check_sessions.lock + f = open(os.path.join(CMBARTER_PROJECT_DIR, 'check_sessions.lock'), 'r+') + try: + lockf(f, LOCK_EX | LOCK_NB) + except IOError: + if repeat_interval == 0: + print '%s: another copy of the program is already running' % __file__ + sys.exit(2) + else: + sys.exit() # no error message, otherwise 'cron' would email it + else: + f.write('%i\n' % os.getpid()) + f.truncate() + + examine_sessions() diff --git a/cmbarter/check_sessions.lock b/cmbarter/check_sessions.lock new file mode 100644 index 0000000..e69de29 diff --git a/cmbarter/locale/bg/LC_MESSAGES/django.po b/cmbarter/locale/bg/LC_MESSAGES/django.po index fec8ce9..ffa8705 100644 --- a/cmbarter/locale/bg/LC_MESSAGES/django.po +++ b/cmbarter/locale/bg/LC_MESSAGES/django.po @@ -6,9 +6,9 @@ # msgid "" msgstr "" -"Project-Id-Version: cmbarter-1.10\n" +"Project-Id-Version: cmbarter-1.11\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-10-13 09:26+0300\n" +"POT-Creation-Date: 2013-10-30 15:59+0200\n" "PO-Revision-Date: 2013-09-28 01:32+0300\n" "Last-Translator: Evgeni Pandurski \n" "Language-Team: Bulgarian \n" @@ -373,7 +373,7 @@ msgstr "Тема" msgid "Write your message here" msgstr "Напишете тук вашето съобщение" -#: profiles/views.py:168 users/views.py:375 +#: profiles/views.py:168 users/views.py:377 msgid "Select your time zone" msgstr "Изберете вашата часова зона" diff --git a/cmbarter/settings.py b/cmbarter/settings.py index 9eb935d..33c2205 100644 --- a/cmbarter/settings.py +++ b/cmbarter/settings.py @@ -81,7 +81,7 @@ CMBARTER_INSERT_BIDI_MARKS = False CMBARTER_HOST_IS_SPAM_LISTED = False CMBARTER_HISTORY_HORISON_DAYS = 26 -CMBARTER_SESSION_TOUCH_MINUTES = 30 +CMBARTER_SESSION_TOUCH_MINUTES = 10 CMBARTER_PRICE_PREFIXES = set([u'', u'$', u'\u00A3', u'\u20AC']) CMBARTER_PRICE_SUFFIXES = set([u'', u'\u20AC', u'ЛВ', u'ЛВ.']) CMBARTER_TRX_COST_QUOTA = 50000.0 diff --git a/cmbarter/users/views.py b/cmbarter/users/views.py index b1b251f..50ef9c3 100644 --- a/cmbarter/users/views.py +++ b/cmbarter/users/views.py @@ -50,7 +50,7 @@ TRADER_ID_STRING = re.compile(r'^[0-9]{1,9}$') SSI_HOST = re.compile(br'') - +GARBAGE = 1000 * ' ' search_limiter = limiter.Limiter( os.path.join(settings.CMBARTER_SESSION_DIR, "cmbarter_search_limiter"), @@ -97,6 +97,7 @@ def login(request, tmpl='login.html'): elif authentication['is_valid']: # Log the user in and redirect him to his start-page. trader_id = request.session['trader_id'] = authentication['trader_id'] + request.session['garbage'] = GARBAGE # we tell "real" sessions by the size if settings.CMBARTER_MAINTAIN_IP_WHITELIST: client_ip = get_client_ip(request) if client_ip: @@ -143,6 +144,7 @@ def login_captcha(request, tmpl='login_captcha.html'): del request.session['auth_trader_id'] db.report_login_captcha_success(trader_id) request.session['trader_id'] = trader_id + request.session['garbage'] = GARBAGE if settings.CMBARTER_MAINTAIN_IP_WHITELIST: client_ip = get_client_ip(request) if client_ip: diff --git a/doc/cmb-install.pdf b/doc/cmb-install.pdf index 2fd2651..2781716 100644 Binary files a/doc/cmb-install.pdf and b/doc/cmb-install.pdf differ diff --git a/execute_turn.py b/execute_turn.py index 8cc3b88..f8433b2 100755 --- a/execute_turn.py +++ b/execute_turn.py @@ -156,7 +156,7 @@ def parse_args(argv): level = max(int(arg), 0) except ValueError: print(USAGE) - sys.exit() + sys.exit(2) elif opt in ('-n', '--no-cluster'): no_cluster = True diff --git a/generate_regkeys.py b/generate_regkeys.py index 0bfc378..1102b10 100755 --- a/generate_regkeys.py +++ b/generate_regkeys.py @@ -64,7 +64,7 @@ def srt2int(s): raise ValueError except ValueError: print(USAGE) - sys.exit() + sys.exit(2) else: return i diff --git a/schedule_turns.py b/schedule_turns.py index 537cbf5..9d67d02 100755 --- a/schedule_turns.py +++ b/schedule_turns.py @@ -86,7 +86,7 @@ def parse_args(argv): raise ValueError except ValueError: print(USAGE) - sys.exit() + sys.exit(2) elif opt == '--dsn': dsn = arg