// The external C2 module extends the C2 functionality and exposes an interface to allow for an
// exploit to utilize a channel that is defined in an external repository. This enables third-party
// and non-trivial channels. This module defines an interface and external service type that must be
// handled by the implementing external module.
//
// The External interface requires the following functions, each of which require the external
// C2 to define a set of functions:
//
//   - Configure - A function to wrap the internal C2 functions and integrate them into the
//     go-exploit expected structure.
//   - SetFlags - Configure the C2 specific flags used by the exploit.
//   - SetInit - Sets up the server singleton/C2 representation of the server structs.
//   - SetChannel` - Creates the go-exploit channel that is the framework representation of an
//     object and allows for channel settings to filter into the external module.
//   - SetRun - The function that actually runs the external C2.
//
// At this time only one External module can be defined per exploit as the implementation and
// singleton can not be duplicated.
//
// # Creating an external C2 channel
//
// An external module template will generally be structured as follows:
//
//	package c2external
//
//	import (
//		"flag"
//		"net"
//
//		"github.com/vulncheck-oss/go-exploit/c2"
//		"github.com/vulncheck-oss/go-exploit/c2/channel"
//		"github.com/vulncheck-oss/go-exploit/c2/external"
//	)
//
//	var flagCommand string
//
//	var (
//		Name      = "ExtServer"
//		ExtServer c2.Impl
//	)
//
//	type ExternalC2 struct {
//		Channel *channel.Channel
//		// Example of how you can define variables accessible in the set functions
//		Listener *net.Listener
//	}
//
//	func New() ExternalC2 {
//		return ExternalC2{}
//	}
//
//	func (c2 *ExternalC2) ExtServerFlags() {
//		// Flags for the external C2. The run function in the framework handles the parsing and
//		// the options will be available to the exploit.
//		flag.StringVar(&flagCommand, Name+".command", "", "Run a single command and exit the payload.")
//	}
//
//	func (c2 *ExternalC2) ExtServerInit() {
//		// Any initialization such as key generation or external configuration components can go
//		// here.
//	}
//
//	func (c2 *ExternalC2) ExtServerChannel(channel *channel.Channel) {
//		// This will generally just be setting the internal channel to match the expected
//		// go-exploit channel and provide access to the framework channel.
//		c2.Channel = channel
//	}
//
//	func (c2 *ExternalC2) ExtServerRun(timeout int) bool {
//		// Add any servers or connection pooling here
//		// Make sure to handle the timeout!
//		return false
//	}
//
//	func Configure(externalServer *external.Server) {
//		ExtServer = c2.AddC2(Name)
//		extc2 := New()
//		externalServer.SetFlags(extc2.ExtServerFlags)
//		externalServer.SetChannel(extc2.ExtServerChannel)
//		externalServer.SetInit(extc2.ExtServerInit)
//		externalServer.SetRun(extc2.ExtServerRun)
//	}
//
// # Adding an external C2 to an exploit
//
// In order to add an external C2 to an exploit it is required to get a new
//
//	package main
//
//	import (
//		"flag"
//		"os/exec"
//
//		"github.com/vulncheck-oss/go-exploit"
//		"github.com/vulncheck-oss/go-exploit/c2"
//		"github.com/vulncheck-oss/go-exploit/c2/external"
//		"github.com/vulncheck-oss/go-exploit/config"
//		"github.com/vulncheck-oss/go-exploit/output"
//
//		c2example "github.com/vulncheck-oss/external-c2-experiments/example"
//	)
//
//	type ExternalTest struct{}
//
//	var flagPayload string
//
//	func (sploit ExternalTest) ValidateTarget(_ *config.Config) bool {
//		return false
//	}
//
//	func (sploit ExternalTest) CheckVersion(_ *config.Config) exploit.VersionCheckType {
//		return exploit.NotImplemented
//	}
//
//	func (sploit ExternalTest) RunExploit(conf *config.Config) bool {
//		if flagPayload == "" {
//			output.PrintfStatus("Payload argument required")
//			return false
//		}
//		cmd := exec.Command(flagPayload)
//		stdoutStderr, err := cmd.CombinedOutput()
//		if err != nil {
//			output.PrintfError("%s", err.Error())
//		}
//		output.PrintfError("%s", stdoutStderr)
//
//		return true
//	}
//
//	func main() {
//		flag.StringVar(&flagPayload, "payload", "", "Payload to execute")
//		ext2 := external.GetInstance(c2example.ExtServer.Name)
//		c2example.Configure(ext2)
//		supportedC2 := []c2.Implementation{
//			c2example.ExtServer,
//			c2.SimpleShellServer,
//		}
//
//		conf := config.NewRemoteExploit(
//				config.ImplementedFeatures{
//					AssetDetection: false,
//					VersionScanning: false,
//					Exploitation: false },
//				config.CodeExecution, supportedC2,
//				"Vendor", []string{"Product"},
//				[]string{"cpe:2.3:a:vendor:product"},
//				"CVE-2024-1270", "HTTP", 8080
//			)
//		sploit := ExternalTest{}
//		exploit.RunProgram(sploit, conf)
//	}
//
// It is important to keep in mind that a payload will still need to be written for our newly
// created external C2, as well as handling said payload in the exploit.
//
// In order to use the above C2 in an exploit the following shows how it could be used:
package external

