Esempio n. 1
0
 def dataReceived(self, data):
     """
     Called when the attacker sends data.
     :param data: The received data
     """
     log.request(Config.listenServiceName,
                 self.transport.getPeer().host,
                 self.transport.getHost().port, data.decode())
Esempio n. 2
0
 def execCommand(self, pp, cmd):
     """
     Gets called when the client requests command execution (eg. with a pipe)
     We don't support command execution but we still want to log the command (e.g. for forensics)
     :param pp: the transport protocol
     :param cmd: the command the client wants executed
     """
     ip = pp.session.avatar.conn.transport.transport.client[0]
     port = pp.session.avatar.conn.transport.transport.server._realPortNumber
     log.request("SSH", ip, port, "execCommand " + cmd.decode(), pp.session.avatar.username.decode())
     pp.session.conn.transport.sendDisconnect(7, b"Command Execution is not supported.")
Esempio n. 3
0
    def execCommand(self, pp, cmd):
        """
        Gets called when the client requests command execution (eg. with a pipe)
        We don't support command execution but we still want to log the command (e.g. for forensics)
        :param pp: the transport protocol
        :param cmd: the command the client wants executed
        """

        remote = pp.session.conn.transport.transport.client
        log.request("SSH", remote[0], remote[1], self.local_ip, self.local_port,
                    "<exec '{}'>".format(cmd.decode()), pp.session.avatar.username)
        pp.session.conn.transport.sendDisconnect(7, b"Command Execution is not supported.")
Esempio n. 4
0
    def openShell(self, transport):
        """
        wire the protocol to the transport channel
        :param transport:
        """
        serverProtocol = insults.insults.ServerProtocol(
            SSHProtocol)  # neues ServerProtocol mit SSHProtocol als Terminal
        serverProtocol.makeConnection(transport)
        transport.makeConnection(session.wrapProtocol(serverProtocol))

        remote = transport.session.avatar.conn.transport.transport.client
        log.request("SSH", remote[0], remote[1], self.local_ip, self.local_port,
                    "<open shell>", transport.session.avatar.username)
Esempio n. 5
0
    def openShell(self, transport):
        """
        wire the protocol to the transport channel
        :param transport:
        """
        serverProtocol = insults.insults.ServerProtocol(
            SSHProtocol)  # neues ServerProtocol mit SSHProtocol als Terminal
        serverProtocol.makeConnection(transport)
        transport.makeConnection(session.wrapProtocol(serverProtocol))

        ip = transport.session.avatar.conn.transport.transport.client[0]
        port = transport.session.avatar.conn.transport.transport.server._realPortNumber
        log.request("SSH", ip, port, "Request shell", transport.session.avatar.username.decode())
