diff options
author | Tyler St. Onge <tylertstonge@gmail.com> | 2020-07-25 22:38:14 -0400 |
---|---|---|
committer | Tyler St. Onge <tylertstonge@gmail.com> | 2020-07-25 22:38:14 -0400 |
commit | 747b6af76b23650756811d896bf76b4331419784 (patch) | |
tree | 2e942c8296567cbe4d57f205f72029a62829bdda | |
parent | 945332ca057383f258c78fd15cbc22f8b8d58a83 (diff) |
refine ftp component and add configuration capabilities
-rw-r--r-- | .idea/modules/honeypot-build.iml | 4 | ||||
-rw-r--r-- | .idea/modules/honeypot.iml | 27 | ||||
-rw-r--r-- | build.sbt | 6 | ||||
-rw-r--r-- | config.json | 9 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/Main.scala | 7 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/Supervisor.scala | 32 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/ftp/FtpHandler.scala | 29 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/ftp/FtpListener.scala | 6 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/http/HttpListener.scala | 27 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/http/SimplisticHandler.scala | 11 | ||||
-rw-r--r-- | src/main/scala/com/tylerstonge/honeypot/messages/MStartComponent.scala | 7 | ||||
-rw-r--r-- | src/test/scala/com/tylerstonge/honeypot/ftp/FtpHandlerTest.scala | 48 |
12 files changed, 149 insertions, 64 deletions
diff --git a/.idea/modules/honeypot-build.iml b/.idea/modules/honeypot-build.iml index 725673b..5767c79 100644 --- a/.idea/modules/honeypot-build.iml +++ b/.idea/modules/honeypot-build.iml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<module external.linked.project.id="honeypot-build" external.linked.project.path="$MODULE_DIR$/../../project" external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" sbt.imports="_root_.sbt.Keys._, _root_.sbt.ScriptedPlugin.autoImport._, _root_.sbt._, _root_.sbt.nio.Keys._, _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.ScriptedPlugin, _root_.sbt.plugins.SbtPlugin, _root_.sbt.plugins.SemanticdbPlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.sbt.plugins.Giter8TemplatePlugin, _root_.scala.xml.{TopScope=&gt;SUB:DOLLARscope}" sbt.resolvers="https://repo1.maven.org/maven2/|maven|public, /home/dropkick/.ivy2/cache|ivy|Local cache, file:/home/dropkick/.sbt/preloaded|maven|local-preloaded" type="SBT_MODULE" version="4"> +<module external.linked.project.id="honeypot-build" external.linked.project.path="$MODULE_DIR$/../../project" external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" sbt.imports="_root_.sbt.Keys._, _root_.sbt.ScriptedPlugin.autoImport._, _root_.sbt._, _root_.sbt.nio.Keys._, _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.ScriptedPlugin, _root_.sbt.plugins.SbtPlugin, _root_.sbt.plugins.SemanticdbPlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.sbt.plugins.Giter8TemplatePlugin, _root_.scala.xml.{TopScope=&gt;SUB:DOLLARscope}" sbt.resolvers="file:/$USER_HOME$/.sbt/preloaded|maven|local-preloaded, https://repo1.maven.org/maven2/|maven|public, C:\Users\dropkick\.ivy2\cache|ivy|Local cache" type="SBT_MODULE" version="4"> <component name="NewModuleRootManager"> <output url="file://$MODULE_DIR$/../../project/target/idea-classes" /> <output-test url="file://$MODULE_DIR$/../../project/target/idea-test-classes" /> @@ -108,7 +108,7 @@ </orderEntry> </component> <component name="SbtModule"> - <option name="buildForURI" value="file:$MODULE_DIR$/../../" /> + <option name="buildForURI" value="file:/$MODULE_DIR$/../../" /> <option name="imports" value="_root_.sbt.Keys._, _root_.sbt.ScriptedPlugin.autoImport._, _root_.sbt._, _root_.sbt.nio.Keys._, _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.ScriptedPlugin, _root_.sbt.plugins.SbtPlugin, _root_.sbt.plugins.SemanticdbPlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.sbt.plugins.Giter8TemplatePlugin, _root_.scala.xml.{TopScope=>SUB:DOLLARscope}" /> </component> </module>
\ No newline at end of file diff --git a/.idea/modules/honeypot.iml b/.idea/modules/honeypot.iml index e735fa7..a948c8e 100644 --- a/.idea/modules/honeypot.iml +++ b/.idea/modules/honeypot.iml @@ -1,18 +1,41 @@ <?xml version="1.0" encoding="UTF-8"?> -<module external.linked.project.id="honeypot [file:/home/dropkick/dev/honeypot/honeypot/]" external.linked.project.path="$MODULE_DIR$/../.." external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" type="JAVA_MODULE" version="4"> +<module external.linked.project.id="honeypot [file:/C:/Users/dropkick/Documents/dev/honeypot/]" external.linked.project.path="$MODULE_DIR$/../.." external.root.project.path="$MODULE_DIR$/../.." external.system.id="SBT" type="JAVA_MODULE" version="4"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8"> <output url="file://$MODULE_DIR$/../../target/scala-2.13/classes" /> <output-test url="file://$MODULE_DIR$/../../target/scala-2.13/test-classes" /> <exclude-output /> <content url="file://$MODULE_DIR$/../.."> <sourceFolder url="file://$MODULE_DIR$/../../src/main/scala" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/../../src/test/scala" isTestSource="true" /> <excludeFolder url="file://$MODULE_DIR$/../../target" /> </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.13:2.6.6:jar" level="project" /> + <orderEntry type="library" name="sbt: com.typesafe.akka:akka-actor_2.13:2.6.8:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: com.typesafe.akka:akka-testkit_2.13:2.6.8:jar" level="project" /> <orderEntry type="library" name="sbt: com.typesafe:config:1.4.0:jar" level="project" /> <orderEntry type="library" name="sbt: org.scala-lang.modules:scala-java8-compat_2.13:0.9.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scala-lang.modules:scala-xml_2.13:1.2.0:jar" level="project" /> <orderEntry type="library" name="sbt: org.scala-lang:scala-library:2.13.1:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scala-lang:scala-reflect:2.13.1:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalactic:scalactic_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-compatible:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-core_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-diagrams_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-featurespec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-flatspec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-freespec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-funspec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-funsuite_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-matchers-core_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-mustmatchers_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-propspec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-refspec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-shouldmatchers_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest-wordspec_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" scope="TEST" name="sbt: org.scalatest:scalatest_2.13:3.2.0:jar" level="project" /> + <orderEntry type="library" name="sbt: org.typelevel:jawn-ast_2.13:1.0.0:jar" level="project" /> + <orderEntry type="library" name="sbt: org.typelevel:jawn-parser_2.13:1.0.0:jar" level="project" /> + <orderEntry type="library" name="sbt: org.typelevel:jawn-util_2.13:1.0.0:jar" level="project" /> </component> </module>
\ No newline at end of file @@ -4,4 +4,8 @@ name := "honeypot" organization := "com.tylerstonge" version := "1.0" -libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.6" +libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.8" +libraryDependencies += "org.typelevel" %% "jawn-ast" % "1.0.0" + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0" % Test +libraryDependencies += "com.typesafe.akka" %% "akka-testkit" % "2.6.8" % Test
\ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..792f931 --- /dev/null +++ b/config.json @@ -0,0 +1,9 @@ +{
+ "components": [
+ {
+ "name": "ftp-listener",
+ "type": "ftp",
+ "port": 21
+ }
+ ]
+}
\ No newline at end of file diff --git a/src/main/scala/com/tylerstonge/honeypot/Main.scala b/src/main/scala/com/tylerstonge/honeypot/Main.scala index 3c01fc8..0d47de4 100644 --- a/src/main/scala/com/tylerstonge/honeypot/Main.scala +++ b/src/main/scala/com/tylerstonge/honeypot/Main.scala @@ -1,9 +1,8 @@ package com.tylerstonge.honeypot import akka.actor.{ActorSystem, Props} -import com.tylerstonge.honeypot.ftp.FtpListener object Main extends App { - val system = ActorSystem("hello-system") - val listener = system.actorOf(Props[FtpListener], name = "ftp-listener") -} + val system = ActorSystem("honeypot_system") + val listener = system.actorOf(Props[Supervisor], name = "honeypot-supervisor") +}
\ No newline at end of file diff --git a/src/main/scala/com/tylerstonge/honeypot/Supervisor.scala b/src/main/scala/com/tylerstonge/honeypot/Supervisor.scala index a04a6e8..ee41ea2 100644 --- a/src/main/scala/com/tylerstonge/honeypot/Supervisor.scala +++ b/src/main/scala/com/tylerstonge/honeypot/Supervisor.scala @@ -1,14 +1,38 @@ package com.tylerstonge.honeypot -import akka.actor.Actor +import java.nio.file.Paths + +import akka.actor.{Actor, Props} +import akka.event.{Logging, LoggingAdapter} +import com.tylerstonge.honeypot.ftp.FtpListener +import com.tylerstonge.honeypot.messages.MStartComponent +import org.typelevel.jawn.ast.{JArray, JParser} class Supervisor extends Actor { - override def receive: Receive = { - case _ => println("kk dood") + val log: LoggingAdapter = Logging(context.system, this) + + override def preStart: Unit = { + val cfg = JParser.parseFromFile(Paths.get("config.json").toFile).get + cfg.get("components").asInstanceOf[JArray].vs.foreach(c => { + self ! MStartComponent(c.get("name").asString, c.get("type").asString, c.get("port").asInt) + }) } override def postStop { - println("Supervisor::postStop") + log.debug("supervisor is shutting down") + } + + override def receive: Receive = { + case msg: MStartComponent => startComponent(msg) + case _ => log.debug("supervisor received unhandled message") + } + + private def startComponent(msg: MStartComponent) { + log.info("starting component :: {}", msg.name) + msg.ctype match { + case "ftp" => context.actorOf(Props(new FtpListener(msg.port)), name = msg.name) + case _ => log.error("unknown component type: ", msg.ctype, msg.name); + } } }
\ No newline at end of file diff --git a/src/main/scala/com/tylerstonge/honeypot/ftp/FtpHandler.scala b/src/main/scala/com/tylerstonge/honeypot/ftp/FtpHandler.scala index 8a55396..48db3f5 100644 --- a/src/main/scala/com/tylerstonge/honeypot/ftp/FtpHandler.scala +++ b/src/main/scala/com/tylerstonge/honeypot/ftp/FtpHandler.scala @@ -6,22 +6,31 @@ import akka.io.Tcp.{PeerClosed, Received, Write} import akka.util.ByteString class FtpHandler extends Actor { + val log: LoggingAdapter = Logging(context.system, this) override def receive: Receive = { - case Received(data) => - log.info(">> {}", data.utf8String) - sender() ! Write(ByteString.apply(parse(data.utf8String))) + case Received(data) => sender() ! Write(ByteString.apply(parse(sanitize(data)))) case PeerClosed => - log.info("closing connection") + log.info("peer closed connection") context.stop(self) } - def parse(msg: String): String = msg match { - case "USER anonymous\n" => "331 Please specify password.\n" - case "PASS password\n" => "230 Login successful.\n" - case "PWD\n" => "257 \"/\" is the current directory\n" - case "QUIT\n" => "221 Goodbye.\n" - case _ => "200 sure\n" + def parse(msg: Array[String]): String = msg(0) match { + case "user" => + log.info("attempted login with username: {}", msg(1)) + "331 Please specify password.\n" + case "pass" => + log.info("attempted login with password: {}", msg(1)) + "230 Login successful.\n" + case "pwd" => "257 \"/\" is the current directory\n" + case "quit" => "221 Goodbye.\n" + case _ => + log.info("unsupported command received: {}", msg.mkString(" ")) + "451 Requested action aborted. Local error in processing.\n" + } + + def sanitize(data: ByteString): Array[String] = { + data.utf8String.trim.toLowerCase().split(" ") } } diff --git a/src/main/scala/com/tylerstonge/honeypot/ftp/FtpListener.scala b/src/main/scala/com/tylerstonge/honeypot/ftp/FtpListener.scala index 3e6606a..b988ba3 100644 --- a/src/main/scala/com/tylerstonge/honeypot/ftp/FtpListener.scala +++ b/src/main/scala/com/tylerstonge/honeypot/ftp/FtpListener.scala @@ -8,16 +8,16 @@ import akka.io.Tcp._ import akka.io.{IO, Tcp} import akka.util.ByteString -class FtpListener extends Actor { +class FtpListener (port: Int) extends Actor { val log: LoggingAdapter = Logging(context.system, this) - IO(Tcp)(context.system) ! Bind(self, new InetSocketAddress("localhost", 2121)) + IO(Tcp)(context.system) ! Bind(self, new InetSocketAddress("localhost", port)) override def receive: Receive = { case Bound(localAddress) => log.info("listening on {}", localAddress) case CommandFailed(_: Bind) => context.stop(self) - case Connected(remote, local) => + case Connected => val handler = context.actorOf(Props[FtpHandler]) val connection = sender() connection ! Register(handler) diff --git a/src/main/scala/com/tylerstonge/honeypot/http/HttpListener.scala b/src/main/scala/com/tylerstonge/honeypot/http/HttpListener.scala deleted file mode 100644 index 8943ee0..0000000 --- a/src/main/scala/com/tylerstonge/honeypot/http/HttpListener.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.tylerstonge.honeypot.http - -import java.net.InetSocketAddress - -import akka.actor.{Actor, Props} -import akka.event.Logging -import akka.io.Tcp._ -import akka.io.{IO, Tcp} - - -class HttpListener extends Actor { - val log = Logging(context.system, this) - - import context.system - - IO(Tcp) ! Bind(self, new InetSocketAddress("localhost", 7333)) - - override def receive: Receive = { - case b@Bound(localAddress) => context.parent ! b - case CommandFailed(_: Bind) => context.stop(self) - case c@Connected(remote, local) => - val handler = context.actorOf(Props[SimplisticHandler]) - val connection = sender() - connection ! Register(handler) - } - -}
\ No newline at end of file diff --git a/src/main/scala/com/tylerstonge/honeypot/http/SimplisticHandler.scala b/src/main/scala/com/tylerstonge/honeypot/http/SimplisticHandler.scala deleted file mode 100644 index 2fe1409..0000000 --- a/src/main/scala/com/tylerstonge/honeypot/http/SimplisticHandler.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.tylerstonge.honeypot.http - -import akka.actor.Actor -import akka.io.Tcp.{PeerClosed, Received, Write} - -class SimplisticHandler extends Actor { - def receive: Receive = { - case Received(data) => sender() ! Write(data) - case PeerClosed => context.stop(self) - } -} diff --git a/src/main/scala/com/tylerstonge/honeypot/messages/MStartComponent.scala b/src/main/scala/com/tylerstonge/honeypot/messages/MStartComponent.scala new file mode 100644 index 0000000..e4109b0 --- /dev/null +++ b/src/main/scala/com/tylerstonge/honeypot/messages/MStartComponent.scala @@ -0,0 +1,7 @@ +package com.tylerstonge.honeypot.messages
+
+/**
+ *
+ * @author Tyler St. Onge <tylertstonge@gmail.com>
+ */
+case class MStartComponent (name: String, ctype: String, port: Int)
diff --git a/src/test/scala/com/tylerstonge/honeypot/ftp/FtpHandlerTest.scala b/src/test/scala/com/tylerstonge/honeypot/ftp/FtpHandlerTest.scala new file mode 100644 index 0000000..bc9b799 --- /dev/null +++ b/src/test/scala/com/tylerstonge/honeypot/ftp/FtpHandlerTest.scala @@ -0,0 +1,48 @@ +package com.tylerstonge.honeypot.ftp
+
+import akka.actor.{ActorSystem, Props}
+import akka.io.Tcp.{Received, Write}
+import akka.testkit.{ImplicitSender, TestKit}
+import akka.util.ByteString
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpecLike
+
+/**
+ *
+ * @author Tyler St. Onge <tylertstonge@gmail.com>
+ */
+class FtpHandlerTest extends TestKit(ActorSystem("honeypot-system")) with ImplicitSender with AnyWordSpecLike with Matchers with BeforeAndAfterAll {
+
+ override def afterAll: Unit = {
+ TestKit.shutdownActorSystem(system)
+ }
+
+ "An FtpHandler actor" must {
+ "return 331 in response to USER" in {
+ val handler = system.actorOf(Props[FtpHandler])
+ handler ! Received(ByteString("USER anonymous"))
+ val msg = expectMsgType[Write]
+ assert(msg.data.utf8String.startsWith("331"))
+ }
+ "return 230 in response to PASS" in {
+ val handler = system.actorOf(Props[FtpHandler])
+ handler ! Received(ByteString("PASS password"))
+ val msg = expectMsgType[Write]
+ assert(msg.data.utf8String.startsWith("230"))
+ }
+ "return 257 in response to PWD" in {
+ val handler = system.actorOf(Props[FtpHandler])
+ handler ! Received(ByteString("PWD"))
+ val msg = expectMsgType[Write]
+ assert(msg.data.utf8String.startsWith("257"))
+ }
+ "return 221 in response to QUIT" in {
+ val handler = system.actorOf(Props[FtpHandler])
+ handler ! Received(ByteString("quit"))
+ val msg = expectMsgType[Write]
+ assert(msg.data.utf8String.startsWith("221"))
+ }
+ }
+
+}
|