We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
builtins.KeyError: 'log_time' Python error
**Describe the bug**
Cowrie won't log properly, due that output plugins are not working -> output_splunk
Following error occurs:
```
2021-04-28T07:00:17.796991Z [twisted.logger._observer#critical] Temporarily disabling observer LegacyLogObserverWrapper(<bound method Output.emit of <cowrie.output.virustotal.Output object at 0x7f3a13c9c550>>) due to exception: [Failure instance: Traceback: <class 'KeyError'>: 'log_time'
        /home/cowrie/cowrie/src/cowrie/ssh/transport.py:246:connectionLost
        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/threadable.py:51:sync
        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/log.py:281:msg
        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py:147:publishToNewObserver
        --- <exception caught here> ---
        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_observer.py:82:__call__
        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py:55:__call__
        ]
        Traceback (most recent call last):
          File "/home/cowrie/cowrie/src/cowrie/ssh/transport.py", line 246, in connectionLost
            log.msg(
          File "/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/threadable.py", line 51, in sync
            return function(self, *args, **kwargs)
          File "/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/log.py", line 281, in msg
            _publishNew(self._publishPublisher, actualEventDict, textFromEventDict)
          File "/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py", line 147, in publishToNewObserver
            observer(eventDict)
        --- <exception caught here> ---
          File "/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_observer.py", line 82, in __call__
            observer(event)
          File "/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py", line 55, in __call__
            event["time"] = event["log_time"]
        builtins.KeyError: 'log_time'
```

**To Reproduce**
Steps to reproduce the behavior:
1. git clone cowrie
2. setup venv
3. setup cowrie.cfg
4. include splunk output
5. run cowrie
6. run honeypot session

**Expected behavior**
Cowrie should properly log.