Esempio n. 6
0
    def dataReceived(self, rawData):
        self.resetTimeout()
        # TODO: Verifizieren, dass möglichst alle 503-Fälle ("Bad sequence of commands") abgedeckt sind
        if (rawData.startswith(b'\xff') or rawData.startswith(b'\x04')):
            # ignore Ctrl+C/D/Z etc.
            pass
        else:
            # binary data like b"\xff\x..." causes trouble when decoding (simply ignore it)
            try:
                # decode raw data
                data = rawData.decode("UTF-8")
            except Exception as e:  # noqa
                data = ""

            # get first line
            line = data[:data.find("\r\n")]
            # restrict maximum input lenght (doesn't affect mail transmission)
            line = line[:4094]
            if not self.state["data"] and not self.state[
                    "authLOGIN"] and not self.state["authLOGINuser"]:
                log.request(self.name, self.peerOfAttacker, Config.smtp.port,
                            line, self.username)

            # moved this block to the top:
            # if self.state["data"] gets verified after commands, a mail body containing these valid commands executes them
            if self.state[
                    "data"]:  # doesn't have to start with anything specific
                self.msg += data
                if ("\r\n.\r\n" in self.msg):
                    self.msg = self.msg[:self.msg.find(
                        "\r\n.\r\n"
                    ) + 2]  # "+2" adds linebreak ("\r\n") to the end of the mail body
                    self.state["data"] = False
                    self.state["msg"] = True
                    response = "250 OK\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username, "250 OK")
                    self.transport.write(response.encode("UTF-8"))

            elif re.match('^RSET( .*)?$', line, re.IGNORECASE):
                # TODO: store data received from attacker somewhere else
                self.state = {
                    "connected": False,
                    "hello": False,
                    "auth": False,
                    "mailfrom": False,
                    "mailto": False,
                    "data": False,
                    "msg": False,
                    "auth": False,
                    "authLOGIN": False,
                    "authLOGINuser": False
                }
                self.msg = ""
                self.mailFrom = ""
                self.mailTo = ""
                self.username = ""
                self.password = ""
                response = "250 OK\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "250 OK")
                self.transport.write(response.encode("UTF-8"))

            elif re.match('^NOOP( .*)?$', line, re.IGNORECASE):
                response = "250 OK\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "250 OK")
                self.transport.write(response.encode("UTF-8"))

            elif re.match('^QUIT( .*)?$', line, re.IGNORECASE):
                # make sure QUIT doesn't have parameters (unimportant for correct functioning but good for concealment)
                if (line == "QUIT"):
                    self.state["connected"] = False
                    response = "221 Service closing transmission channel\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "221 OK (closing)")
                    self.transport.write(response.encode("UTF-8"))
                    # close connection
                    self.transport.loseConnection()
                else:
                    response = "501 Syntax error in parameters or arguments\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "501 (syntax)")
                    self.transport.write(response.encode("UTF-8"))

            elif self.state[
                    "authLOGIN"]:  # doesn't start with anything recognisable
                # state after client chose LOGIN authentication method
                # TODO: missing protection against malformed inputs
                self.username = base64.b64decode(line).decode("utf-8")
                self.state["authLOGIN"] = False
                self.state["authLOGINuser"] = True
                response = "334 UGFzc3dvcmQ6\r\n"  # "334 Password:"******"UTF-8"))

            elif self.state[
                    "authLOGINuser"]:  # doesn't start with anything recognisable
                # state after client sent username for LOGIN authentication method
                # TODO: missing protection against malformed inputs
                self.password = base64.b64decode(line).decode("utf-8")
                self.state["authLOGINuser"] = False
                # TODO: implement honeytokendb check
                if False:
                    log.login(self.name, self.peerOfAttacker, Config.smtp.port,
                              True, self.username, self.password, "")
                    self.state["auth"] = True
                    self.setTimeout(self.timeoutPostAuth)
                    response = "235 OK\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username, "235 OK")
                else:
                    log.login(self.name, self.peerOfAttacker, Config.smtp.port,
                              False, self.username, self.password, "")
                    response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "535 (credentials)")
                self.transport.write(response.encode("UTF-8"))

            elif re.match('^HELP( .*)?$', line, re.IGNORECASE):
                # don't react to arguments (exactly like e.g. smtp.outlook.com)
                response = "214 This server supports the following commands:\r\n214 HELO EHLO RCPT DATA MAIL QUIT HELP AUTH VRFY RSET NOOP\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "214 OK")
                self.transport.write(response.encode("UTF-8"))

            elif re.match('^(HELO|EHLO)( .*)?$', line, re.IGNORECASE):
                self.state["hello"] = True
                if (self.AuthMethods != ""):
                    response = "250 AUTH" + self.AuthMethods + "\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username, "250 OK")
                else:
                    # authentication completed without authentication
                    self.state["auth"] = True
                    self.setTimeout(self.timeoutPostAuth)
                    response = "250 OK\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username, "250 OK")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^AUTH( .*)?$", line, re.IGNORECASE):
                if (line == "AUTH"):
                    response = "501 Syntax error in parameters or arguments\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "501 (syntax")
                else:
                    if self.state["hello"]:
                        if re.match("^AUTH PLAIN \S*$", line, re.IGNORECASE):
                            if ("PLAIN" in self.AuthMethods):
                                # b64decode everything from 12th char
                                # TODO: missing protection against malformed inputs
                                credentials = base64.b64decode(
                                    line[11:]).decode("utf-8")[1:]
                                self.username = credentials[:credentials.
                                                            find("\x00")]
                                self.password = credentials[credentials.
                                                            find("\x00") + 1:]
                                # TODO: implement honeytokendb check
                                if False:
                                    self.state["auth"] = True
                                    self.setTimeout(self.timeoutPostAuth)
                                    response = "235 OK\r\n"
                                    log.response(self.name,
                                                 self.peerOfAttacker,
                                                 Config.smtp.port, "",
                                                 self.username, "235 OK")
                                else:
                                    response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                                    log.response(self.name,
                                                 self.peerOfAttacker,
                                                 Config.smtp.port, "",
                                                 self.username,
                                                 "535 (credentials)")
                            else:
                                response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "535 (unsupported)")
                        elif line == "AUTH LOGIN":
                            if ("LOGIN" in self.AuthMethods):
                                self.state["authLOGIN"] = True
                                response = "334 VXNlcm5hbWU6\r\n"  # "334 Username:"******"535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "535 (unsupported)")
                        elif line == "AUTH CRAM-MD5":
                            # TODO: implement CRAM-MD5 or disable this code path
                            if ("CRAM-MD5" in self.AuthMethods):
                                print("CRAM-MD5 not yet implemented")
                                response = "504 Command parameter not implemented\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "504 (not implemented)")
                                self.transport.write(response.encode("UTF-8"))
                                self.transport.loseConnection()
                            else:
                                response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "535 (unsupported)")
                        elif line == "AUTH SCRAM-SHA-1":
                            # TODO: implement SCRAM-SHA-1 or disable this code path
                            if "SCRAM-SHA-1" in self.AuthMethods:
                                print("SCRAM-SHA-1 not yet implemented")
                                response = "504 Command parameter not implemented\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "504 (not implemented)")
                                self.transport.write(response.encode("UTF-8"))
                                self.transport.loseConnection()
                            else:
                                response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                                log.response(self.name, self.peerOfAttacker,
                                             Config.smtp.port, "",
                                             self.username,
                                             "535 (unsupported)")
                        else:
                            response = "501 Syntax error in parameters or arguments\r\n"
                            log.response(self.name, self.peerOfAttacker,
                                         Config.smtp.port, "", self.username,
                                         "501 (syntax)")
                    else:
                        response = "503 Bad sequence of commands\r\n"  # "Send hello first"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "503 (sequence)")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^MAIL FROM:(.*)$", line, re.IGNORECASE):
                # tolerate additional whitespace
                if re.match(
                        "^MAIL FROM:[ ]?<(.*)>$", line, re.IGNORECASE
                ):  # use "<(.*@.*\..*?)>" to check basic adress syntax
                    if self.state["auth"]:
                        self.state["mailfrom"] = True
                        self.mailFrom = re.match(
                            "^MAIL FROM:[ ]?<(.*)>$", line, re.IGNORECASE
                        ).groups(
                        )[0]  # use "<(.*@.*\..*?)>" to check basic adress syntax
                        response = "250 OK\r\n"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "250 OK")
                    elif self.state["hello"]:
                        response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "535 (credentials)")
                    else:
                        response = "503 Bad sequence of commands\r\n"  # "Send hello first"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "503 (sequence)")
                else:
                    # no email adress given etc.
                    response = "501 Syntax error in parameters or arguments\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "501 (syntax)")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^RCPT TO:(.*)$", line, re.IGNORECASE):
                # tolerate additional whitespace
                if re.match(
                        "^RCPT TO:[ ]?<(.*)>$", line, re.IGNORECASE
                ):  # use "<(.*@.*\..*?)>" to check basic adress syntax
                    if self.state["mailfrom"]:
                        self.state["mailto"] = True
                        self.mailTo = re.match(
                            "^RCPT TO:[ ]?<(.*)>$", line, re.IGNORECASE
                        ).groups(
                        )[0]  # use "<(.*@.*\..*?)>" to check basic adress syntax # use "<(.*@.*\..*?)>" to check basic adress syntax
                        response = "250 OK\r\n"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "250 OK")
                    elif self.state["hello"]:
                        response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "535 (credentials)")
                    else:
                        response = "503 Bad sequence of commands\r\n"  # "Send hello first"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "503 (sequence)")
                else:
                    # no email adress given etc.
                    response = "501 Syntax error in parameters or arguments\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "501 (syntax)")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^DATA( .*)?$", line, re.IGNORECASE):
                if line == "DATA":
                    if self.state["mailto"]:
                        self.state["data"] = True
                        response = "354 Start mail input\r\n"
                    elif self.state["auth"]:
                        response = "503 Bad sequence of commands\r\n"  # "Send hello first"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "503 (sequence)")
                    elif self.state["hello"]:
                        response = "535 SMTP Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "535 (credentials)")
                    else:
                        response = "503 Bad sequence of commands\r\n"  # "Send hello first"
                        log.response(self.name, self.peerOfAttacker,
                                     Config.smtp.port, "", self.username,
                                     "503 (sequence)")
                else:
                    response = "501 Syntax error in parameters or arguments\r\n"
                    log.response(self.name, self.peerOfAttacker,
                                 Config.smtp.port, "", self.username,
                                 "501 (syntax)")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^VRFY( .*)?$", line, re.IGNORECASE):
                response = "252 Cannot VRFY user\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "252 OK")
                self.transport.write(response.encode("UTF-8"))

            elif re.match("^SIZE( .*)?$", line, re.IGNORECASE):
                response = "502 Command not implemented\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "502 (not implemented)")
                self.transport.write(response.encode("UTF-8"))

            else:
                response = "500 Unrecognized command \'" + line + "\'\r\n"
                log.response(self.name, self.peerOfAttacker, Config.smtp.port,
                             "", self.username, "500 (command)")
                self.transport.write(response.encode("UTF-8"))
