mempot 2.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. core/__init__.py +0 -0
  2. core/config.py +50 -0
  3. core/logfile.py +74 -0
  4. core/output.py +39 -0
  5. core/paths.py +53 -0
  6. core/protocol.py +460 -0
  7. core/tools.py +168 -0
  8. mempot/__init__.py +25 -0
  9. mempot/cli.py +513 -0
  10. mempot/data/Dockerfile +56 -0
  11. mempot/data/docs/INSTALL.md +426 -0
  12. mempot/data/docs/INSTALLWIN.md +437 -0
  13. mempot/data/docs/PLUGINS.md +21 -0
  14. mempot/data/docs/TODO.md +17 -0
  15. mempot/data/docs/datadog/README.md +32 -0
  16. mempot/data/docs/discord/README.md +49 -0
  17. mempot/data/docs/mysql/README.md +176 -0
  18. mempot/data/docs/mysql/READMEWIN.md +157 -0
  19. mempot/data/docs/mysql/mysql.sql +60 -0
  20. mempot/data/docs/postgres/README.md +184 -0
  21. mempot/data/docs/postgres/READMEWIN.md +196 -0
  22. mempot/data/docs/postgres/postgres.sql +58 -0
  23. mempot/data/docs/slack/README.md +68 -0
  24. mempot/data/docs/sqlite3/README.md +131 -0
  25. mempot/data/docs/sqlite3/READMEWIN.md +123 -0
  26. mempot/data/docs/sqlite3/sqlite3.sql +58 -0
  27. mempot/data/docs/telegram/README.md +103 -0
  28. mempot/data/etc/honeypot.cfg +424 -0
  29. mempot/data/etc/honeypot.cfg.base +412 -0
  30. mempot/data/test/.gitignore +6 -0
  31. mempot/data/test/README.md +39 -0
  32. mempot/data/test/baseline +361 -0
  33. mempot/data/test/input +215 -0
  34. mempot/data/test/test.py +74 -0
  35. mempot/honeypot.py +118 -0
  36. mempot-2.0.0.dist-info/METADATA +148 -0
  37. mempot-2.0.0.dist-info/RECORD +63 -0
  38. mempot-2.0.0.dist-info/WHEEL +6 -0
  39. mempot-2.0.0.dist-info/entry_points.txt +2 -0
  40. mempot-2.0.0.dist-info/licenses/LICENSE +674 -0
  41. mempot-2.0.0.dist-info/top_level.txt +3 -0
  42. output_plugins/__init__.py +0 -0
  43. output_plugins/couch.py +68 -0
  44. output_plugins/datadog.py +74 -0
  45. output_plugins/discord.py +125 -0
  46. output_plugins/elastic.py +137 -0
  47. output_plugins/hpfeed.py +43 -0
  48. output_plugins/influx2.py +60 -0
  49. output_plugins/jsonlog.py +36 -0
  50. output_plugins/kafka.py +57 -0
  51. output_plugins/localsyslog.py +64 -0
  52. output_plugins/mongodb.py +83 -0
  53. output_plugins/mysql.py +214 -0
  54. output_plugins/nlcvapi.py +119 -0
  55. output_plugins/postgres.py +166 -0
  56. output_plugins/redisdb.py +48 -0
  57. output_plugins/rethinkdblog.py +46 -0
  58. output_plugins/slack.py +83 -0
  59. output_plugins/socketlog.py +40 -0
  60. output_plugins/sqlite.py +155 -0
  61. output_plugins/telegram.py +130 -0
  62. output_plugins/textlog.py +35 -0
  63. output_plugins/xmpp.py +181 -0
