blacklist.py 5.07 KB
Newer Older
1
2
# -*- coding: utf-8 -*-
#
3
# This file is part of GetTor, a Tor Browser distribution system.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#
# :authors: Israel Leiva <ilv@riseup.net>
#           see also AUTHORS file
#
# :copyright:   (c) 2008-2014, The Tor Project, Inc.
#               (c) 2014, Israel Leiva
#
# :license: This is Free Software. See LICENSE for license information.

import os
import time
import logging
import sqlite3
import datetime
import ConfigParser

import db
import utils

"""Blacklist module for managing blacklisting of users."""


class BlacklistError(Exception):
    pass


ilv's avatar
ilv committed
30
31
32
33
34
35
36
37
class ConfigError(Exception):
    pass


class InternalError(Exception):
    pass


38
39
40
41
42
43
44
45
46
47
48
class Blacklist(object):
    """Manage blacklisting of users.

    Public methods:

        is_blacklisted(): Check if someone is blacklisted.

    Exceptions:

         ConfigurationError: Bad configuration.
         BlacklistError: User is blacklisted.
ilv's avatar
ilv committed
49
         InternalError: Something went wrong internally.
50
51
52
53
54
55
56
57
58

    """

    def __init__(self, cfg=None):
    	"""Create new object by reading a configuration file.

        :param: cfg (string) path of the configuration file.

        """
ilv's avatar
ilv committed
59
        default_cfg = 'blacklist.cfg'
60
61
62
        config = ConfigParser.ConfigParser()

        if cfg is None or not os.path.isfile(cfg):
ilv's avatar
ilv committed
63
            cfg = default_cfg
64
65

        try:
ilv's avatar
ilv committed
66
67
68
69
            with open(cfg) as f:
                config.readfp(f)
        except IOError:
            raise ConfigError("File %s not found!" % cfg)
70
71

        try:
ilv's avatar
ilv committed
72
            dbname = config.get('general', 'db')
73
74
75
            logdir = config.get('log', 'dir')
            logfile = os.path.join(logdir, 'blacklist.log')
            loglevel = config.get('log', 'level')
ilv's avatar
ilv committed
76
77
            self.db = db.DB(dbname)

78
        except ConfigParser.Error as e:
ilv's avatar
ilv committed
79
80
81
            raise ConfigError("%s" % e)
        except db.Exception as e:
            raise ConfigError("%s" % e)
82

ilv's avatar
ilv committed
83
84
85
86
87
88
89
90
        # logging
        log = logging.getLogger(__name__)

        logging_format = utils.get_logging_format()
        date_format = utils.get_date_format()
        formatter = logging.Formatter(logging_format, date_format)

        log.info('Redirecting BLACKLIST logging to %s' % logfile)
91
        logfileh = logging.FileHandler(logfile, mode='a+')
ilv's avatar
ilv committed
92
        logfileh.setFormatter(formatter)
93
94
        logfileh.setLevel(logging.getLevelName(loglevel))
        log.addHandler(logfileh)
95
96
97

        # stop logging on stdout from now on
        log.propagate = False
ilv's avatar
ilv committed
98
        self.log = log
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

    def is_blacklisted(self, user, service, max_req, wait_time):
        """Check if a user is blacklisted.

        The user is blacklisted if:

        a) The 'blocked' field is set to one, meaning that is permanently
        blacklisted.

        b) Does too many requests on a short period of time. For now, a user
        that makes more than 'max_req' requests should wait 'wait_time'
        minutes to make a new request.

        :param: user (string) the hashed user.
        :param: service (string) the service the user is making a request to.
        :param: max_req (int) maximum number of requests a user can make
                in a row.
        :param: wait_time (int) amount of time the user must wait before
                making requests again after 'max_req' requests is reached.
                For now this is considered in minutes.

        :raise: BlacklistError if the user is blacklisted

        """
ilv's avatar
ilv committed
123
124
125
126
127
128
129
130
        try:
            self.log.info("Trying to get info from user")
            self.db.connect()
            r = self.db.get_user(user, service)
            if r:
                # permanently blacklisted
                if r['blocked']:
                    self.log.warning("Request from user permanently blocked")
ilv's avatar
ilv committed
131
132
                    self.db.update_user(user, service, r['times']+1, 1)
                    raise BlacklistError("Blocked user")
ilv's avatar
ilv committed
133
134
135
136
137
138
139
140
141
                # don't be greedy
                elif r['times'] >= max_req:
                    last = datetime.datetime.fromtimestamp(
                        float(r['last_request'])
                    )
                    next = last + datetime.timedelta(minutes=wait_time)

                    if datetime.datetime.now() < next:
                        self.log.warning("Too many requests from same user")
ilv's avatar
ilv committed
142
143
                        self.db.update_user(user, service, r['times']+1, 0)
                        raise BlacklistError("Too many requests")
ilv's avatar
ilv committed
144
145
146
                    else:
                        # fresh user again!
                        self.log.info("Updating counter for existing user")
ilv's avatar
ilv committed
147
                        self.db.update_user(user, service, 1, 0)
ilv's avatar
ilv committed
148
149
150
                else:
                    # adding up a request for user
                    self.log.info("Request from existing user")
ilv's avatar
ilv committed
151
                    self.db.update_user(user, service, r['times']+1, 0)
ilv's avatar
ilv committed
152
153
154
            else:
                # new request for user
                self.log.info("Request from new user")
ilv's avatar
ilv committed
155
                self.db.add_user(user, service, 0)
ilv's avatar
ilv committed
156
157
158
159
160
        except db.DBError as e:
            self.log.error("Something failed!")
            raise InternalError("Error with database (%s)" % str(e))
        except BlacklistError as e:
            raise BlacklistError(e)