Esempio n. 7
0
    def dataReceived(self, data):

        data = data.decode('utf-8')

        self.requestType = data.split(' ', 1)[0]

        if self.requestType == "GET":
            self.page = data[data.find("GET ") + 4:data.find(" HTTP/1.1")]
        elif self.requestType == "POST":
            self.page = data[data.find("POST ") + 5:data.find(" HTTP/1.1")]

        pageNotFound = True

        for serviceLink in HTTPService.supportedSites:
            if self.page == serviceLink:
                pageNotFound = False
                self.attackingSite = str(
                    self.path / HTTPService.html_dictionary[serviceLink][0])
                if len(HTTPService.html_dictionary[serviceLink]) > 1:
                    self.loginSuccessfulSite = str(
                        self.path /
                        HTTPService.html_dictionary[serviceLink][1])
                else:
                    self.loginSuccessfulSite = str(
                        self.path / HTTPService.html_dictionary['404'][0])
                self.short = serviceLink
                break

        if pageNotFound:
            self.notFoundSite = str(self.path /
                                    HTTPService.html_dictionary['404'][0])

        # Handle GETs
        if self.requestType == "GET" and (
                '.gif' in self.page or '.png' in self.page
                or '/dashboard_files/' in self.page or '.jpg' in self.page
                or '.woff' in self.page or '.ttf' in self.page
                or '.svg' in self.page):
            message = HTTPService.notFoundStatus + "\n"
            for k in HTTPService.responseHeadersNotFound.keys():
                message = message + k + ": " + HTTPService.responseHeadersNotFound[
                    k] + "\n"
            self.transport.write(message.encode('UTF-8'))
            self.transport.loseConnection()

        elif self.requestType == "GET" and self.page in HTTPService.supportedSites:

            log.request("HTTP", self.peerOfAttacker, HTTPService.port,
                        self.page, "", "GET")

            message = HTTPService.okStatus + "\n"
            for k in HTTPService.responseHeadersOkStatus.keys():
                message = message + k + ": " + HTTPService.responseHeadersOkStatus[
                    k] + "\n"

            with open(self.attackingSite, encoding='utf8') as file:
                message = message + "\n" + file.read()

            self.transport.write(message.encode('UTF-8'))
            if self.page in HTTPService.supportedSites:
                log.response("HTTP", self.peerOfAttacker, HTTPService.port,
                             self.page, "", "200 OK")

            self.transport.loseConnection()

        # Handle POSTs
        elif (self.requestType == "POST"
              and self.page in HTTPService.supportedSites):
            self.page = data[data.find("POST ") + 5:data.find(" HTTP/1.1")]
            login_string = ""
            password_string = ""
            login_index = data.find("log=") + 4
            if login_index == 3:
                login_string = "fritz.box"
            else:
                login_string = data[login_index:data.find("&")]
            password_index = data.find("pwd=") + 4
            if data[password_index:data.find("&")] != -1:
                password_string = data[password_index:len(data)]
            else:
                password_string = data[password_index:data.find("&")]

            log.request("HTTP", self.peerOfAttacker, HTTPService.port,
                        self.page, login_string, "POST")
            result = HTTPService.htdb.requestAvatarId(
                HTTPAvatar(login_string, password_string))
            if isinstance(result, Deferred):
                if isinstance(result.result, failure.Failure):  # Failure
                    result.addErrback(self.errorBack)
                    log.response("HTTP", self.peerOfAttacker, HTTPService.port,
                                 "", login_string, "403 FORBIDDEN")
                    log.login(
                        "HTTP", self.peerOfAttacker, HTTPService.port, False,
                        login_string, password_string,
                        str(
                            HTTPService.htdb.getActual(login_string,
                                                       password_string)))
                else:  # Success
                    message = HTTPService.okStatus + "\n"
                    for k in HTTPService.responseHeadersOkStatus.keys():
                        message = message + k + ": " + HTTPService.responseHeadersOkStatus[
                            k] + "\n"

                    with open(self.loginSuccessfulSite,
                              encoding='utf8') as file:
                        message = message + "\n" + file.read()

                    self.transport.write(message.encode('UTF-8'))
                    self.page = "wp-admin_content.html"
                    log.response("HTTP", self.peerOfAttacker, HTTPService.port,
                                 self.page, login_string, "200 OK")
                    log.login(
                        "HTTP", self.peerOfAttacker, HTTPService.port, True,
                        login_string, password_string,
                        str(
                            HTTPService.htdb.getActual(login_string,
                                                       password_string)))
                    self.transport.loseConnection()
        else:
            message = HTTPService.notFoundStatus + "\n"
            for k in HTTPService.responseHeadersNotFound.keys():
                message = message + k + ": " + HTTPService.responseHeadersNotFound[
                    k] + "\n"

            with open(self.notFoundSite, encoding='utf8') as file:
                message = message + "\n" + file.read()

            self.transport.write(message.encode('UTF-8'))
            log.request("HTTP", self.peerOfAttacker, HTTPService.port,
                        self.page, "", "GET")
            self.page = "404_login.html"
            log.response("HTTP", self.peerOfAttacker, HTTPService.port,
                         self.page, "", "404 Not Found")
            self.transport.loseConnection()
