We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Implement regular expressions in userdb.txt
The file that contains the combinations of usernames and passwords that Cowrie accepts from the attackers (`data/userdb.txt`) currently handles 3 special characters - `#`, which means a comment till the end of the line, `!`, which means negation, and `*`, which means "anything" (in either the username or the password field).

Would it be possible to allow any regular expression instead of the special characters '!' and '*'?

I've seen attackers use variations of the password "honeypot" to determine that they are dealing with a honeypot and refuse to conduct their usual attack. Examples include "Honeypot321" (309 times), "honeypot" (6 times), and "nologinissahoneypotlmao" (once) over a 17-month period.

I could, of course, explicitly block just these 3 passwords, but I'd like to disallow any password with the word "honeypot" (case-insensitive) in it.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cowrie/core/auth.py`
Content:
```
1 # Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
2 # See the COPYRIGHT file for more information
3 
4 """
5 This module contains ...
6 """
7 
8 from __future__ import division, absolute_import
9 
10 import json
11 from os import path
12 from random import randint
13 
14 from twisted.python import log
15 
16 from cowrie.core.config import CONFIG
17 
18 class UserDB(object):
19     """
20     By Walter de Jong <walter@sara.nl>
21     """
22 
23     def __init__(self):
24         self.userdb = []
25         self.userdb_file = '%s/userdb.txt' % CONFIG.get('honeypot', 'data_path')
26         self.load()
27 
28 
29     def load(self):
30         """
31         load the user db
32         """
33 
34         with open(self.userdb_file, 'rb') as f:
35             while True:
36                 rawline = f.readline()
37                 if not rawline:
38                     break
39 
40                 line = rawline.strip()
41                 if not line:
42                     continue
43 
44                 if line.startswith(b'#'):
45                     continue
46 
47                 (login, uid, passwd) = line.split(b':', 2)
48 
49                 self.userdb.append((login, passwd))
50 
51 
52     def save(self):
53         """
54         save the user db
55         """
56 
57         # Note: this is subject to races between cowrie instances, but hey ...
58         with open(self.userdb_file, 'w') as f:
59             for (login, passwd) in self.userdb:
60                 f.write('%s:x:%s\n' % (login, passwd))
61 
62 
63     def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):
64         """
65         check entered username/password against database
66         note that it allows multiple passwords for a single username
67         it also knows wildcard '*' for any username or password
68         prepend password with ! to explicitly deny it. Denials must come before wildcards
69         """
70         for (login, passwd) in self.userdb:
71             # Explicitly fail on !password
72             if login == thelogin and passwd == b'!' + thepasswd:
73                 return False
74             if login in (thelogin, b'*') and passwd in (thepasswd, b'*'):
75                 return True
76         return False
77 
78 
79     def user_password_exists(self, thelogin, thepasswd):
80         """
81         """
82         for (login, passwd) in self.userdb:
83             if login == thelogin and passwd == thepasswd:
84                 return True
85         return False
86 
87 
88     def adduser(self, login, passwd):
89         """
90         """
91         if self.user_password_exists(login, passwd):
92             return
93         self.userdb.append((login, passwd))
94         self.save()
95 
96 
97 
98 class AuthRandom(object):
99     """
100     Alternative class that defines the checklogin() method.
101     Users will be authenticated after a random number of attempts.
102     """
103 
104     def __init__(self):
105         # Default values
106         self.mintry, self.maxtry, self.maxcache = 2, 5, 10
107 
108         # Are there auth_class parameters?
109         if CONFIG.has_option('honeypot', 'auth_class_parameters'):
110             parameters = CONFIG.get('honeypot', 'auth_class_parameters')
111             parlist = parameters.split(',')
112             if len(parlist) == 3:
113                 self.mintry = int(parlist[0])
114                 self.maxtry = int(parlist[1])
115                 self.maxcache = int(parlist[2])
116 
117         if self.maxtry < self.mintry:
118             self.maxtry = self.mintry + 1
119             log.msg('maxtry < mintry, adjusting maxtry to: %d' % (self.maxtry,))
120         self.uservar = {}
121         self.uservar_file = '%s/uservar.json' % CONFIG.get('honeypot', 'data_path')
122         self.loadvars()
123 
124 
125     def loadvars(self):
126         """
127         Load user vars from json file
128         """
129         if path.isfile(self.uservar_file):
130             with open(self.uservar_file, 'rb') as fp:
131                 try:
132                     self.uservar = json.load(fp)
133                 except:
134                     self.uservar = {}
135 
136 
137     def savevars(self):
138         """
139         Save the user vars to json file
140         """
141         data = self.uservar
142         # Note: this is subject to races between cowrie logins
143         with open(self.uservar_file, 'wb') as fp:
144             json.dump(data, fp)
145 
146 
147     def checklogin(self, thelogin, thepasswd, src_ip):
148         """
149         Every new source IP will have to try a random number of times between
150         'mintry' and 'maxtry' before succeeding to login.
151         All username/password combinations  must be different.
152         The successful login combination is stored with the IP address.
153         Successful username/passwords pairs are also cached for 'maxcache' times.
154         This is to allow access for returns from different IP addresses.
155         Variables are saved in 'uservar.json' in the data directory.
156         """
157 
158         auth = False
159         userpass = thelogin + ':' + thepasswd
160 
161         if not 'cache' in self.uservar:
162             self.uservar['cache'] = []
163         cache = self.uservar['cache']
164 
165         # Check if it is the first visit from src_ip
166         if src_ip not in self.uservar:
167             self.uservar[src_ip] = {}
168             ipinfo = self.uservar[src_ip]
169             ipinfo['try'] = 0
170             if userpass in cache:
171                 log.msg('first time for %s, found cached: %s' % (src_ip, userpass))
172                 ipinfo['max'] = 1
173                 ipinfo['user'] = thelogin
174                 ipinfo['pw'] = thepasswd
175                 auth = True
176                 self.savevars()
177                 return auth
178             else:
179                 ipinfo['max'] = randint(self.mintry, self.maxtry)
180                 log.msg('first time for %s, need: %d' % (src_ip, ipinfo['max']))
181 
182         ipinfo = self.uservar[src_ip]
183 
184         # Fill in missing variables
185         if not 'max' in ipinfo:
186             ipinfo['max'] = randint(self.mintry, self.maxtry)
187         if not 'try' in ipinfo:
188             ipinfo['try'] = 0
189         if not 'tried' in ipinfo:
190             ipinfo['tried'] = []
191 
192         # Don't count repeated username/password combinations
193         if userpass in ipinfo['tried']:
194             log.msg('already tried this combination')
195             self.savevars()
196             return auth
197 
198         ipinfo['try'] += 1
199         attempts = ipinfo['try']
200         need = ipinfo['max']
201         log.msg('login attempt: %d' % (attempts,))
202 
203         # Check if enough login attempts are tried
204         if attempts < need:
205             self.uservar[src_ip]['tried'].append(userpass)
206         elif attempts == need:
207             ipinfo['user'] = thelogin
208             ipinfo['pw'] = thepasswd
209             cache.append(userpass)
210             if len(cache) > self.maxcache:
211                 cache.pop(0)
212             auth = True
213         # Returning after successful login
214         elif attempts > need:
215             if not 'user' in ipinfo or not 'pw' in ipinfo:
216                 log.msg('return, but username or password not set!!!')
217                 ipinfo['tried'].append(userpass)
218                 ipinfo['try'] = 1
219             else:
220                 log.msg('login return, expect: [%s/%s]' % (ipinfo['user'], ipinfo['pw']))
221                 if thelogin == ipinfo['user'] and thepasswd == ipinfo['pw']:
222                     auth = True
223         self.savevars()
224         return auth
225 
226
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/cowrie/core/auth.py b/cowrie/core/auth.py
--- a/cowrie/core/auth.py
+++ b/cowrie/core/auth.py
@@ -7,6 +7,7 @@
 
 from __future__ import division, absolute_import
 
+import re
 import json
 from os import path
 from random import randint
@@ -21,7 +22,7 @@
     """
 
     def __init__(self):
-        self.userdb = []
+        self.userdb = {}
         self.userdb_file = '%s/userdb.txt' % CONFIG.get('honeypot', 'data_path')
         self.load()
 
@@ -44,55 +45,50 @@
                 if line.startswith(b'#'):
                     continue
 
-                (login, uid, passwd) = line.split(b':', 2)
+                login, passwd = re.split(br':\w+:', line, 1)
+                self.adduser(login, passwd)
 
-                self.userdb.append((login, passwd))
 
+    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):
+        for credentials, policy in self.userdb.items():
+            login, passwd = credentials
 
-    def save(self):
-        """
-        save the user db
-        """
+            if self.match_rule(login, thelogin):
+                if self.match_rule(passwd, thepasswd):
+                    return policy
 
-        # Note: this is subject to races between cowrie instances, but hey ...
-        with open(self.userdb_file, 'w') as f:
-            for (login, passwd) in self.userdb:
-                f.write('%s:x:%s\n' % (login, passwd))
+        return False
 
 
-    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):
-        """
-        check entered username/password against database
-        note that it allows multiple passwords for a single username
-        it also knows wildcard '*' for any username or password
-        prepend password with ! to explicitly deny it. Denials must come before wildcards
-        """
-        for (login, passwd) in self.userdb:
-            # Explicitly fail on !password
-            if login == thelogin and passwd == b'!' + thepasswd:
-                return False
-            if login in (thelogin, b'*') and passwd in (thepasswd, b'*'):
-                return True
-        return False
+    def match_rule(self, rule, input):
+        if type(rule) is bytes:
+            return rule in [b'*', input]
+        else:
+            return bool(rule.search(input))
 
 
-    def user_password_exists(self, thelogin, thepasswd):
+    def re_or_str(self, rule):
         """
+        Convert a /.../ type rule to a regex, otherwise return the string as-is
         """
-        for (login, passwd) in self.userdb:
-            if login == thelogin and passwd == thepasswd:
-                return True
-        return False
+        res = re.match(br'/(.+)/(i)?$', rule)
+        if res:
+            return re.compile(res.group(1), re.IGNORECASE if res.group(2) else 0)
+
+        return rule
 
 
     def adduser(self, login, passwd):
-        """
-        """
-        if self.user_password_exists(login, passwd):
-            return
-        self.userdb.append((login, passwd))
-        self.save()
+        login = self.re_or_str(login)
+
+        if passwd.startswith(b'!'):
+            policy = False
+            passwd = passwd[1:]
+        else:
+            policy = True
 
+        passwd = self.re_or_str(passwd)
+        self.userdb[(login, passwd)] = policy
 
 
 class AuthRandom(object):

{"golden_diff": "diff --git a/cowrie/core/auth.py b/cowrie/core/auth.py\n--- a/cowrie/core/auth.py\n+++ b/cowrie/core/auth.py\n@@ -7,6 +7,7 @@\n \n from __future__ import division, absolute_import\n \n+import re\n import json\n from os import path\n from random import randint\n@@ -21,7 +22,7 @@\n     \"\"\"\n \n     def __init__(self):\n-        self.userdb = []\n+        self.userdb = {}\n         self.userdb_file = '%s/userdb.txt' % CONFIG.get('honeypot', 'data_path')\n         self.load()\n \n@@ -44,55 +45,50 @@\n                 if line.startswith(b'#'):\n                     continue\n \n-                (login, uid, passwd) = line.split(b':', 2)\n+                login, passwd = re.split(br':\\w+:', line, 1)\n+                self.adduser(login, passwd)\n \n-                self.userdb.append((login, passwd))\n \n+    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):\n+        for credentials, policy in self.userdb.items():\n+            login, passwd = credentials\n \n-    def save(self):\n-        \"\"\"\n-        save the user db\n-        \"\"\"\n+            if self.match_rule(login, thelogin):\n+                if self.match_rule(passwd, thepasswd):\n+                    return policy\n \n-        # Note: this is subject to races between cowrie instances, but hey ...\n-        with open(self.userdb_file, 'w') as f:\n-            for (login, passwd) in self.userdb:\n-                f.write('%s:x:%s\\n' % (login, passwd))\n+        return False\n \n \n-    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):\n-        \"\"\"\n-        check entered username/password against database\n-        note that it allows multiple passwords for a single username\n-        it also knows wildcard '*' for any username or password\n-        prepend password with ! to explicitly deny it. Denials must come before wildcards\n-        \"\"\"\n-        for (login, passwd) in self.userdb:\n-            # Explicitly fail on !password\n-            if login == thelogin and passwd == b'!' + thepasswd:\n-                return False\n-            if login in (thelogin, b'*') and passwd in (thepasswd, b'*'):\n-                return True\n-        return False\n+    def match_rule(self, rule, input):\n+        if type(rule) is bytes:\n+            return rule in [b'*', input]\n+        else:\n+            return bool(rule.search(input))\n \n \n-    def user_password_exists(self, thelogin, thepasswd):\n+    def re_or_str(self, rule):\n         \"\"\"\n+        Convert a /.../ type rule to a regex, otherwise return the string as-is\n         \"\"\"\n-        for (login, passwd) in self.userdb:\n-            if login == thelogin and passwd == thepasswd:\n-                return True\n-        return False\n+        res = re.match(br'/(.+)/(i)?$', rule)\n+        if res:\n+            return re.compile(res.group(1), re.IGNORECASE if res.group(2) else 0)\n+\n+        return rule\n \n \n     def adduser(self, login, passwd):\n-        \"\"\"\n-        \"\"\"\n-        if self.user_password_exists(login, passwd):\n-            return\n-        self.userdb.append((login, passwd))\n-        self.save()\n+        login = self.re_or_str(login)\n+\n+        if passwd.startswith(b'!'):\n+            policy = False\n+            passwd = passwd[1:]\n+        else:\n+            policy = True\n \n+        passwd = self.re_or_str(passwd)\n+        self.userdb[(login, passwd)] = policy\n \n \n class AuthRandom(object):\n", "issue": "Implement regular expressions in userdb.txt\nThe file that contains the combinations of usernames and passwords that Cowrie accepts from the attackers (`data/userdb.txt`) currently handles 3 special characters - `#`, which means a comment till the end of the line, `!`, which means negation, and `*`, which means \"anything\" (in either the username or the password field).\r\n\r\nWould it be possible to allow any regular expression instead of the special characters '!' and '*'?\r\n\r\nI've seen attackers use variations of the password \"honeypot\" to determine that they are dealing with a honeypot and refuse to conduct their usual attack. Examples include \"Honeypot321\" (309 times), \"honeypot\" (6 times), and \"nologinissahoneypotlmao\" (once) over a 17-month period.\r\n\r\nI could, of course, explicitly block just these 3 passwords, but I'd like to disallow any password with the word \"honeypot\" (case-insensitive) in it.\n", "before_files": [{"content": "# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>\n# See the COPYRIGHT file for more information\n\n\"\"\"\nThis module contains ...\n\"\"\"\n\nfrom __future__ import division, absolute_import\n\nimport json\nfrom os import path\nfrom random import randint\n\nfrom twisted.python import log\n\nfrom cowrie.core.config import CONFIG\n\nclass UserDB(object):\n    \"\"\"\n    By Walter de Jong <walter@sara.nl>\n    \"\"\"\n\n    def __init__(self):\n        self.userdb = []\n        self.userdb_file = '%s/userdb.txt' % CONFIG.get('honeypot', 'data_path')\n        self.load()\n\n\n    def load(self):\n        \"\"\"\n        load the user db\n        \"\"\"\n\n        with open(self.userdb_file, 'rb') as f:\n            while True:\n                rawline = f.readline()\n                if not rawline:\n                    break\n\n                line = rawline.strip()\n                if not line:\n                    continue\n\n                if line.startswith(b'#'):\n                    continue\n\n                (login, uid, passwd) = line.split(b':', 2)\n\n                self.userdb.append((login, passwd))\n\n\n    def save(self):\n        \"\"\"\n        save the user db\n        \"\"\"\n\n        # Note: this is subject to races between cowrie instances, but hey ...\n        with open(self.userdb_file, 'w') as f:\n            for (login, passwd) in self.userdb:\n                f.write('%s:x:%s\\n' % (login, passwd))\n\n\n    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):\n        \"\"\"\n        check entered username/password against database\n        note that it allows multiple passwords for a single username\n        it also knows wildcard '*' for any username or password\n        prepend password with ! to explicitly deny it. Denials must come before wildcards\n        \"\"\"\n        for (login, passwd) in self.userdb:\n            # Explicitly fail on !password\n            if login == thelogin and passwd == b'!' + thepasswd:\n                return False\n            if login in (thelogin, b'*') and passwd in (thepasswd, b'*'):\n                return True\n        return False\n\n\n    def user_password_exists(self, thelogin, thepasswd):\n        \"\"\"\n        \"\"\"\n        for (login, passwd) in self.userdb:\n            if login == thelogin and passwd == thepasswd:\n                return True\n        return False\n\n\n    def adduser(self, login, passwd):\n        \"\"\"\n        \"\"\"\n        if self.user_password_exists(login, passwd):\n            return\n        self.userdb.append((login, passwd))\n        self.save()\n\n\n\nclass AuthRandom(object):\n    \"\"\"\n    Alternative class that defines the checklogin() method.\n    Users will be authenticated after a random number of attempts.\n    \"\"\"\n\n    def __init__(self):\n        # Default values\n        self.mintry, self.maxtry, self.maxcache = 2, 5, 10\n\n        # Are there auth_class parameters?\n        if CONFIG.has_option('honeypot', 'auth_class_parameters'):\n            parameters = CONFIG.get('honeypot', 'auth_class_parameters')\n            parlist = parameters.split(',')\n            if len(parlist) == 3:\n                self.mintry = int(parlist[0])\n                self.maxtry = int(parlist[1])\n                self.maxcache = int(parlist[2])\n\n        if self.maxtry < self.mintry:\n            self.maxtry = self.mintry + 1\n            log.msg('maxtry < mintry, adjusting maxtry to: %d' % (self.maxtry,))\n        self.uservar = {}\n        self.uservar_file = '%s/uservar.json' % CONFIG.get('honeypot', 'data_path')\n        self.loadvars()\n\n\n    def loadvars(self):\n        \"\"\"\n        Load user vars from json file\n        \"\"\"\n        if path.isfile(self.uservar_file):\n            with open(self.uservar_file, 'rb') as fp:\n                try:\n                    self.uservar = json.load(fp)\n                except:\n                    self.uservar = {}\n\n\n    def savevars(self):\n        \"\"\"\n        Save the user vars to json file\n        \"\"\"\n        data = self.uservar\n        # Note: this is subject to races between cowrie logins\n        with open(self.uservar_file, 'wb') as fp:\n            json.dump(data, fp)\n\n\n    def checklogin(self, thelogin, thepasswd, src_ip):\n        \"\"\"\n        Every new source IP will have to try a random number of times between\n        'mintry' and 'maxtry' before succeeding to login.\n        All username/password combinations  must be different.\n        The successful login combination is stored with the IP address.\n        Successful username/passwords pairs are also cached for 'maxcache' times.\n        This is to allow access for returns from different IP addresses.\n        Variables are saved in 'uservar.json' in the data directory.\n        \"\"\"\n\n        auth = False\n        userpass = thelogin + ':' + thepasswd\n\n        if not 'cache' in self.uservar:\n            self.uservar['cache'] = []\n        cache = self.uservar['cache']\n\n        # Check if it is the first visit from src_ip\n        if src_ip not in self.uservar:\n            self.uservar[src_ip] = {}\n            ipinfo = self.uservar[src_ip]\n            ipinfo['try'] = 0\n            if userpass in cache:\n                log.msg('first time for %s, found cached: %s' % (src_ip, userpass))\n                ipinfo['max'] = 1\n                ipinfo['user'] = thelogin\n                ipinfo['pw'] = thepasswd\n                auth = True\n                self.savevars()\n                return auth\n            else:\n                ipinfo['max'] = randint(self.mintry, self.maxtry)\n                log.msg('first time for %s, need: %d' % (src_ip, ipinfo['max']))\n\n        ipinfo = self.uservar[src_ip]\n\n        # Fill in missing variables\n        if not 'max' in ipinfo:\n            ipinfo['max'] = randint(self.mintry, self.maxtry)\n        if not 'try' in ipinfo:\n            ipinfo['try'] = 0\n        if not 'tried' in ipinfo:\n            ipinfo['tried'] = []\n\n        # Don't count repeated username/password combinations\n        if userpass in ipinfo['tried']:\n            log.msg('already tried this combination')\n            self.savevars()\n            return auth\n\n        ipinfo['try'] += 1\n        attempts = ipinfo['try']\n        need = ipinfo['max']\n        log.msg('login attempt: %d' % (attempts,))\n\n        # Check if enough login attempts are tried\n        if attempts < need:\n            self.uservar[src_ip]['tried'].append(userpass)\n        elif attempts == need:\n            ipinfo['user'] = thelogin\n            ipinfo['pw'] = thepasswd\n            cache.append(userpass)\n            if len(cache) > self.maxcache:\n                cache.pop(0)\n            auth = True\n        # Returning after successful login\n        elif attempts > need:\n            if not 'user' in ipinfo or not 'pw' in ipinfo:\n                log.msg('return, but username or password not set!!!')\n                ipinfo['tried'].append(userpass)\n                ipinfo['try'] = 1\n            else:\n                log.msg('login return, expect: [%s/%s]' % (ipinfo['user'], ipinfo['pw']))\n                if thelogin == ipinfo['user'] and thepasswd == ipinfo['pw']:\n                    auth = True\n        self.savevars()\n        return auth\n\n", "path": "cowrie/core/auth.py"}], "after_files": [{"content": "# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>\n# See the COPYRIGHT file for more information\n\n\"\"\"\nThis module contains ...\n\"\"\"\n\nfrom __future__ import division, absolute_import\n\nimport re\nimport json\nfrom os import path\nfrom random import randint\n\nfrom twisted.python import log\n\nfrom cowrie.core.config import CONFIG\n\nclass UserDB(object):\n    \"\"\"\n    By Walter de Jong <walter@sara.nl>\n    \"\"\"\n\n    def __init__(self):\n        self.userdb = {}\n        self.userdb_file = '%s/userdb.txt' % CONFIG.get('honeypot', 'data_path')\n        self.load()\n\n\n    def load(self):\n        \"\"\"\n        load the user db\n        \"\"\"\n\n        with open(self.userdb_file, 'rb') as f:\n            while True:\n                rawline = f.readline()\n                if not rawline:\n                    break\n\n                line = rawline.strip()\n                if not line:\n                    continue\n\n                if line.startswith(b'#'):\n                    continue\n\n                login, passwd = re.split(br':\\w+:', line, 1)\n                self.adduser(login, passwd)\n\n\n    def checklogin(self, thelogin, thepasswd, src_ip='0.0.0.0'):\n        for credentials, policy in self.userdb.items():\n            login, passwd = credentials\n\n            if self.match_rule(login, thelogin):\n                if self.match_rule(passwd, thepasswd):\n                    return policy\n\n        return False\n\n\n    def match_rule(self, rule, input):\n        if type(rule) is bytes:\n            return rule in [b'*', input]\n        else:\n            return bool(rule.search(input))\n\n\n    def re_or_str(self, rule):\n        \"\"\"\n        Convert a /.../ type rule to a regex, otherwise return the string as-is\n        \"\"\"\n        res = re.match(br'/(.+)/(i)?$', rule)\n        if res:\n            return re.compile(res.group(1), re.IGNORECASE if res.group(2) else 0)\n\n        return rule\n\n\n    def adduser(self, login, passwd):\n        login = self.re_or_str(login)\n\n        if passwd.startswith(b'!'):\n            policy = False\n            passwd = passwd[1:]\n        else:\n            policy = True\n\n        passwd = self.re_or_str(passwd)\n        self.userdb[(login, passwd)] = policy\n\n\nclass AuthRandom(object):\n    \"\"\"\n    Alternative class that defines the checklogin() method.\n    Users will be authenticated after a random number of attempts.\n    \"\"\"\n\n    def __init__(self):\n        # Default values\n        self.mintry, self.maxtry, self.maxcache = 2, 5, 10\n\n        # Are there auth_class parameters?\n        if CONFIG.has_option('honeypot', 'auth_class_parameters'):\n            parameters = CONFIG.get('honeypot', 'auth_class_parameters')\n            parlist = parameters.split(',')\n            if len(parlist) == 3:\n                self.mintry = int(parlist[0])\n                self.maxtry = int(parlist[1])\n                self.maxcache = int(parlist[2])\n\n        if self.maxtry < self.mintry:\n            self.maxtry = self.mintry + 1\n            log.msg('maxtry < mintry, adjusting maxtry to: %d' % (self.maxtry,))\n        self.uservar = {}\n        self.uservar_file = '%s/uservar.json' % CONFIG.get('honeypot', 'data_path')\n        self.loadvars()\n\n\n    def loadvars(self):\n        \"\"\"\n        Load user vars from json file\n        \"\"\"\n        if path.isfile(self.uservar_file):\n            with open(self.uservar_file, 'rb') as fp:\n                try:\n                    self.uservar = json.load(fp)\n                except:\n                    self.uservar = {}\n\n\n    def savevars(self):\n        \"\"\"\n        Save the user vars to json file\n        \"\"\"\n        data = self.uservar\n        # Note: this is subject to races between cowrie logins\n        with open(self.uservar_file, 'wb') as fp:\n            json.dump(data, fp)\n\n\n    def checklogin(self, thelogin, thepasswd, src_ip):\n        \"\"\"\n        Every new source IP will have to try a random number of times between\n        'mintry' and 'maxtry' before succeeding to login.\n        All username/password combinations  must be different.\n        The successful login combination is stored with the IP address.\n        Successful username/passwords pairs are also cached for 'maxcache' times.\n        This is to allow access for returns from different IP addresses.\n        Variables are saved in 'uservar.json' in the data directory.\n        \"\"\"\n\n        auth = False\n        userpass = thelogin + ':' + thepasswd\n\n        if not 'cache' in self.uservar:\n            self.uservar['cache'] = []\n        cache = self.uservar['cache']\n\n        # Check if it is the first visit from src_ip\n        if src_ip not in self.uservar:\n            self.uservar[src_ip] = {}\n            ipinfo = self.uservar[src_ip]\n            ipinfo['try'] = 0\n            if userpass in cache:\n                log.msg('first time for %s, found cached: %s' % (src_ip, userpass))\n                ipinfo['max'] = 1\n                ipinfo['user'] = thelogin\n                ipinfo['pw'] = thepasswd\n                auth = True\n                self.savevars()\n                return auth\n            else:\n                ipinfo['max'] = randint(self.mintry, self.maxtry)\n                log.msg('first time for %s, need: %d' % (src_ip, ipinfo['max']))\n\n        ipinfo = self.uservar[src_ip]\n\n        # Fill in missing variables\n        if not 'max' in ipinfo:\n            ipinfo['max'] = randint(self.mintry, self.maxtry)\n        if not 'try' in ipinfo:\n            ipinfo['try'] = 0\n        if not 'tried' in ipinfo:\n            ipinfo['tried'] = []\n\n        # Don't count repeated username/password combinations\n        if userpass in ipinfo['tried']:\n            log.msg('already tried this combination')\n            self.savevars()\n            return auth\n\n        ipinfo['try'] += 1\n        attempts = ipinfo['try']\n        need = ipinfo['max']\n        log.msg('login attempt: %d' % (attempts,))\n\n        # Check if enough login attempts are tried\n        if attempts < need:\n            self.uservar[src_ip]['tried'].append(userpass)\n        elif attempts == need:\n            ipinfo['user'] = thelogin\n            ipinfo['pw'] = thepasswd\n            cache.append(userpass)\n            if len(cache) > self.maxcache:\n                cache.pop(0)\n            auth = True\n        # Returning after successful login\n        elif attempts > need:\n            if not 'user' in ipinfo or not 'pw' in ipinfo:\n                log.msg('return, but username or password not set!!!')\n                ipinfo['tried'].append(userpass)\n                ipinfo['try'] = 1\n            else:\n                log.msg('login return, expect: [%s/%s]' % (ipinfo['user'], ipinfo['pw']))\n                if thelogin == ipinfo['user'] and thepasswd == ipinfo['pw']:\n                    auth = True\n        self.savevars()\n        return auth\n\n", "path": "cowrie/core/auth.py"}]}