import (
	"github.com/vulncheck-oss/go-exploit/c2/channel"
	"github.com/vulncheck-oss/go-exploit/output"
)

// The Server struct holds the declared external modules internal functions and channel data.
type Server struct {
	flags    func()
	init     func()
	run      func(int) bool
	shutdown func() bool
	meta     func(*channel.Channel)
	channel  *channel.Channel
	name     string
}

// The External interface defines which functions are required to be defined in an external C2
// channel in order to function inside the framework properly. These are ordered in generally
// suggested execution order.
type External interface {
	Configure(*Server)
	SetChannel(func(*channel.Channel))
	SetFlags(func())
	SetInit(func())
	SetRun(func(int) bool)
	SetShutdown(func() bool)
}

var serverSingletons map[string]*Server

// Gets the singleton instance of the external C2. These are kept track based on their internal
// names.
func GetInstance(externalName string) *Server {
	if len(serverSingletons) == 0 {
		serverSingletons = make(map[string]*Server)
		singleton := new(Server)
		singleton.name = externalName
		serverSingletons[externalName] = singleton

		return singleton
	}
	_, exists := serverSingletons[externalName]
	if !exists {
		singleton := new(Server)
		singleton.name = externalName
		serverSingletons[externalName] = singleton

		return singleton
	}

	return serverSingletons[externalName]
}

// SetFlags sets the external modules function for command line flag management.
func (externalServer *Server) SetFlags(f func()) {
	if f == nil {
		panic("SetFlags *must* be a valid function")
	}
	externalServer.flags = f
}

// CreateFlags is used by the framework to run the set function for flag management. This is not
// expected to be implemented by the downstream external C2.
func (externalServer *Server) CreateFlags() {
	if externalServer.flags == nil {
		panic("CreateFlags *must* be a valid function")
	}
	externalServer.flags()
}

// SetInit sets the external C2 initialization function. This function is expected to be used for
// any database management, configuration parsing, and any other functionality required for the C2
// that is not managed by the go-exploit framework channels or command line flags.
func (externalServer *Server) SetInit(f func()) {
	if externalServer.flags == nil {
		panic("Init *must* be a valid function")
	}
	externalServer.init = f
}

// SetChannel sets the function for channel management. The go-exploit channel represents basic
// settings that are provided to the frameworks core modules and are regularly used as ergonomic
// helpers, but may also be required by the external module (ie accessing -lhost or -lport variables
// without having to resort to passing all flag arguments). This generally does not need to
// be complex and is often just passing the channel to a C2 side variable, but they can also be used
// to modify the channel for use in a C2.
func (externalServer *Server) SetChannel(f func(*channel.Channel)) {
	externalServer.meta = f
}

// Init triggers the set C2 initialization and passes the channel to the external module.
func (externalServer *Server) Init(channel *channel.Channel) bool {
	if channel.IsClient {
		output.PrintFrameworkError("Called ExternalServer as a client.")

		return false
	}
	externalServer.init()
	externalServer.meta(channel)

	return true
}

// SetRun sets the external C2 run logic. This is often where the core of the handling is done and
// will often times be setting up listeners, connecting to a SaaS service and querying for payload
// responses, setting up external handlers, etc.
func (externalServer *Server) SetRun(f func(int) bool) {
	externalServer.run = f
}

// Triggers the external modules Run function with the set timeout.
func (externalServer *Server) Run(timeout int) {
	externalServer.run(timeout)
}

// SetShutdown sets the function for server shutdown handling and session cleanup logic. This
// function is what gets called when the framework receives a OS signal, a shell is closed, or
// manually invoked.
func (externalServer *Server) SetShutdown(f func() bool) {
	externalServer.shutdown = f
}

// Shutdown triggers the set shutdown function.
func (externalServer *Server) Shutdown() bool {
	return externalServer.shutdown()
}

// Return the underlying C2 channel containing channel metadata and session tracking.
func (externalServer *Server) Channel() *channel.Channel {
	// I'd much rather have just exposed a `Server.Channel`, but we are interface bound
	return externalServer.channel
}