Esempio n. 8
0
    def dataReceived(self, rawData):
        self.resetTimeout()

        local = self.transport.getHost()
        remote = self.transport.getPeer()

        # problem: at least Thunderbird pushes sometimes two commands so quickly they are received by twisted as one line
        # solution: put received data in buffer and fetch single lines from there

        # TODO: Verifizieren, dass möglichst alle 503-Fälle ("Bad sequence of commands") abgedeckt sind
        if (rawData.startswith(b'\xff') or rawData.startswith(b'\x04')):
            # ignore Ctrl+C/D/Z etc.
            pass
        else:
            # binary data like b"\xff\x..." causes trouble when decoding (simply ignore it)
            try:
                # decode raw data and add it to buffer
                self.dataBuffer += rawData.decode("UTF-8")
            except Exception as e:  # noqa
                pass

            # get first line from buffer
            if ("\r\n" in self.dataBuffer):
                line = self.dataBuffer[:self.dataBuffer.find("\r\n")]
                self.dataBuffer = self.dataBuffer[self.dataBuffer.find("\r\n"
                                                                       ) + 2:]
            else:
                print(
                    "IMAPService: Warning: Client command didn't end with CRLF!"
                )
                line = self.dataBuffer
                self.dataBuffer = ""

            if (not self.state["appendCleanUp"]
                    and not self.state["appendData"]):
                log.request(self.name, remote.host, remote.port, local.host,
                            local.ip, line, self.username)
            # ignore one (maybe optional) empty line after append command
            if (self.state["appendCleanUp"]):
                if (line != ""):
                    self.dataBuffer = line + "\r\n" + self.dataBuffer
                self.state["appendCleanUp"] = False
            elif (self.state["appendData"]
                  ):  # wait for another part of the mail
                # reunify first line and remaining buffer
                self.dataBuffer = line + "\r\n" + self.dataBuffer
                # buffer is bigger than remaining announced transmission
                if (len(self.dataBuffer) >= self.appendLength):
                    self.appendBuffer += self.dataBuffer[:self.appendLength]
                    self.dataBuffer = self.dataBuffer[self.appendLength:]
                    self.appendLength = 0
                    self.state["appendData"] = False
                    self.state["appendCleanUp"] = True
                    # add uploaded mail to storage
                    mail = self.appendBuffer
                    self.saveEmail(self.appendMailbox, self.appendFlaglist,
                                   mail)
                    response = self.appendTag + " OK APPEND completed\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "OK")
                    self.transport.write(response.encode("UTF-8"))
                else:  # wait for another part of the mail
                    self.appendBuffer += self.dataBuffer
                    self.appendLength -= len(self.dataBuffer)
                    self.dataBuffer = ""
            elif (len(line) > self.maxCommandLength):
                response = "* BAD Command line too long\r\n"
                log.response(self.name, remote.host, remote.port, local.host,
                             local.port, "", self.username, "BAD (too long)")
                self.transport.write(response.encode("UTF-8"))
            elif (len(line) == 0):
                response = "* BAD Empty command line\r\n"
                log.response(self.name, remote.host, remote.port, local.host,
                             local.port, "", self.username, "BAD (too short)")
                self.transport.write(response.encode("UTF-8"))
            elif (re.match("^[A-Za-z\d]+ .+$", line, re.IGNORECASE)):
                # provides tag value for following answers in all cases
                tag = re.match("^(?P<tag>[A-Za-z\d]+) .+$", line,
                               re.IGNORECASE).group("tag")

                # "command-any" (Valid in all states)
                if (re.match("^" + tag + " CAPABILITY$", line, re.IGNORECASE)):
                    response = "* CAPABILITY IMAP4rev1 LOGIN LOGOUT NOOP LIST LSUB UID SELECT\r\n" + tag + " OK CAPABILITY completed\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "OK")
                    self.transport.write(response.encode("UTF-8"))
                elif (re.match("^" + tag + " LOGOUT$", line, re.IGNORECASE)):
                    self.state["connected"] = False
                    response = "* BYE IMAP4rev1 Server logging out\r\n" + tag + " OK LOGOUT completed\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "OK")
                    self.transport.write(response.encode("UTF-8"))
                    # close connection
                    self.transport.loseConnection()
                elif (re.match("^" + tag + " NOOP$", line, re.IGNORECASE)):
                    response = tag + " OK NOOP completed\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "OK")
                    self.transport.write(response.encode("UTF-8"))
                elif (re.match(
                        "^" + tag + " ((CAPABILITY)|(LOGOUT)|(NOOP)) .*$",
                        line, re.IGNORECASE)):
                    # unified error message for several commands
                    response = "* BAD Command argument error\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "BAD (syntax)")
                    self.transport.write(response.encode("UTF-8"))

                # "command-nonauth" (Valid only when in Not Authenticated state)
                elif (re.match(
                        "^" + tag +
                        " LOGIN (?P<userid>\S+) (?P<password>\S+)$", line,
                        re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.NotAuth):
                        arguments = re.match(
                            "^" + tag +
                            " LOGIN (?P<userid>\S+) (?P<password>\S+)$", line,
                            re.IGNORECASE)

                        self.username = self.stripPadding(
                            arguments.group("userid"))
                        self.password = self.stripPadding(
                            arguments.group("password"))

                        if (True):
                            log.login(self.name, remote.host, local.port, True,
                                      self.username, self.password, "")
                            self.setTimeout(self.timeoutPostAuth)
                            self.stateRFC = IMAPState.Auth
                            response = tag + " OK LOGIN completed\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username, "OK")
                        else:
                            log.login(self.name, remote.host, local.port,
                                      False, self.username, self.password, "")
                            response = tag + " NO Invalid credentials\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username, "NO (credentials")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))
                elif (re.match("^" + tag + " LOGIN .*$", line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.NotAuth):
                        response = "* BAD Command argument error\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (syntax)")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence")
                    self.transport.write(response.encode("UTF-8"))

                    # TODO: implement AUTHENTICATE command (provides SASL auth (GSSAPI etc.))

                # "command-auth" (Valid only in Authenticated or Selected state)
                elif (re.match(
                        "^" + tag +
                        " LIST (?P<mailbox>\S+) (?P<listmailbox>\S+)$", line,
                        re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        arguments = re.match(
                            "^" + tag +
                            " LIST (?P<mailbox>\S+) (?P<listmailbox>\S+)$",
                            line, re.IGNORECASE)
                        reference = self.stripPadding(
                            arguments.group("mailbox"))
                        mailboxName = self.stripPadding(
                            arguments.group("listmailbox"))

                        response = ""
                        # doesn't really support the wildcard feature, just distinguishes between "everything" and "one specific mailbox"
                        if (mailboxName in ["*", "%"]):
                            for i in self.mailboxes:
                                response += "* LIST (\\HasNoChildren) \".\" \"" + i + "\"\r\n"
                            response += tag + " OK LIST Completed\r\n"
                        else:
                            if (mailboxName in self.mailboxes):
                                response += "* LIST (\\HasNoChildren) \".\" \"" + mailboxName + "\"\r\n"
                            response += tag + " OK LIST Completed\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "OK")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match(
                        "^" + tag +
                        " LSUB (?P<mailbox>\S+) (?P<listmailbox>\S+)$", line,
                        re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        arguments = re.match(
                            "^" + tag +
                            " LSUB (?P<mailbox>\S+) (?P<listmailbox>\S+)$",
                            line, re.IGNORECASE)
                        # XXX: why is this unused?
                        reference = self.stripPadding(
                            arguments.group("mailbox"))
                        mailboxName = self.stripPadding(
                            arguments.group("listmailbox"))
                        response = ""
                        # doesn't really support the wildcard feature, just distinguishes between "everything" and "one specific mailbox"
                        if (mailboxName in ["*", "%"]):
                            for i in self.mailboxes:
                                response += "* LSUB (\\HasNoChildren) \".\" \"" + i + "\"\r\n"
                            response += tag + " OK LSUB Completed\r\n"
                        else:
                            if (mailboxName in self.mailboxes):
                                response += "* LSUB (\\HasNoChildren) \".\" \"" + mailboxName + "\"\r\n"
                            response += tag + " OK LSUB Completed\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "OK")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match("^" + tag + " SELECT (?P<mailbox>\S+)$", line,
                               re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        arguments = re.match(
                            "^" + tag + " SELECT (?P<mailbox>\S+)$", line,
                            re.IGNORECASE)
                        mailbox = self.stripPadding(arguments.group("mailbox"))
                        if (mailbox in self.mailboxes):
                            self.stateRFC = IMAPState.Selected
                            self.selectedMailbox = mailbox
                            response = "* " + str(
                                self.mailboxes[mailbox]
                            ) + " EXISTS\r\n* 0 RECENT\r\n" + tag + " OK SELECT completed\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username, "OK")
                        else:
                            response = tag + " NO Mailbox not found\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username,
                                         "NO (mailbox not found)")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match(
                        "^" + tag +
                        " STATUS (?P<mailbox>\S+) \((?P<statusatt>\S+( \S+)*)\)$",
                        line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        arguments = re.match(
                            "^" + tag +
                            " STATUS (?P<mailbox>\S+) \((?P<statusatt>\S+( \S+)*)\)$",
                            line, re.IGNORECASE)
                        mailbox = self.stripPadding(arguments.group("mailbox"))
                        statusatt = arguments.group("statusatt")
                        if (mailbox in self.mailboxes):
                            statusatt = statusatt.replace(
                                "MESSAGES",
                                "MESSAGES " + str(self.mailboxes[mailbox]))
                            if ("RECENT" in statusatt):
                                c = 0
                                for mail in self.emails:
                                    if (mail[0] == self.selectedMailbox
                                        ):  # just mails in this mailbox
                                        if ("\\Recent" in mail[1]):
                                            c += 1
                                statusatt = statusatt.replace(
                                    "RECENT", "RECENT " + str(c))
                            if ("UNSEEN" in statusatt):
                                c = 0
                                for mail in self.emails:
                                    if (mail[0] == self.selectedMailbox
                                        ):  # just mails in this mailbox
                                        if "\\Seen" not in mail[1]:
                                            c += 1
                                statusatt = statusatt.replace(
                                    "UNSEEN", "UNSEEN " + str(c))
                                # TODO: research possible values (nonsense values at the moment)
                                statusatt = statusatt.replace(
                                    "UIDNEXT", "UIDNEXT " + str(1))
                                statusatt = statusatt.replace(
                                    "UIDVALIDITY", "UIDVALIDITY " + str(0))

                            response = "* STATUS " + mailbox + " (" + statusatt + ")\r\n"
                            response += tag + " OK STATUS completed\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username, "OK")
                        else:
                            response = "* NO STATUS failure: no status for that name\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username,
                                         "NO (mailbox not found)")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match(
                        "^" + tag +
                        " APPEND (?P<mailbox>\S+) \((?P<flaglist>\S+( \S+)*)?\)(?P<datetime> .+)? \{(?P<literal>\d+)\}$",
                        line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        arguments = re.match(
                            "^" + tag +
                            " APPEND (?P<mailbox>\S+) \((?P<flaglist>\S+( \S+)*)?\)(?P<datetime> .+)? \{(?P<literal>\d+)\}$",
                            line, re.IGNORECASE)
                        mailbox = self.stripPadding(arguments.group("mailbox"))
                        flaglist = arguments.group("flaglist")
                        # XXX: Why is this unused??
                        datetime = arguments.group("datetime")
                        literal = arguments.group("literal")
                        # grant up to about 20 * 1 MiB storage (memory-DOS protection)
                        if (int(literal) <= 1048576 and len(self.emails) < 20):
                            self.state["appendData"] = True
                            self.appendTag = tag
                            self.appendMailbox = mailbox
                            self.appendFlaglist = flaglist
                            self.appendBuffer = ""
                            self.appendLength = int(literal)
                            response = "+ Ready for literal data\r\n"
                            self.transport.write(response.encode("UTF-8"))
                        else:
                            response = "* NO APPEND error: can't append to that mailbox, error in flags or date/time or message text\r\n"
                            log.response(self.name, remote.host, remote.port,
                                         local.host, local.port, "",
                                         self.username,
                                         "NO (storage limitation)")
                            self.transport.write(response.encode("UTF-8"))
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                        self.transport.write(response.encode("UTF-8"))

                elif (re.match(
                        "^" + tag + " (LIST|LSUB|SELECT|STATUS|APPEND) .*$",
                        line, re.IGNORECASE)):
                    # unified error message for several commands
                    if (self.stateRFC == IMAPState.Auth
                            or self.stateRFC == IMAPState.Selected):
                        response = "* BAD Command argument error\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (syntax)")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                # "command-select" (Valid only when in Selected state)
                elif (re.match("^" + tag + " CLOSE$", line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Selected):
                        # TODO: delete accordingly flagged mails
                        self.stateRFC = IMAPState.Auth
                        response = tag + " OK CLOSE completed\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "OK")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match("^" + tag + "CLOSE .*$", line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Selected):
                        response = "* BAD Command argument error\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (syntax)")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match(
                        "^" + tag +
                        " UID FETCH (?P<sequenceset>\S+) (?P<entries>.+)$",
                        line, re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Selected):

                        arguments = re.match(
                            "^" + tag +
                            " UID FETCH (?P<sequenceset>\S+) (?P<entries>.+)$",
                            line, re.IGNORECASE)
                        sequenceset = arguments.group("sequenceset")
                        entries = arguments.group("entries")

                        # resolve fetch macros (according to RFC 3501) for parsing
                        macros = {
                            "ALL":
                            "(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)",
                            "FAST":
                            "(FLAGS INTERNALDATE RFC822.SIZE)",
                            "FULL":
                            "(FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)"
                        }
                        if (entries in macros):
                            entries = macros[entries]

                        response = ""
                        c = 1  # sequence number of mail in mailbox (use position in mail array as ascending UID)
                        for i, mail in enumerate(self.emails):
                            if (mail[0] == self.selectedMailbox
                                ):  # just mails in this mailbox
                                if (
                                        sequenceset == "1:*"
                                        or self.sequenceNumberInSet(
                                            sequenceset, i)
                                ):  # important performance improvement for default parameter
                                    if (entries in ["FLAGS", "(FLAGS)"]):
                                        response += "* " + str(
                                            c) + " FETCH (UID " + str(
                                                i) + " FLAGS (" + (" ".join(
                                                    mail[1])) + "))\r\n"
                                    else:
                                        response += "* " + str(c) + " FETCH "

                                        # TODO: best solution: parse fetch-att parameters and select accordingly which attributes to send
                                        entriesProcessed = entries.upper()[:-1]
                                        entriesProcessed = entriesProcessed.replace(
                                            "UID", "UID " + str(i))
                                        entriesProcessed = entriesProcessed.replace(
                                            "RFC822.SIZE", "RFC822.SIZE " +
                                            str(len(mail[3]) + len(mail[4])))
                                        entriesProcessed = entriesProcessed.replace(
                                            "FLAGS", "FLAGS (" +
                                            (" ".join(mail[1])) + ")")
                                        entriesProcessed = entriesProcessed.replace(
                                            "BODY.PEEK", "BODY")
                                        buffer = ""
                                        if ("BODY" in entriesProcessed or
                                                "ENVELOPE" in entriesProcessed
                                                or "TEXT" in entriesProcessed):
                                            buffer += mail[3] + "\r\n" + mail[4]
                                        response += entriesProcessed + " {" + str(
                                            len(buffer)) + "}\r\n" + buffer

                                        response += "\r\n)\r\n"
                                c += 1
                        response += tag + " OK FETCH complete\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "OK")

                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                elif (re.match("^" + tag + " UID FETCH .+$", line,
                               re.IGNORECASE)):
                    response = "* BAD Command argument error\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "BAD (syntax)")
                    self.transport.write(response.encode("UTF-8"))

                    # temporary workaround to avoid the client to throw an error after uploading the mail
                elif (re.match("^" + tag + " UID SEARCH *$", line,
                               re.IGNORECASE)):
                    if (self.stateRFC == IMAPState.Selected):
                        response = "* SEARCH\r\n" + tag + " OK SEARCH completed\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "OK")
                    else:
                        response = tag + " BAD Command received in invalid state.\r\n"
                        log.response(self.name, remote.host, remote.port,
                                     local.host, local.port, "", self.username,
                                     "BAD (sequence)")
                    self.transport.write(response.encode("UTF-8"))

                else:
                    response = "* BAD Command unknown\r\n"
                    log.response(self.name, remote.host, remote.port,
                                 local.host, local.port, "", self.username,
                                 "BAD (command)")
                    self.transport.write(response.encode("UTF-8"))

            else:
                response = "* BAD Syntax error\r\n"
                log.response(self.name, remote.host, remote.port, local.host,
                             local.port, "", self.username, "BAD (syntax)")
                self.transport.write(response.encode("UTF-8"))
Esempio n. 9
0
    def dataReceived(self, rawData):
        self.resetTimeout()

        local = self.transport.getHost()
        remote = self.transport.getPeer()

        # TODO: Verifizieren, dass möglichst alle 503-Fälle ("Bad sequence of commands") abgedeckt sind
        if(rawData.startswith(b'\xff') or rawData.startswith(b'\x04')):
            # ignore Ctrl+C/D/Z etc.
            pass
        else:
            # binary data like b"\xff\x..." causes trouble when decoding (simply ignore it)
            try:
                # decode raw data
                data = rawData.decode("UTF-8")
            except Exception as e: # noqa
                data = ""

            # get first line
            line = data[:data.find("\r\n")]
            # restrict maximum input lenght (doesn't affect mail transmission)
            line = line[:4094]
            log.request(self.name, remote.host, remote.port, local.host, local.port, line, self.username)

            if(re.match("^USER( \S+)?$", line, re.IGNORECASE)):
                if(re.match("^USER \S+$", line, re.IGNORECASE)):
                    arguments = re.match("^USER (?P<username>.+)$", line, re.IGNORECASE)
                    self.username = arguments.group("username")
                    self.state["user"] = True
                    response = "+OK Please enter password\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR")
                self.transport.write(response.encode("UTF-8"))

            elif(re.match("^PASS( .*)?$", line, re.IGNORECASE)):
                if(re.match("^PASS \S+$", line, re.IGNORECASE)):
                    if(self.state["user"]):
                        arguments = re.match("^PASS (?P<password>\S+)$", line, re.IGNORECASE)
                        self.password = arguments.group("password")
                        # TODO: implement honeytokendb check
                        if (True):
                            log.login(self.name, remote.host, local.port, True, self.username, self.password, "")
                            self.state["auth"] = True
                            self.setTimeout(self.timeoutPostAuth)
                            response = "+OK mailbox locked and ready\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                        else:
                            log.login(self.name, remote.host, local.port, False, self.username, self.password, "")
                            response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                    else:
                        response = "-ERR Bad sequence of commands\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (sequence of commands)")
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
#                print("POP3Service sent: \""+response+"\"")
                self.transport.write(response.encode("UTF-8"))

            elif(re.match("^STAT( .*)?$", line, re.IGNORECASE)):
                if(line == "STAT"):
                    if (self.state["auth"]):
                        response = "+OK "+str(self.mailcount)+" "+str(self.mailsize)+"\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                    else:
                        response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
                self.transport.write(response.encode("UTF-8"))

            elif(re.match("^LIST( .*)?$", line, re.IGNORECASE)):
                if(re.match("^LIST( \d+)?$", line, re.IGNORECASE)):
                    if (self.state["auth"]):
                        if(line == "LIST"):
                            response = "+OK mailbox has "+str(self.mailcount)+" messages ("+str(self.mailsize)+" octets)\r\n"
                            for i, mail in enumerate(self.emails):
                                response += str(i+1)+" "+str(len(mail[1]))+"\r\n"
                            response += ".\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                        else:
                            # n-th mail requested
                            n = int(re.match("^LIST( (\d+))$", line, re.IGNORECASE).groups()[1])
                            if (0 <= (n-1) < len(self.emails)):
                                response = "+OK "+str(n)+" "+str(len(self.emails[n-1][1]))+"\r\n"
                                log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                            else:
                                response = "-ERR Email "+str(n)+" not available\r\n"
                                log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (mail not found)")
                        self.transport.write(response.encode("UTF-8"))
                    else:
                        response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                        self.transport.write(response.encode("UTF-8"))
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
                    self.transport.write(response.encode("UTF-8"))

            elif(re.match("^UIDL( .*)?$", line, re.IGNORECASE)):
                if(re.match("^UIDL( \d+)?$", line, re.IGNORECASE)):
                    if (self.state["auth"]):
                        if(line == "UIDL"):
                            response = "+OK\r\n"
                            for i, mail in enumerate(self.emails):
                                response += str(i+1)+" "+mail[2]+"\r\n"
                            response += ".\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                        else:
                            # n-th mail requested
                            n = int(re.match("^UIDL( (\d+))$", line).groups()[1])
                            if (0 <= (n-1) < len(self.emails)):
                                response = "+OK "+str(n)+" "+hashlib.md5(self.emails[n-1][1].encode("UTF-8")).hexdigest()+"\r\n"
                                log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                            else:
                                response = "-ERR Email "+str(n)+" not available\r\n"
                                log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (mail not found)")
                        self.transport.write(response.encode("UTF-8"))
                    else:
                        response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                        self.transport.write(response.encode("UTF-8"))
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
                    self.transport.write(response.encode("UTF-8"))

            elif(re.match("^RETR( .*)?$", line, re.IGNORECASE)):
                if(re.match("^RETR( \d+)$", line, re.IGNORECASE)):
                    if (self.state["auth"]):
                        # n-th mail requested
                        n = int(re.match("^RETR( (\d+))$", line, re.IGNORECASE).groups()[1])
                        if (0 <= (n-1) < len(self.emails)):
                            response = "+OK "+str(len(self.emails[n-1][1]))+" octets\r\n"+self.emails[n-1][0]+"\r\n"+self.emails[n-1][1]+"\r\n.\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                        else:
                            response = "-ERR message "+str(n)+" not available\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (mail not found)")
                        self.transport.write(response.encode("UTF-8"))
                    else:
                        response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                        self.transport.write(response.encode("UTF-8"))
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
                    self.transport.write(response.encode("UTF-8"))

            elif(re.match("^DELE( .*)?$", line, re.IGNORECASE)):
                if(re.match("^DELE( \d+)?$", line, re.IGNORECASE)):
                    if (self.state["auth"]):
                        # n-th mail requested
                        n = int(re.match("^DELE( (\d+))$", line, re.IGNORECASE).groups()[1])
                        if (0 <= (n-1) < len(self.emails)):
                            response = "+OK message "+str(n)+" deleted\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                        else:
                            response = "-ERR message "+str(n)+" not available\r\n"
                            log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (mail not found)")
                        self.transport.write(response.encode("UTF-8"))
                    else:
                        response = "-ERR POP3 Authentication unsuccessful/Bad username or password\r\n"
                        log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (credentials)")
                        self.transport.write(response.encode("UTF-8"))
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax)")
                    self.transport.write(response.encode("UTF-8"))

            elif(re.match("^QUIT( .*)?$", line, re.IGNORECASE)):
                # make sure QUIT doesn't have parameters (unimportant for correct functioning but good for concealment)
                if (line == "QUIT"):
                    self.state["connected"] = False
                    response = "+OK bye\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "+OK")
                    self.transport.write(response.encode("UTF-8"))
                    # close connection
                    self.transport.loseConnection()
                else:
                    response = "-ERR Syntax error in parameters or arguments\r\n"
                    log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (syntax")
                    self.transport.write(response.encode("UTF-8"))

            else:
                response = "-ERR Unrecognized command \'"+line+"\'\r\n"
                log.response(self.name, remote.host, remote.port, local.host, local.port, "", self.username, "-ERR (command)")
                self.transport.write(response.encode("UTF-8"))