From 747b6af76b23650756811d896bf76b4331419784 Mon Sep 17 00:00:00 2001 From: "Tyler St. Onge" Date: Sat, 25 Jul 2020 22:38:14 -0400 Subject: refine ftp component and add configuration capabilities --- .idea/modules/honeypot-build.iml | 4 +- .idea/modules/honeypot.iml | 27 +++++++++++- build.sbt | 6 ++- config.json | 9 ++++ src/main/scala/com/tylerstonge/honeypot/Main.scala | 7 ++-- .../com/tylerstonge/honeypot/Supervisor.scala | 32 +++++++++++++-- .../com/tylerstonge/honeypot/ftp/FtpHandler.scala | 29 ++++++++----- .../com/tylerstonge/honeypot/ftp/FtpListener.scala | 6 +-- .../tylerstonge/honeypot/http/HttpListener.scala | 27 ------------ .../honeypot/http/SimplisticHandler.scala | 11 ----- .../honeypot/messages/MStartComponent.scala | 7 ++++ .../tylerstonge/honeypot/ftp/FtpHandlerTest.scala | 48 ++++++++++++++++++++++ 12 files changed, 149 insertions(+), 64 deletions(-) create mode 100644 config.json delete mode 100644 src/main/scala/com/tylerstonge/honeypot/http/HttpListener.scala delete mode 100644 src/main/scala/com/tylerstonge/honeypot/http/SimplisticHandler.scala create mode 100644 src/main/scala/com/tylerstonge/honeypot/messages/MStartComponent.scala create mode 100644 src/test/scala/com/tylerstonge/honeypot/ftp/FtpHandlerTest.scala 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 @@ - + @@ -108,7 +108,7 @@ - \ 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 @@ - + + - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.sbt b/build.sbt index 04e6aec..7a7a1ff 100644 --- a/build.sbt +++ b/build.sbt @@ -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 + */ +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 + */ +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")) + } + } + +} -- cgit v1.1