core/__init__.py ADDED
File without changes
core/config.py ADDED
@@ -0,0 +1,50 @@
1
+
2
+ from configparser import ConfigParser, ExtendedInterpolation
3
+
4
+ from os import environ
5
+
6
+
7
+ def to_environ_key(key):
8
+ return key.upper()
9
+
10
+
11
+ class EnvironmentConfigParser(ConfigParser):
12
+
13
+ def has_option(self, section, option):
14
+ if to_environ_key('_'.join((section, option))) in environ:
15
+ return True
16
+ return super(EnvironmentConfigParser, self).has_option(section, option)
17
+
18
+ def get(self, section, option, **kwargs):
19
+ key = to_environ_key('_'.join((section, option)))
20
+ if key in environ:
21
+ return environ[key]
22
+ return super(EnvironmentConfigParser, self).get(section, option, **kwargs)
23
+
24
+
25
+ def readConfigFile(cfgfile):
26
+ """
27
+ Read config files and return ConfigParser object
28
+
29
+ @param cfgfile: filename or array of filenames
30
+ @return: ConfigParser object
31
+ """
32
+ parser = EnvironmentConfigParser(
33
+ interpolation=ExtendedInterpolation(),
34
+ converters={'list': lambda x: [i.strip() for i in x.split(',')]}
35
+ )
36
+ parser.read(cfgfile)
37
+ return parser
38
+
39
+
40
+ def _config_files():
41
+ # Import here (not at module top) to avoid any circular-import risk.
42
+ from core.paths import workdir_path, bundled
43
+ return [
44
+ bundled('etc', 'honeypot.cfg.base'), # bundled read-only defaults
45
+ workdir_path('etc', 'honeypot.cfg'), # site-local overrides
46
+ workdir_path('honeypot.cfg'), # convenience root-level override
47
+ ]
48
+
49
+
50
+ CONFIG = readConfigFile(_config_files())
core/logfile.py ADDED
@@ -0,0 +1,74 @@
1
+
2
+ from sys import stdout
3
+ from datetime import datetime
4
+
5
+ from pytz import timezone
6
+
7
+ from twisted.python import log, util
8
+ from twisted.python.logfile import DailyLogFile
9
+
10
+
11
+ class HoneypotDailyLogFile(DailyLogFile):
12
+ """
13
+ Overload original Twisted with improved date formatting
14
+ """
15
+
16
+ def suffix(self, tupledate):
17
+ """
18
+ Return the suffix given a (year, month, day) tuple or unixtime
19
+ """
20
+ try:
21
+ return "{:02d}-{:02d}-{:02d}".format(tupledate[0], tupledate[1], tupledate[2])
22
+ except Exception:
23
+ # try taking a float unixtime
24
+ return '_'.join(map(str, self.toDate(tupledate)))
25
+
26
+
27
+ def myFLOemit(self, eventDict):
28
+ """
29
+ Format the given log event as text and write it to the output file.
30
+
31
+ @param eventDict: a log event
32
+ @type eventDict: L{dict} mapping L{str} (native string) to L{object}
33
+ """
34
+
35
+ # Custom emit for FileLogObserver
36
+ text = log.textFromEventDict(eventDict)
37
+ if text is None:
38
+ return
39
+ timeStr = self.formatTime(eventDict['time'])
40
+ fmtDict = {
41
+ 'text': text.replace('\n', '\n\t')
42
+ }
43
+ msgStr = log._safeFormat('%(text)s\n', fmtDict)
44
+ util.untilConcludes(self.write, timeStr + ' ' + msgStr)
45
+ util.untilConcludes(self.flush)
46
+
47
+
48
+ def myFLOformatTime(self, when):
49
+ """
50
+ Log time in UTC
51
+
52
+ By default it's formatted as an ISO8601-like string (ISO8601 date and
53
+ ISO8601 time separated by a space). It can be customized using the
54
+ C{timeFormat} attribute, which will be used as input for the underlying
55
+ L{datetime.datetime.strftime} call.
56
+
57
+ @type when: C{int}
58
+ @param when: POSIX (ie, UTC) timestamp.
59
+
60
+ @rtype: C{str}
61
+ """
62
+ timeFormatString = self.timeFormat
63
+ if timeFormatString is None:
64
+ timeFormatString = '[%Y-%m-%d %H:%M:%S.%fZ]'
65
+ return datetime.fromtimestamp(when, tz=timezone('UTC')).strftime(timeFormatString)
66
+
67
+
68
+ def set_logger(cfg_options):
69
+ log.FileLogObserver.emit = myFLOemit
70
+ log.FileLogObserver.formatTime = myFLOformatTime
71
+ if cfg_options['logfile'] is None:
72
+ log.startLogging(stdout)
73
+ else:
74
+ log.startLogging(HoneypotDailyLogFile.fromFullPath(cfg_options['logfile']), setStdout=False)
core/output.py ADDED
@@ -0,0 +1,39 @@
1
+
2
+ from socket import gethostname
3
+
4
+ from core.config import CONFIG
5
+
6
+
7
+ class Output(object):
8
+ """
9
+ Abstract base class intended to be inherited by output plugins.
10
+ """
11
+
12
+ def __init__(self, general_options):
13
+
14
+ self.cfg = general_options
15
+
16
+ if 'sensor' in self.cfg:
17
+ self.sensor = self.cfg['sensor']
18
+ else:
19
+ self.sensor = CONFIG.get('honeypot', 'sensor_name', fallback=gethostname())
20
+
21
+ self.start()
22
+
23
+ def start(self):
24
+ """
25
+ Abstract method to initialize output plugin
26
+ """
27
+ pass
28
+
29
+ def stop(self):
30
+ """
31
+ Abstract method to shut down output plugin
32
+ """
33
+ pass
34
+
35
+ def write(self, event):
36
+ """
37
+ Handle a general event within the output plugin
38
+ """
39
+ pass
core/paths.py ADDED
@@ -0,0 +1,53 @@
1
+ """
2
+ paths.py - Single source of truth for runtime path resolution.
3
+
4
+ The honeypot needs a "working directory" containing:
5
+ data/ geolocation databases, SQLite db, etc.
6
+ etc/ config file (honeypot.cfg.base)
7
+ log/ rotating log files (created on demand)
8
+
9
+ Priority for locating the working directory:
10
+ 1. MEMPOT_WORKDIR environment variable
11
+ 2. Current working directory
12
+
13
+ The bundled read-only defaults (etc/*.cfg.base, responses/*.json)
14
+ are installed inside the `mempot` package and located via the package's
15
+ own __file__ attribute, which works on all Python versions without
16
+ requiring pkg_resources or importlib.resources.
17
+ """
18
+
19
+ from __future__ import absolute_import
20
+
21
+
22
+ from os import getcwd, environ
23
+ from os.path import abspath, dirname, join
24
+
25
+
26
+ def get_workdir():
27
+ """Return the absolute path to the runtime working directory."""
28
+ env = environ.get('MEMPOT_WORKDIR', '').strip()
29
+ if env:
30
+ return abspath(env)
31
+ return getcwd()
32
+
33
+
34
+ def workdir_path(*parts):
35
+ """Return an absolute path rooted at the working directory."""
36
+ return join(get_workdir(), *parts)
37
+
38
+
39
+ def bundled(*parts):
40
+ """
41
+ Return the filesystem path to a file bundled inside the installed package.
42
+ Arguments are path components relative to the mempot/data/ directory,
43
+ passed as separate strings (like os.path.join) to avoid hardcoded separators.
44
+
45
+ Uses the package's own __file__ to locate the data directory, which works
46
+ on all Python versions (2.7+) without requiring pkg_resources or
47
+ importlib.resources.
48
+ """
49
+ # mempot/data/ lives alongside this module's package (core/ is a sibling
50
+ # of mempot/), so we go up one level from core/ to find mempot/data/.
51
+ here = dirname(abspath(__file__))
52
+ package_dir = join(dirname(here), 'mempot')
53
+ return join(package_dir, 'data', *parts)
core/protocol.py ADDED
@@ -0,0 +1,460 @@
1
+
2
+ from __future__ import absolute_import
3
+
4
+ from binascii import hexlify
5
+ from collections import OrderedDict
6
+ from hashlib import sha256
7
+ from ipaddress import ip_address, ip_network
8
+ from random import randint, uniform
9
+ from sys import version_info
10
+ from time import time
11
+ from uuid import uuid4
12
+
13
+ from core.tools import (
14
+ decode,
15
+ encode,
16
+ get_local_ip,
17
+ get_utc_time,
18
+ printable,
19
+ write_event
20
+ )
21
+
22
+ from twisted.internet.protocol import Factory, Protocol
23
+ from twisted.python.log import msg
24
+
25
+
26
+ if version_info[0] >= 3:
27
+ def unicode(x):
28
+ return x
29
+
30
+
31
+ class MemcacheServer(Protocol):
32
+ def __init__(self, options):
33
+ self.cfg = options
34
+ self.state = None
35
+ self.data = {}
36
+
37
+ def get_stats(self, kind):
38
+ items = randint(80000000, 90000000)
39
+ ret = ''
40
+ if kind == 'settings':
41
+ temp = OrderedDict([
42
+ ('maxbytes', 67108864),
43
+ ('maxconns', 1024),
44
+ ('tcpport', self.cfg['port']),
45
+ ('udpport', 0),
46
+ ('inter', 'NULL'),
47
+ ('verbosity', 0),
48
+ ('oldest', 0),
49
+ ('evictions', 'on'),
50
+ ('domain_socket', 'NULL'),
51
+ ('umask', 700),
52
+ ('shutdown_command', 'no'),
53
+ ('growth_factor', 1.25),
54
+ ('chunk_size', 48),
55
+ ('num_threads', 4),
56
+ ('num_threads_per_udp', 4),
57
+ ('stat_key_prefix', ':'),
58
+ ('detail_enabled', 'no'),
59
+ ('reqs_per_event', 20),
60
+ ('cas_enabled', 'no'),
61
+ ('tcp_backlog', 1024),
62
+ ('binding_protocol', 'auto-negotiate'),
63
+ ('auth_enabled_sasl', 'no'),
64
+ ('auth_enabled_ascii', 'no'),
65
+ ('item_size_max', 1048576),
66
+ ('maxconns_fast', 'yes'),
67
+ ('hashpower_init', 0),
68
+ ('slab_reassign', 'yes'),
69
+ ('slab_automove', 1),
70
+ ('slab_automove_ratio', 0.80),
71
+ ('slab_automove_window', 30),
72
+ ('slab_chunk_max', 524288),
73
+ ('lru_crawler', 'yes'),
74
+ ('lru_crawler_sleep', 100),
75
+ ('lru_crawler_tocrawl', 0),
76
+ ('tail_repair_time', 0),
77
+ ('flush_enabled', 'yes'),
78
+ ('dump_enabled', 'yes'),
79
+ ('hash_algorithm', 'murmur3'),
80
+ ('lru_maintainer_thread', 'yes'),
81
+ ('lru_segmented', 'yes'),
82
+ ('hot_lru_pct', 20),
83
+ ('warm_lru_pct', 40),
84
+ ('hot_max_factor', 0.20),
85
+ ('warm_max_factor', 2.00),
86
+ ('temp_lru', 'no'),
87
+ ('temporary_ttl', 61),
88
+ ('idle_timeout', 0),
89
+ ('watcher_logbuf_size', 262144),
90
+ ('worker_logbuf_size', 65536),
91
+ ('read_buf_mem_limit', 0),
92
+ ('track_sizes', 'no'),
93
+ ('inline_ascii_response', 'no'),
94
+ ('ext_item_size', 512),
95
+ ('ext_item_age', 4294967295),
96
+ ('ext_low_ttl', 0),
97
+ ('ext_recache_rate', 2000),
98
+ ('ext_wbuf_size', 4194304),
99
+ ('ext_compact_under', 0),
100
+ ('ext_drop_under', 0),
101
+ ('ext_max_sleep', 1000000),
102
+ ('ext_max_frag', 0.80),
103
+ ('slab_automove_freeratio', 0.010),
104
+ ('ext_drop_unread', 'no'),
105
+ ('ssl_enabled', 'no'),
106
+ ('ssl_chain_cert', '(null)'),
107
+ ('ssl_key', '(null)'),
108
+ ('ssl_verify_mode', 0),
109
+ ('ssl_keyformat', 1),
110
+ ('ssl_ciphers', 'NULL'),
111
+ ('ssl_ca_cert', 'NULL'),
112
+ ('ssl_wbuf_size', 16384),
113
+ ('ssl_session_cache', 'no'),
114
+ ('ssl_kernel_tls', 'no'),
115
+ ('ssl_min_version', 'tlsv1.2'),
116
+ ('num_napi_ids', '(null)'),
117
+ ('memory_file', '(null)'),
118
+ ('client_flags_size', 4),
119
+ ])
120
+ #elif kind == 'sizes':
121
+ # pass
122
+ #elif kind == 'slabs':
123
+ # pass
124
+ #elif kind == 'items':
125
+ # pass
126
+ #elif kind == 'conns':
127
+ # pass
128
+ else:
129
+ # e.g., 'stats'
130
+ temp = OrderedDict([
131
+ ('pid', randint(5, 400)),
132
+ ('uptime', randint(1000, 2000)),
133
+ ('time', int(time())),
134
+ ('version', '1.6.29'),
135
+ ('libevent', '2.1.8-stable'),
136
+ ('pointer_size', 64),
137
+ ('rusage_user', round(uniform(0.1, 0.9), 4)),
138
+ ('rusage_system', round(uniform(0.1, 0.9), 6)),
139
+ ('max_connections', 1024),
140
+ ('curr_connections', randint(1, 1024)),
141
+ ('total_connections', 5),
142
+ ('rejected_connections', 0),
143
+ ('connection_structures', 2),
144
+ ('reserved_fds', 20),
145
+ ('cmd_get', 0),
146
+ ('cmd_set', 40),
147
+ ('cmd_flush', 0),
148
+ ('cmd_touch', 0),
149
+ ('get_hits', 0),
150
+ ('get_misses', 0),
151
+ ('get_expired', 0),
152
+ ('get_flushed', 0),
153
+ ('delete_misses', 0),
154
+ ('delete_hits', 0),
155
+ ('incr_misses', 0),
156
+ ('incr_hits', 0),
157
+ ('decr_misses', 0),
158
+ ('decr_hits', 0),
159
+ ('cas_misses', 0),
160
+ ('cas_hits', 0),
161
+ ('cas_badval', 0),
162
+ ('touch_hits', 0),
163
+ ('touch_misses', 0),
164
+ ('auth_cmds', 0),
165
+ ('auth_errors', 0),
166
+ ('bytes_read', randint(7000000, 8000000)),
167
+ ('bytes_written', randint(500000, 1000000)),
168
+ ('limit_maxbytes', 33554432),
169
+ ('accepting_conns', 1),
170
+ ('listen_disabled_num', 0),
171
+ ('time_in_listen_disabled_us', 0),
172
+ ('threads', randint(4, 9000)),
173
+ ('conn_yields', 0),
174
+ ('hash_power_level', 16),
175
+ ('hash_bytes', 524288),
176
+ ('hash_is_expanding', False),
177
+ ('slab_reassign_rescues', 0),
178
+ ('slab_reassign_chunk_rescues', 0),
179
+ ('slab_reassign_evictions_nomem', 0),
180
+ ('slab_reassign_inline_reclaim', 0),
181
+ ('slab_reassign_busy_items', 0),
182
+ ('slab_reassign_busy_deletes', 0),
183
+ ('slab_reassign_running', False),
184
+ ('slabs_moved', 0),
185
+ ('lru_crawler_running', 0),
186
+ ('lru_crawler_starts', randint(500000, 700000)),
187
+ ('lru_maintainer_juggles', randint(400000, 500000)),
188
+ ('malloc_fails', 0),
189
+ ('log_worker_dropped', 0),
190
+ ('log_worker_written', 0),
191
+ ('log_watcher_skipped', 0),
192
+ ('log_watcher_sent', 0),
193
+ ('bytes', randint(13554432, 33554432)),
194
+ ('curr_items', items),
195
+ ('total_items', items),
196
+ ('slab_global_page_pool', 0),
197
+ ('expired_unfetched', 0),
198
+ ('evicted_unfetched', 0),
199
+ ('evicted_active', 0),
200
+ ('evictions', 0),
201
+ ('reclaimed', 0),
202
+ ('crawler_reclaimed', 0),
203
+ ('crawler_items_checked', randint(5000, 6000)),
204
+ ('lrutail_reflocked', 0),
205
+ ('moves_to_cold', randint(5000, 6000)),
206
+ ('moves_to_warm', randint(5000, 6000)),
207
+ ('moves_within_lru', 0),
208
+ ('direct_reclaims', 0),
209
+ ('lru_bumps_dropped', 0),
210
+ ])
211
+
212
+ for key, value in temp.items():
213
+ ret += 'STAT {} {}\r\n'.format(key, value)
214
+ ret += 'END\r\n'
215
+ return ret.encode()
216
+
217
+ def check_len(self, verb, data):
218
+ if verb in [
219
+ b'set',
220
+ b'add',
221
+ b'replace',
222
+ b'append',
223
+ b'prepend',
224
+ b'cas',
225
+ ]:
226
+ if len(data) >= 5:
227
+ self.key = data[1]
228
+ self.state = 2
229
+ return True
230
+ else:
231
+ return False
232
+ elif verb in [
233
+ b'get',
234
+ b'gets',
235
+ b'delete',
236
+ ]:
237
+ if len(data) > 1:
238
+ self.key = data[1]
239
+ return True
240
+ else:
241
+ return False
242
+ elif verb in [
243
+ b'incr',
244
+ b'decr'
245
+ ]:
246
+ if len(data) > 2:
247
+ self.key = data[1]
248
+ return True
249
+ else:
250
+ return False
251
+ else:
252
+ return True
253
+
254
+ def process_command(self, line):
255
+ _data = line.split(b' ')
256
+ self.verb = _data[0]
257
+ if not self.check_len(self.verb, _data):
258
+ self.transport.write(b'ERROR\r\n')
259
+ return
260
+ if self.verb == b'stats':
261
+ if len(_data) > 1:
262
+ if _data[1] == b'settings':
263
+ response = self.get_stats('settings')
264
+ elif _data[1] == b'cachedump':
265
+ response = ''
266
+ for key, value in self.data.items():
267
+ response += 'ITEM {} [{} b; {} s]\r\n'.format(
268
+ decode(key), len(value), randint(1000000, 2000000)
269
+ )
270
+ response += 'END\r\n'
271
+ response = response.encode()
272
+ elif _data[1] == b'slabs':
273
+ response = self.get_stats('stats')
274
+ elif _data[1] == b'sizes':
275
+ response = self.get_stats('stats')
276
+ elif _data[1] == b'items':
277
+ response = self.get_stats('stats')
278
+ else:
279
+ response = self.get_stats('stats')
280
+ else:
281
+ response = self.get_stats('stats')
282
+ self.transport.write(response)
283
+ elif self.verb == b'flush_all':
284
+ self.data = {}
285
+ self.transport.write(b'OK\r\n')
286
+ elif hexlify(self.verb) == b'fff4fffd06' or self.verb == b'quit':
287
+ self.transport.loseConnection()
288
+ elif self.verb == b'get':
289
+ if self.key in self.data:
290
+ response = 'VALUE {} 0 {}\r\n{}\r\n'.format(
291
+ decode(self.key),
292
+ len(self.data[self.key]),
293
+ decode(self.data[self.key])
294
+ )
295
+ self.transport.write(response.encode())
296
+ self.transport.write(b'END\r\n')
297
+ elif self.verb == b'delete':
298
+ if self.key in self.data:
299
+ self.data.pop(self.key)
300
+ self.transport.write(b'DELETED\r\n')
301
+ else:
302
+ self.transport.write(b'NOT_FOUND\r\n')
303
+ elif self.verb == b'incr':
304
+ if not _data[2].isdigit():
305
+ self.transport.write(b'ERROR\r\n')
306
+ else:
307
+ if self.key in self.data:
308
+ if self.data[self.key].isdigit():
309
+ result = str(int(self.data[self.key]) + int(_data[2])).encode()
310
+ self.data[self.key] = result
311
+ self.transport.write(result + b'\r\n')
312
+ else:
313
+ self.transport.write(b'CLIENT_ERROR cannot increment or decrement non-numeric value\r\n')
314
+ else:
315
+ self.transport.write(b'NOT_FOUND\r\n')
316
+ elif self.verb == b'decr':
317
+ if not _data[2].isdigit():
318
+ self.transport.write(b'ERROR\r\n')
319
+ else:
320
+ if self.key in self.data:
321
+ if self.data[self.key].isdigit():
322
+ result = str(int(self.data[self.key]) - int(_data[2])).encode()
323
+ self.data[self.key] = result
324
+ self.transport.write(result + b'\r\n')
325
+ else:
326
+ self.transport.write(b'CLIENT_ERROR cannot increment or decrement non-numeric value\r\n')
327
+ else:
328
+ self.transport.write(b'NOT_FOUND\r\n')
329
+ elif self.verb == b'touch':
330
+ if self.key in self.data:
331
+ self.transport.write(b'TOUCHED\r\n')
332
+ else:
333
+ self.transport.write(b'NOT_FOUND\r\n')
334
+ elif self.verb == b'version':
335
+ self.transport.write(b'VERSION 1.6.29\r\n')
336
+ elif self.verb == b'verbosity':
337
+ self.transport.write(b'OK\r\n')
338
+ elif self.verb == b'cache_memlimit':
339
+ self.transport.write(b'OK\r\n')
340
+ #elif self.verb == b'cas':
341
+ # pass
342
+ #elif self.verb == b'gat':
343
+ # pass
344
+ #elif self.verb == b'gats':
345
+ # pass
346
+ #elif self.verb == b'gets':
347
+ # pass
348
+ elif self.verb not in [b'set', b'add', b'replace', b'append', b'prepend']:
349
+ self.transport.write(b'ERROR\r\n')
350
+ if self.verb != b'':
351
+ peer = self.transport.getPeer()
352
+ self.report_event(
353
+ 'command',
354
+ peer.host,
355
+ peer.port,
356
+ decode(line)
357
+ )
358
+
359
+ def process_data (self, line):
360
+ if self.verb == b'set':
361
+ self.data[self.key] = line
362
+ self.transport.write(b'STORED\r\n')
363
+ elif self.verb == b'replace':
364
+ if self.key in self.data:
365
+ self.data[self.key] = line
366
+ self.transport.write(b'STORED\r\n')
367
+ else:
368
+ self.transport.write(b'NOT_STORED\r\n')
369
+ elif self.verb == b'add':
370
+ if self.key in self.data:
371
+ self.transport.write(b'NOT_STORED\r\n')
372
+ else:
373
+ self.data[self.key] = line
374
+ self.transport.write(b'STORED\r\n')
375
+ elif self.verb == b'append':
376
+ if self.key in self.data:
377
+ self.data[self.key] += line
378
+ self.transport.write(b'STORED\r\n')
379
+ else:
380
+ self.transport.write(b'NOT_STORED\r\n')
381
+ elif self.verb == b'prepend':
382
+ if self.key in self.data:
383
+ self.data[self.key] = line + self.data[self.key]
384
+ self.transport.write(b'STORED\r\n')
385
+ else:
386
+ self.transport.write(b'NOT_STORED\r\n')
387
+ self.state = 1
388
+ if line != b'':
389
+ peer = self.transport.getPeer()
390
+ self.report_event(
391
+ 'data',
392
+ peer.host,
393
+ peer.port,
394
+ decode(line)
395
+ )
396
+
397
+ def process_input(self, line):
398
+ if self.state == 1:
399
+ self.process_command(line)
400
+ elif self.state == 2:
401
+ self.process_data(line)
402
+
403
+ def connectionMade(self):
404
+ self.state = 1
405
+ self.session = uuid4().hex[:12]
406
+ peer = self.transport.getPeer()
407
+ self.report_event('connect', peer.host, peer.port)
408
+
409
+ def dataReceived(self, data):
410
+ if self.state in [1, 2]:
411
+ line = data.split(b'\r\n')[0].strip()
412
+ self.process_input(line)
413
+
414
+ def connectionLost(self, reason):
415
+ peer = self.transport.getPeer()
416
+ self.report_event('disconnect', peer.host, peer.port, reason.value)
417
+ self.state = None
418
+ self.session = None
419
+
420
+ def report_event(self, operation, ip, port, command=None):
421
+ for network in self.cfg['blacklist']:
422
+ if ip_address(unicode(ip)) in ip_network(unicode(network)):
423
+ return
424
+ unix_time = time()
425
+ operation = operation.lower()
426
+ if operation == 'connect':
427
+ message = 'Connection made from {}:{}.'.format(ip, port)
428
+ elif operation == 'disconnect':
429
+ message = '{}:{} disconnected. Reason: {}'.format(ip, port, command)
430
+ elif operation == 'command':
431
+ message = 'Command: "{}" from {}:{}.'.format(printable(command), ip, port)
432
+ elif operation == 'data':
433
+ message = 'Data: "{}" from {}:{}.'.format(printable(command), ip, port)
434
+ else:
435
+ message = 'Error: Unknown operation "{}" from {}:{}.'.format(operation, ip, port)
436
+ event = {
437
+ 'session': self.session,
438
+ 'eventid': 'mempot.' + operation,
439
+ 'operation': operation,
440
+ 'timestamp': get_utc_time(unix_time),
441
+ 'unixtime': unix_time,
442
+ 'src_ip': ip,
443
+ 'src_port': port,
444
+ 'dst_port': self.cfg['port'],
445
+ 'sensor': self.cfg['sensor'],
446
+ 'dst_ip': self.cfg['public_ip'] if self.cfg['report_public_ip'] else get_local_ip()
447
+ }
448
+ if command is not None and operation != 'disconnect':
449
+ event['command'] = printable(command)
450
+ event['sha256'] = sha256(encode(command)).hexdigest()
451
+ msg(message)
452
+ write_event(event, self.cfg)
453
+
454
+
455
+ class MemcacheFactory(Factory):
456
+ def __init__(self, options):
457
+ self.cfg = options
458
+
459
+ def buildProtocol(self, addr):
460
+ return MemcacheServer(self.cfg)