**Server (please complete the following information):**
 - OS: `Linux cowrie-1 5.4.103-1-pve #1 SMP PVE 5.4.103-1 (Sun, 07 Mar 2021 15:55:09 +0100) x86_64 x86_64 x86_64 GNU/Linux`
 - Python: Python 3.8.6


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cowrie/core/output.py`
Content:
```
1 # Copyright (c) 2015 Michel Oosterhof <michel@oosterhof.net>
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 #
8 # 1. Redistributions of source code must retain the above copyright
9 #    notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 #    notice, this list of conditions and the following disclaimer in the
12 #    documentation and/or other materials provided with the distribution.
13 # 3. The names of the author(s) may not be used to endorse or promote
14 #    products derived from this software without specific prior written
15 #    permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
18 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 # SUCH DAMAGE.
28 
29 
30 import abc
31 import re
32 import socket
33 import time
34 from os import environ
35 from typing import Any, Dict, Pattern
36 
37 from twisted.internet import reactor
38 from twisted.logger import formatTime
39 
40 from cowrie.core.config import CowrieConfig
41 
42 # Events:
43 #  cowrie.client.fingerprint
44 #  cowrie.client.size
45 #  cowrie.client.var
46 #  cowrie.client.version
47 #  cowrie.command.input
48 #  cowrie.command.failed
49 #  cowrie.command.success (deprecated)
50 #  cowrie.direct-tcpip.data
51 #  cowrie.direct-tcpip.request
52 #  cowrie.log.closed
53 #  cowrie.login.failed
54 #  cowrie.login.success
55 #  cowrie.session.closed
56 #  cowrie.session.connect
57 #  cowrie.session.file_download
58 #  cowrie.session.file_upload
59 
60 """
61 The time is available in two formats in each event, as key 'time'
62 in epoch format and in key 'timestamp' as a ISO compliant string
63 in UTC.
64 """
65 
66 
67 class Output(metaclass=abc.ABCMeta):
68     """
69     This is the abstract base class intended to be inherited by
70     cowrie output plugins. Plugins require the mandatory
71     methods: stop, start and write
72     """
73 
74     def __init__(self) -> None:
75         self.sessions: Dict[str, str] = {}
76         self.ips: Dict[str, str] = {}
77 
78         # Need these for each individual transport, or else the session numbers overlap
79         self.sshRegex: Pattern[str] = re.compile(".*SSHTransport,([0-9]+),[0-9a-f:.]+$")
80         self.telnetRegex: Pattern[str] = re.compile(
81             ".*TelnetTransport,([0-9]+),[0-9a-f:.]+$"
82         )
83         self.sensor: str = CowrieConfig.get(
84             "honeypot", "sensor_name", fallback=socket.gethostname()
85         )
86         self.timeFormat: str
87 
88         # use Z for UTC (Zulu) time, it's shorter.
89         if "TZ" in environ and environ["TZ"] == "UTC":
90             self.timeFormat = "%Y-%m-%dT%H:%M:%S.%fZ"
91         else:
92             self.timeFormat = "%Y-%m-%dT%H:%M:%S.%f%z"
93 
94         # Event trigger so that stop() is called by the reactor when stopping
95         reactor.addSystemEventTrigger("before", "shutdown", self.stop)  # type: ignore
96 
97         self.start()
98 
99     def logDispatch(self, **kw: str) -> None:
100         """
101         Use logDispatch when the HoneypotTransport prefix is not available.
102         Here you can explicitly set the sessionIds to tie the sessions together
103         """
104         ev = kw
105         # ev["message"] = msg
106         self.emit(ev)
107 
108     @abc.abstractmethod
109     def start(self) -> None:
110         """
111         Abstract method to initialize output plugin
112         """
113         pass
114 
115     @abc.abstractmethod
116     def stop(self) -> None:
117         """
118         Abstract method to shut down output plugin
119         """
120         pass
121 
122     @abc.abstractmethod
123     def write(self, event: Dict[str, Any]) -> None:
124         """
125         Handle a general event within the output plugin
126         """
127         pass
128 
129     def emit(self, event: dict) -> None:
130         """
131         This is the main emit() hook that gets called by the the Twisted logging
132 
133         To make this work with Cowrie, the event dictionary needs the following keys:
134         - 'eventid'
135         - 'sessionno' or 'session'
136         - 'message' or 'format'
137         """
138         sessionno: str
139         ev: dict
140 
141         # Ignore stdout and stderr in output plugins
142         if "printed" in event:
143             return
144 
145         # Ignore anything without eventid
146         if "eventid" not in event:
147             return
148 
149         # Ignore anything without session information
150         if (
151             "sessionno" not in event
152             and "session" not in event
153             and "system" not in event
154         ):
155             return
156 
157         # Ignore anything without message
158         if "message" not in event and "format" not in event:
159             return
160 
161         ev: Dict[str, any] = event  # type: ignore
162         ev["sensor"] = self.sensor
163 
164         if "isError" in ev:
165             del ev["isError"]
166 
167         # Add ISO timestamp and sensor data
168         if "time" not in ev:
169             ev["time"] = time.time()
170         ev["timestamp"] = formatTime(ev["time"], timeFormat=self.timeFormat)
171 
172         if "format" in ev and ("message" not in ev or ev["message"] == ()):
173             try:
174                 ev["message"] = ev["format"] % ev
175                 del ev["format"]
176             except Exception:
177                 pass
178 
179         # Explicit sessionno (from logDispatch) overrides from 'system'
180         if "sessionno" in ev:
181             sessionno = ev["sessionno"]
182             del ev["sessionno"]
183         # Maybe it's passed explicitly
184         elif "session" in ev:
185             # reverse engineer sessionno
186             try:
187                 sessionno = next(
188                     key
189                     for key, value in self.sessions.items()
190                     if value == ev["session"]
191                 )
192             except StopIteration:
193                 return
194         # Extract session id from the twisted log prefix
195         elif "system" in ev:
196             sessionno = "0"
197             telnetmatch = self.telnetRegex.match(ev["system"])
198             if telnetmatch:
199                 sessionno = "T{}".format(telnetmatch.groups()[0])
200             else:
201                 sshmatch = self.sshRegex.match(ev["system"])
202                 if sshmatch:
203                     sessionno = "S{}".format(sshmatch.groups()[0])
204             if sessionno == "0":
205                 return
206 
207         if sessionno in self.ips:
208             ev["src_ip"] = self.ips[sessionno]
209 
210         # Connection event is special. adds to session list
211         if ev["eventid"] == "cowrie.session.connect":
212             self.sessions[sessionno] = ev["session"]
213             self.ips[sessionno] = ev["src_ip"]
214         else:
215             ev["session"] = self.sessions[sessionno]
216 
217         self.write(ev)
218 
219         # Disconnect is special, remove cached data
220         if ev["eventid"] == "cowrie.session.closed":
221             del self.sessions[sessionno]
222             del self.ips[sessionno]
223
```
--- 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/src/cowrie/core/output.py b/src/cowrie/core/output.py
--- a/src/cowrie/core/output.py
+++ b/src/cowrie/core/output.py
@@ -158,7 +158,7 @@
         if "message" not in event and "format" not in event:
             return
 
-        ev: Dict[str, any] = event  # type: ignore
+        ev: Dict[str, any] = event.copy()  # type: ignore
         ev["sensor"] = self.sensor
 
         if "isError" in ev:

{"golden_diff": "diff --git a/src/cowrie/core/output.py b/src/cowrie/core/output.py\n--- a/src/cowrie/core/output.py\n+++ b/src/cowrie/core/output.py\n@@ -158,7 +158,7 @@\n         if \"message\" not in event and \"format\" not in event:\n             return\n \n-        ev: Dict[str, any] = event  # type: ignore\n+        ev: Dict[str, any] = event.copy()  # type: ignore\n         ev[\"sensor\"] = self.sensor\n \n         if \"isError\" in ev:\n", "issue": "builtins.KeyError: 'log_time' Python error\n**Describe the bug**\r\nCowrie won't log properly, due that output plugins are not working -> output_splunk\r\nFollowing error occurs:\r\n```\r\n2021-04-28T07:00:17.796991Z [twisted.logger._observer#critical] Temporarily disabling observer LegacyLogObserverWrapper(<bound method Output.emit of <cowrie.output.virustotal.Output object at 0x7f3a13c9c550>>) due to exception: [Failure instance: Traceback: <class 'KeyError'>: 'log_time'\r\n        /home/cowrie/cowrie/src/cowrie/ssh/transport.py:246:connectionLost\r\n        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/threadable.py:51:sync\r\n        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/log.py:281:msg\r\n        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py:147:publishToNewObserver\r\n        --- <exception caught here> ---\r\n        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_observer.py:82:__call__\r\n        /home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py:55:__call__\r\n        ]\r\n        Traceback (most recent call last):\r\n          File \"/home/cowrie/cowrie/src/cowrie/ssh/transport.py\", line 246, in connectionLost\r\n            log.msg(\r\n          File \"/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/threadable.py\", line 51, in sync\r\n            return function(self, *args, **kwargs)\r\n          File \"/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/python/log.py\", line 281, in msg\r\n            _publishNew(self._publishPublisher, actualEventDict, textFromEventDict)\r\n          File \"/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py\", line 147, in publishToNewObserver\r\n            observer(eventDict)\r\n        --- <exception caught here> ---\r\n          File \"/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_observer.py\", line 82, in __call__\r\n            observer(event)\r\n          File \"/home/cowrie/cowrie/cowrie-env/lib/python3.8/site-packages/twisted/logger/_legacy.py\", line 55, in __call__\r\n            event[\"time\"] = event[\"log_time\"]\r\n        builtins.KeyError: 'log_time'\r\n```\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. git clone cowrie\r\n2. setup venv\r\n3. setup cowrie.cfg\r\n4. include splunk output\r\n5. run cowrie\r\n6. run honeypot session\r\n\r\n**Expected behavior**\r\nCowrie should properly log.\r\n\r\n**Server (please complete the following information):**\r\n - OS: `Linux cowrie-1 5.4.103-1-pve #1 SMP PVE 5.4.103-1 (Sun, 07 Mar 2021 15:55:09 +0100) x86_64 x86_64 x86_64 GNU/Linux`\r\n - Python: Python 3.8.6\r\n\n", "before_files": [{"content": "# Copyright (c) 2015 Michel Oosterhof <michel@oosterhof.net>\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#\n# 1. Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n# 2. Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n# 3. The names of the author(s) may not be used to endorse or promote\n#    products derived from this software without specific prior written\n#    permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR\n# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n# SUCH DAMAGE.\n\n\nimport abc\nimport re\nimport socket\nimport time\nfrom os import environ\nfrom typing import Any, Dict, Pattern\n\nfrom twisted.internet import reactor\nfrom twisted.logger import formatTime\n\nfrom cowrie.core.config import CowrieConfig\n\n# Events:\n#  cowrie.client.fingerprint\n#  cowrie.client.size\n#  cowrie.client.var\n#  cowrie.client.version\n#  cowrie.command.input\n#  cowrie.command.failed\n#  cowrie.command.success (deprecated)\n#  cowrie.direct-tcpip.data\n#  cowrie.direct-tcpip.request\n#  cowrie.log.closed\n#  cowrie.login.failed\n#  cowrie.login.success\n#  cowrie.session.closed\n#  cowrie.session.connect\n#  cowrie.session.file_download\n#  cowrie.session.file_upload\n\n\"\"\"\nThe time is available in two formats in each event, as key 'time'\nin epoch format and in key 'timestamp' as a ISO compliant string\nin UTC.\n\"\"\"\n\n\nclass Output(metaclass=abc.ABCMeta):\n    \"\"\"\n    This is the abstract base class intended to be inherited by\n    cowrie output plugins. Plugins require the mandatory\n    methods: stop, start and write\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.sessions: Dict[str, str] = {}\n        self.ips: Dict[str, str] = {}\n\n        # Need these for each individual transport, or else the session numbers overlap\n        self.sshRegex: Pattern[str] = re.compile(\".*SSHTransport,([0-9]+),[0-9a-f:.]+$\")\n        self.telnetRegex: Pattern[str] = re.compile(\n            \".*TelnetTransport,([0-9]+),[0-9a-f:.]+$\"\n        )\n        self.sensor: str = CowrieConfig.get(\n            \"honeypot\", \"sensor_name\", fallback=socket.gethostname()\n        )\n        self.timeFormat: str\n\n        # use Z for UTC (Zulu) time, it's shorter.\n        if \"TZ\" in environ and environ[\"TZ\"] == \"UTC\":\n            self.timeFormat = \"%Y-%m-%dT%H:%M:%S.%fZ\"\n        else:\n            self.timeFormat = \"%Y-%m-%dT%H:%M:%S.%f%z\"\n\n        # Event trigger so that stop() is called by the reactor when stopping\n        reactor.addSystemEventTrigger(\"before\", \"shutdown\", self.stop)  # type: ignore\n\n        self.start()\n\n    def logDispatch(self, **kw: str) -> None:\n        \"\"\"\n        Use logDispatch when the HoneypotTransport prefix is not available.\n        Here you can explicitly set the sessionIds to tie the sessions together\n        \"\"\"\n        ev = kw\n        # ev[\"message\"] = msg\n        self.emit(ev)\n\n    @abc.abstractmethod\n    def start(self) -> None:\n        \"\"\"\n        Abstract method to initialize output plugin\n        \"\"\"\n        pass\n\n    @abc.abstractmethod\n    def stop(self) -> None:\n        \"\"\"\n        Abstract method to shut down output plugin\n        \"\"\"\n        pass\n\n    @abc.abstractmethod\n    def write(self, event: Dict[str, Any]) -> None:\n        \"\"\"\n        Handle a general event within the output plugin\n        \"\"\"\n        pass\n\n    def emit(self, event: dict) -> None:\n        \"\"\"\n        This is the main emit() hook that gets called by the the Twisted logging\n\n        To make this work with Cowrie, the event dictionary needs the following keys:\n        - 'eventid'\n        - 'sessionno' or 'session'\n        - 'message' or 'format'\n        \"\"\"\n        sessionno: str\n        ev: dict\n\n        # Ignore stdout and stderr in output plugins\n        if \"printed\" in event:\n            return\n\n        # Ignore anything without eventid\n        if \"eventid\" not in event:\n            return\n\n        # Ignore anything without session information\n        if (\n            \"sessionno\" not in event\n            and \"session\" not in event\n            and \"system\" not in event\n        ):\n            return\n\n        # Ignore anything without message\n        if \"message\" not in event and \"format\" not in event:\n            return\n\n        ev: Dict[str, any] = event  # type: ignore\n        ev[\"sensor\"] = self.sensor\n\n        if \"isError\" in ev:\n            del ev[\"isError\"]\n\n        # Add ISO timestamp and sensor data\n        if \"time\" not in ev:\n            ev[\"time\"] = time.time()\n        ev[\"timestamp\"] = formatTime(ev[\"time\"], timeFormat=self.timeFormat)\n\n        if \"format\" in ev and (\"message\" not in ev or ev[\"message\"] == ()):\n            try:\n                ev[\"message\"] = ev[\"format\"] % ev\n                del ev[\"format\"]\n            except Exception:\n                pass\n\n        # Explicit sessionno (from logDispatch) overrides from 'system'\n        if \"sessionno\" in ev:\n            sessionno = ev[\"sessionno\"]\n            del ev[\"sessionno\"]\n        # Maybe it's passed explicitly\n        elif \"session\" in ev:\n            # reverse engineer sessionno\n            try:\n                sessionno = next(\n                    key\n                    for key, value in self.sessions.items()\n                    if value == ev[\"session\"]\n                )\n            except StopIteration:\n                return\n        # Extract session id from the twisted log prefix\n        elif \"system\" in ev:\n            sessionno = \"0\"\n            telnetmatch = self.telnetRegex.match(ev[\"system\"])\n            if telnetmatch:\n                sessionno = \"T{}\".format(telnetmatch.groups()[0])\n            else:\n                sshmatch = self.sshRegex.match(ev[\"system\"])\n                if sshmatch:\n                    sessionno = \"S{}\".format(sshmatch.groups()[0])\n            if sessionno == \"0\":\n                return\n\n        if sessionno in self.ips:\n            ev[\"src_ip\"] = self.ips[sessionno]\n\n        # Connection event is special. adds to session list\n        if ev[\"eventid\"] == \"cowrie.session.connect\":\n            self.sessions[sessionno] = ev[\"session\"]\n            self.ips[sessionno] = ev[\"src_ip\"]\n        else:\n            ev[\"session\"] = self.sessions[sessionno]\n\n        self.write(ev)\n\n        # Disconnect is special, remove cached data\n        if ev[\"eventid\"] == \"cowrie.session.closed\":\n            del self.sessions[sessionno]\n            del self.ips[sessionno]\n", "path": "src/cowrie/core/output.py"}], "after_files": [{"content": "# Copyright (c) 2015 Michel Oosterhof <michel@oosterhof.net>\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#\n# 1. Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n# 2. Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n# 3. The names of the author(s) may not be used to endorse or promote\n#    products derived from this software without specific prior written\n#    permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR\n# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n# SUCH DAMAGE.\n\n\nimport abc\nimport re\nimport socket\nimport time\nfrom os import environ\nfrom typing import Any, Dict, Pattern\n\nfrom twisted.internet import reactor\nfrom twisted.logger import formatTime\n\nfrom cowrie.core.config import CowrieConfig\n\n# Events:\n#  cowrie.client.fingerprint\n#  cowrie.client.size\n#  cowrie.client.var\n#  cowrie.client.version\n#  cowrie.command.input\n#  cowrie.command.failed\n#  cowrie.command.success (deprecated)\n#  cowrie.direct-tcpip.data\n#  cowrie.direct-tcpip.request\n#  cowrie.log.closed\n#  cowrie.login.failed\n#  cowrie.login.success\n#  cowrie.session.closed\n#  cowrie.session.connect\n#  cowrie.session.file_download\n#  cowrie.session.file_upload\n\n\"\"\"\nThe time is available in two formats in each event, as key 'time'\nin epoch format and in key 'timestamp' as a ISO compliant string\nin UTC.\n\"\"\"\n\n\nclass Output(metaclass=abc.ABCMeta):\n    \"\"\"\n    This is the abstract base class intended to be inherited by\n    cowrie output plugins. Plugins require the mandatory\n    methods: stop, start and write\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.sessions: Dict[str, str] = {}\n        self.ips: Dict[str, str] = {}\n\n        # Need these for each individual transport, or else the session numbers overlap\n        self.sshRegex: Pattern[str] = re.compile(\".*SSHTransport,([0-9]+),[0-9a-f:.]+$\")\n        self.telnetRegex: Pattern[str] = re.compile(\n            \".*TelnetTransport,([0-9]+),[0-9a-f:.]+$\"\n        )\n        self.sensor: str = CowrieConfig.get(\n            \"honeypot\", \"sensor_name\", fallback=socket.gethostname()\n        )\n        self.timeFormat: str\n\n        # use Z for UTC (Zulu) time, it's shorter.\n        if \"TZ\" in environ and environ[\"TZ\"] == \"UTC\":\n            self.timeFormat = \"%Y-%m-%dT%H:%M:%S.%fZ\"\n        else:\n            self.timeFormat = \"%Y-%m-%dT%H:%M:%S.%f%z\"\n\n        # Event trigger so that stop() is called by the reactor when stopping\n        reactor.addSystemEventTrigger(\"before\", \"shutdown\", self.stop)  # type: ignore\n\n        self.start()\n\n    def logDispatch(self, **kw: str) -> None:\n        \"\"\"\n        Use logDispatch when the HoneypotTransport prefix is not available.\n        Here you can explicitly set the sessionIds to tie the sessions together\n        \"\"\"\n        ev = kw\n        # ev[\"message\"] = msg\n        self.emit(ev)\n\n    @abc.abstractmethod\n    def start(self) -> None:\n        \"\"\"\n        Abstract method to initialize output plugin\n        \"\"\"\n        pass\n\n    @abc.abstractmethod\n    def stop(self) -> None:\n        \"\"\"\n        Abstract method to shut down output plugin\n        \"\"\"\n        pass\n\n    @abc.abstractmethod\n    def write(self, event: Dict[str, Any]) -> None:\n        \"\"\"\n        Handle a general event within the output plugin\n        \"\"\"\n        pass\n\n    def emit(self, event: dict) -> None:\n        \"\"\"\n        This is the main emit() hook that gets called by the the Twisted logging\n\n        To make this work with Cowrie, the event dictionary needs the following keys:\n        - 'eventid'\n        - 'sessionno' or 'session'\n        - 'message' or 'format'\n        \"\"\"\n        sessionno: str\n        ev: dict\n\n        # Ignore stdout and stderr in output plugins\n        if \"printed\" in event:\n            return\n\n        # Ignore anything without eventid\n        if \"eventid\" not in event:\n            return\n\n        # Ignore anything without session information\n        if (\n            \"sessionno\" not in event\n            and \"session\" not in event\n            and \"system\" not in event\n        ):\n            return\n\n        # Ignore anything without message\n        if \"message\" not in event and \"format\" not in event:\n            return\n\n        ev: Dict[str, any] = event.copy()  # type: ignore\n        ev[\"sensor\"] = self.sensor\n\n        if \"isError\" in ev:\n            del ev[\"isError\"]\n\n        # Add ISO timestamp and sensor data\n        if \"time\" not in ev:\n            ev[\"time\"] = time.time()\n        ev[\"timestamp\"] = formatTime(ev[\"time\"], timeFormat=self.timeFormat)\n\n        if \"format\" in ev and (\"message\" not in ev or ev[\"message\"] == ()):\n            try:\n                ev[\"message\"] = ev[\"format\"] % ev\n                del ev[\"format\"]\n            except Exception:\n                pass\n\n        # Explicit sessionno (from logDispatch) overrides from 'system'\n        if \"sessionno\" in ev:\n            sessionno = ev[\"sessionno\"]\n            del ev[\"sessionno\"]\n        # Maybe it's passed explicitly\n        elif \"session\" in ev:\n            # reverse engineer sessionno\n            try:\n                sessionno = next(\n                    key\n                    for key, value in self.sessions.items()\n                    if value == ev[\"session\"]\n                )\n            except StopIteration:\n                return\n        # Extract session id from the twisted log prefix\n        elif \"system\" in ev:\n            sessionno = \"0\"\n            telnetmatch = self.telnetRegex.match(ev[\"system\"])\n            if telnetmatch:\n                sessionno = \"T{}\".format(telnetmatch.groups()[0])\n            else:\n                sshmatch = self.sshRegex.match(ev[\"system\"])\n                if sshmatch:\n                    sessionno = \"S{}\".format(sshmatch.groups()[0])\n            if sessionno == \"0\":\n                return\n\n        if sessionno in self.ips:\n            ev[\"src_ip\"] = self.ips[sessionno]\n\n        # Connection event is special. adds to session list\n        if ev[\"eventid\"] == \"cowrie.session.connect\":\n            self.sessions[sessionno] = ev[\"session\"]\n            self.ips[sessionno] = ev[\"src_ip\"]\n        else:\n            ev[\"session\"] = self.sessions[sessionno]\n\n        self.write(ev)\n\n        # Disconnect is special, remove cached data\n        if ev[\"eventid\"] == \"cowrie.session.closed\":\n            del self.sessions[sessionno]\n            del self.ips[sessionno]\n", "path": "src/cowrie/core/output.py"}]}