diff --git a/api/client/client.go b/api/client/client.go
new file mode 100644
index 0000000..92787eb
--- /dev/null
+++ b/api/client/client.go
@@ -0,0 +1,60 @@
+package client
+
+// exists to provide helper functions to interact with the api
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/vulncheck-oss/go-exploit/config"
+	"github.com/vulncheck-oss/go-exploit/c2"
+	"github.com/vulncheck-oss/go-exploit/c2/channel"
+	"github.com/vulncheck-oss/go-exploit/output"
+	"github.com/vulncheck-oss/go-exploit/protocol"
+	"github.com/vulncheck-oss/go-exploit/api/types"
+	httpapi "github.com/vulncheck-oss/go-exploit/api/http"
+)
+
+var C2TypeMap = map[c2.Impl]string {
+	c2.SimpleShellServer: types.SimpleShellServer,
+	c2.SSLShellServer: types.SSLShellServer,
+}
+
+func StartListener(conf *config.Config, serverChannel *channel.Channel) bool {
+	serverType, ok := C2TypeMap[conf.C2Type]
+	if !ok {
+		output.PrintfFrameworkError("Error creating API request to start listener, invalid ServerType provided.")
+
+		return false
+	}
+
+	req := httpapi.StartListenerRequest {
+		ServerType: serverType,
+		IPAddr:     serverChannel.IPAddr,
+		Port:       serverChannel.Port,
+		Timeout:    serverChannel.Timeout,
+		HTTPAddr:   serverChannel.HTTPAddr,
+		HTTPPort:   serverChannel.HTTPPort,
+	}
+
+	requestString, err := json.Marshal(req)
+	if err != nil {
+		output.PrintfFrameworkError("Failed to encode JSON for StartListener API request: %s", err)
+
+		return false
+	}
+
+	url := protocol.GenerateURL(conf.APIAddr, conf.APIPort, conf.SSL, "/startListener")
+	resp, body, ok := protocol.HTTPSendAndRecv("POST", url, string(requestString))
+	if !ok {
+		return false
+	}
+
+	if resp.StatusCode != http.StatusOK {
+		output.PrintfFrameworkDebug("Received failure status for StartListener API request: %d, message: %s", resp.StatusCode, body)
+
+		return false
+	}
+
+	output.PrintFrameworkStatus("API successfully created the listener")
+	return true
+}
diff --git a/api/http/handlers.go b/api/http/handlers.go
new file mode 100644
index 0000000..ba1aa03
--- /dev/null
+++ b/api/http/handlers.go
@@ -0,0 +1,161 @@
+package http
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/vulncheck-oss/go-exploit/api/listenermanager"
+	"github.com/vulncheck-oss/go-exploit/c2/channel"
+	"github.com/vulncheck-oss/go-exploit/output"
+)
+
+type APIResponse struct {
+	Status string `json:"status"`
+	Data   string `json:"data"`
+}
+
+type StartListenersAPIResponse struct {
+	Status string            `json:"status"`
+	Data   map[string]string `json:"data"`
+}
+
+type GetListenersAPIResponse struct {
+	Status string                                 `json:"status"`
+	Data   []listenermanager.GetListenersResponse `json:"data"`
+}
+
+type StartListenerRequest struct {
+	ServerType string `json:"servertype"`
+	IPAddr string `json:"ipaddr"`
+	Port int `json:"port"`
+	Timeout int `json:"timeout"`
+	HTTPAddr string `json:"httpaddr"`
+	HTTPPort int `json:"httpport"`
+}
+
+type StopListenerRequest struct {
+	UUID string `json:"uuid"`
+}
+
+
+func sendAPIResponse(w http.ResponseWriter, status string, errMessage string) {
+	if status == "failure" {
+		w.WriteHeader(http.StatusBadRequest)
+	}
+	response := APIResponse{Status: status, Data: errMessage}
+	if err := json.NewEncoder(w).Encode(response); err != nil {
+		http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
+
+		return
+	}
+}
+
+func startListener(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		sendAPIResponse(w, "failure", "Invalid request method")
+
+		return
+	}
+
+	var request StartListenerRequest
+
+	err := json.NewDecoder(r.Body).Decode(&request)
+	if err != nil {
+		sendAPIResponse(w, "failure", "Invalid request provided")
+
+		return
+	}
+
+	if request.Timeout == 0 {
+		output.PrintFrameworkWarn("No serverTimeout provided, using 30 instead")
+		request.Timeout = 30
+	}
+
+	serverChannel := channel.Channel{
+		Timeout: request.Timeout,
+		HTTPAddr: request.HTTPAddr,
+		HTTPPort: request.HTTPPort,
+		IPAddr: request.IPAddr,
+		Port: request.Port,
+		Managed: true,
+	}
+
+	uuid, ok := listenermanager.GetInstance().StartListener(request.ServerType, serverChannel)
+	if !ok {
+		sendAPIResponse(w, "failure", "Failed to start listener")
+
+		return
+	}
+
+	uuidData := map[string]string{"uuid": uuid}
+	if err := json.NewEncoder(w).Encode(&StartListenersAPIResponse{Status: "success", Data: uuidData}); err != nil {
+		http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError)
+
+		return
+	}
+}
+
+func getListeners(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		sendAPIResponse(w, "failure", "Invalid request method")
+
+		return
+	}
+
+	responseArray, ok := listenermanager.GetInstance().GetListeners()
+	if !ok {
+		sendAPIResponse(w, "failure", "Failed to retrieve listeners")
+
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+
+	if len(responseArray) == 0 {
+		sendAPIResponse(w, "success", "[]")
+
+		return
+	}
+
+	if err := json.NewEncoder(w).Encode(&GetListenersAPIResponse{Status: "success", Data: responseArray}); err != nil {
+		http.Error(w, "Failed to encode JSON reseponse", http.StatusInternalServerError)
+
+		return
+	}
+}
+
+func stopListener(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodPost {
+		sendAPIResponse(w, "failure", "Invalid request method")
+
+		return
+	}
+
+	var request StopListenerRequest
+
+	err := json.NewDecoder(r.Body).Decode(&request)
+	if err != nil {
+		sendAPIResponse(w, "failure", "Invalid request provided")
+
+		return
+	}
+
+	ok := listenermanager.GetInstance().StopListener(request.UUID)
+	if !ok {
+		sendAPIResponse(w, "failure", "Failed to shutdown server")
+
+		return
+	}
+
+	sendAPIResponse(w, "success", "Successfully shutdown server")
+}
+
+func getStatus(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		sendAPIResponse(w, "failure", "Invalid request method")
+
+		return
+	}
+
+	sendAPIResponse(w, "success", "go-exploit-server-status: good")
+}
diff --git a/api/http/http.go b/api/http/http.go
new file mode 100644
index 0000000..114fbca
--- /dev/null
+++ b/api/http/http.go
@@ -0,0 +1,144 @@
+package http
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net/http"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/vulncheck-oss/go-exploit/encryption"
+	"github.com/vulncheck-oss/go-exploit/output"
+)
+
+type Server struct {
+	// signals the shutdown
+	Shutdown *atomic.Bool
+	// specifies if https should be used
+	HTTPS bool
+	// port that the api listens on
+	Port int
+	// addr that api listens on
+	Addr string
+	// handle to use for shutting down the server
+	ServerHandle http.Server
+	// ssl cert
+	PrivateKeyFile string
+	// ssl private key
+	CertificateFile string
+	// loaded certificate
+	Certificate tls.Certificate
+}
+
+var (
+	wg              sync.WaitGroup
+	serverSingleton *Server
+)
+
+// The singleton interface for the Socks over HTTPS server.
+func GetInstance() *Server {
+	if serverSingleton == nil {
+		serverSingleton = new(Server)
+	}
+
+	return serverSingleton
+}
+
+// setup certs and vars.
+func (server *Server) Init(Addr string, Port int) bool {
+	server.Addr = Addr
+	server.Port = Port
+
+	if server.Shutdown == nil {
+		// Initialize the shutdown atomic. This lets us not have to define it if the C2 is manually
+		// configured.
+		var shutdown atomic.Bool
+		shutdown.Store(false)
+		server.Shutdown = &shutdown
+	}
+
+	if server.HTTPS {
+		var ok bool
+		var err error
+		if len(server.CertificateFile) != 0 && len(server.PrivateKeyFile) != 0 {
+			server.Certificate, err = tls.LoadX509KeyPair(server.CertificateFile, server.PrivateKeyFile)
+			if err != nil {
+				output.PrintfFrameworkError("Error loading certificate: %s", err.Error())
+
+				return false
+			}
+		} else {
+			output.PrintFrameworkStatus("Certificate not provided. Generating a TLS Certificate")
+			server.Certificate, ok = encryption.GenerateCertificate()
+			if !ok {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+func (server *Server) Run() {
+	defer server.Shutdown.Store(true)
+	sockAddr := fmt.Sprintf("%s:%d", server.Addr, server.Port)
+	APIMux := http.NewServeMux()
+	// assign handlers
+	APIMux.HandleFunc("/startListener", startListener)
+	APIMux.HandleFunc("/stopListener", stopListener)
+	APIMux.HandleFunc("/getListeners", getListeners)
+	APIMux.HandleFunc("/status", getStatus)
+
+	// start the server in a go routine, can kill it with the serverHandle
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		if server.HTTPS {
+			output.PrintfFrameworkStatus("Starting HTTPS API Server on: %s", sockAddr)
+			tlsConfig := &tls.Config{
+				Certificates: []tls.Certificate{server.Certificate},
+				MinVersion:   tls.VersionSSL30, // TODO remove this probably
+			}
+			server.ServerHandle = http.Server{
+				Addr:      sockAddr,
+				TLSConfig: tlsConfig,
+				Handler:   APIMux,
+			}
+			_ = server.ServerHandle.ListenAndServeTLS("", "")
+		} else {
+			output.PrintfFrameworkStatus("Starting HTTP API Server on: %s", sockAddr)
+			server.ServerHandle = http.Server{
+				Addr:    sockAddr,
+				Handler: APIMux,
+			}
+			_ = server.ServerHandle.ListenAndServe()
+		}
+	}()
+
+	output.PrintFrameworkStatus("API server started successfully")
+	defer server.ServerHandle.Close() // TODO revist whether or not this needs to exist
+
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		for {
+			if server.Shutdown.Load() {
+				server.ServerHandle.Close()
+				wg.Done()
+
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}()
+
+	wg.Wait()
+}
+
+// func (server *Server) CreateFlags() {
+// if flag.Lookup("sslShellServer.PrivateKeyFile") == nil {
+// flag.StringVar(&shellServer.PrivateKeyFile, "sslShellServer.PrivateKeyFile", "", "A private key to use with the SSL server")
+// flag.StringVar(&shellServer.CertificateFile, "sslShellServer.CertificateFile", "", "The certificate to use with the SSL server")
+//}
+//}
diff --git a/api/listenermanager/listenermanager.go b/api/listenermanager/listenermanager.go
new file mode 100644
index 0000000..1c0f751
--- /dev/null
+++ b/api/listenermanager/listenermanager.go
@@ -0,0 +1,108 @@
+package listenermanager
+
+import (
+	_ "encoding/json" // for json var-naming
+
+	"github.com/google/uuid"
+	"github.com/vulncheck-oss/go-exploit/c2"
+	"github.com/vulncheck-oss/go-exploit/c2/channel"
+	"github.com/vulncheck-oss/go-exploit/api/types"
+	"github.com/vulncheck-oss/go-exploit/c2/simpleshell"
+	// "github.com/vulncheck-oss/go-exploit/c2/sslshell"
+	"github.com/vulncheck-oss/go-exploit/output"
+)
+
+var singleton *ListenerManager
+
+func GetInstance() *ListenerManager {
+	if singleton == nil {
+		singleton = new(ListenerManager)
+		singleton.ServersMap= make(map[string]c2.Interface)
+	}
+
+	return singleton
+}
+
+type ListenerManager struct {
+	// holds handles to servers
+	ServersMap map[string]c2.Interface
+}
+
+type GetListenersResponse struct {
+	UUID        string `json:"uuid"`
+	Ipaddr      string `json:"ipaddr"`
+	Port        int    `json:"port"`
+	Httpaddr    string `json:"httpaddr"`
+	Httpport    int    `json:"httpport"`
+	Active      bool   `json:"active"`
+	Hassessions bool   `json:"hassessions"`
+}
+
+func (manager *ListenerManager) StopListener(uuid string) bool {
+	var ok bool
+	for key, item := range manager.ServersMap {
+		if key == uuid {
+			output.PrintFrameworkDebug("Found UUID requested for shutdown")
+			ok = item.Shutdown()
+			if ok {
+				break
+			}
+		}
+	}
+
+	if ok {
+		delete(manager.ServersMap, uuid)
+
+		return true
+	}
+
+	output.PrintFrameworkError("Could not shutdown server, failed to find UUID")
+
+	return false
+}
+
+func (manager *ListenerManager) GetListeners() ([]GetListenersResponse, bool) {
+	//nolint:prealloc
+	var retArray []GetListenersResponse
+
+	for key, item := range manager.ServersMap {
+		retArray = append(retArray, GetListenersResponse{
+			UUID:        key,
+			Ipaddr:      item.Channel().IPAddr,
+			Port:        item.Channel().Port,
+			Httpaddr:    item.Channel().HTTPAddr,
+			Httpport:    item.Channel().HTTPPort,
+			Hassessions: item.Channel().HasSessions(),
+			Active:      !item.Channel().Shutdown.Load(),
+		})
+	}
+
+	return retArray, true
+}
+
+func (manager *ListenerManager) StartListener(serverType string, serverChan channel.Channel) (string, bool) {
+	var ok bool
+	switch serverType {
+	case types.SimpleShellServer:
+		serverStruct := new(simpleshell.Server)
+		ok = serverStruct.Init(&serverChan)
+		if !ok {
+			output.PrintFrameworkError("Failed to init simple shell server")
+
+			return "", false
+		}
+		go serverStruct.Run(serverChan.Timeout)
+		// TODO put additional metadata in the map like cve that called it
+		serverUUID := uuid.New().String()
+		manager.ServersMap[serverUUID] = serverStruct
+		return serverUUID, true
+	case types.SSLShellServer:
+		output.PrintFrameworkError("Not implemented") // TODO make this one
+
+		return "", false
+	default:
+		output.PrintFrameworkError("CreateListener called with invalid listenertype")
+
+		return "", false
+	}
+}
diff --git a/api/testapi/test.go b/api/testapi/test.go
new file mode 100644
index 0000000..6cd081a
--- /dev/null
+++ b/api/testapi/test.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"github.com/vulncheck-oss/go-exploit/c2/cli"
+	"github.com/vulncheck-oss/go-exploit/api/http"
+	"github.com/vulncheck-oss/go-exploit/api/listenermanager"
+)
+
+func main() {
+	http.GetInstance().Init("127.0.0.1", 2828)
+	go http.GetInstance().Run()
+	lManager := listenermanager.GetInstance()
+	for {
+	}
+}
diff --git a/api/types/types.go b/api/types/types.go
new file mode 100644
index 0000000..fd7a016
--- /dev/null
+++ b/api/types/types.go
@@ -0,0 +1,17 @@
+package types
+
+// this package exists to hold constants for expected api key/value
+
+// keys for api requests
+const (
+	ServerType    = "serverType"
+	ServerAddr    = "serverAddr"
+	ServerPort    = "serverPort"
+	ServerTimeout = "serverTimeout"
+)
+
+// expected values for api requests
+const (
+	SimpleShellServer = "simpleshellserver"
+	SSLShellServer    = "sslshellserver"
+)
diff --git a/c2/channel/channel.go b/c2/channel/channel.go
index 3e51a33..54ecde2 100644
--- a/c2/channel/channel.go
+++ b/c2/channel/channel.go
@@ -15,16 +15,17 @@ import (
 )
 
 type Channel struct {
-	IPAddr   string
-	HTTPAddr string
-	Port     int
-	HTTPPort int
-	Timeout  int
-	IsClient bool
-	Shutdown *atomic.Bool
-	Sessions map[string]Session
-	Input    io.Reader
-	Output   io.Writer // Currently unused but figured we'd add it ahead of time
+	IPAddr     string
+	HTTPAddr   string
+	Port       int
+	HTTPPort   int
+	Timeout    int
+	IsClient   bool
+	Shutdown   *atomic.Bool
+	Sessions   map[string]Session
+	Input      io.Reader
+	Output     io.Writer // Currently unused but figured we'd add it ahead of time
+	Managed bool// for use by listener manager, to signal to listener to spawn new connection into cli.Managed instead of cli.Basic
 }
 
 type Session struct {
@@ -33,6 +34,10 @@ type Session struct {
 	conn           *net.Conn
 }
 
+func (session *Session) Conn() *net.Conn {
+	return session.conn
+}
+
 // HasSessions checks if a channel has any tracked sessions. This can be used to lookup if a C2
 // successfully received callbacks:
 //
diff --git a/c2/cli/managed.go b/c2/cli/managed.go
new file mode 100644
index 0000000..5f10279
--- /dev/null
+++ b/c2/cli/managed.go
@@ -0,0 +1,123 @@
+package cli
+
+import (
+	"bufio"
+	"net"
+	"strings"
+	"os"
+	"sync"
+	"testing"
+	"time"
+	"sync/atomic"
+
+	"github.com/vulncheck-oss/go-exploit/c2/channel"
+	"github.com/vulncheck-oss/go-exploit/output"
+	"github.com/vulncheck-oss/go-exploit/protocol"
+)
+
+var sessionEnded atomic.Bool
+
+// backgroundResponse handles the network connection reading for response data.
+func managedBackgroundResponse(ch *channel.Channel, wg *sync.WaitGroup, conn net.Conn, responseCh chan string) {
+	defer wg.Done()
+	responseBuffer := make([]byte, 1024)
+	for {
+		if ch.Shutdown.Load() || sessionEnded.Load() {
+			return
+		}
+		time.Sleep(10 * time.Millisecond)
+
+		err := conn.SetReadDeadline(time.Now().Add(1 * time.Second))
+		if err != nil {
+			output.PrintfFrameworkError("Error setting read deadline: %s, exiting.", err)
+
+			return
+		}
+
+		bytesRead, err := conn.Read(responseBuffer)
+		if err != nil && !os.IsTimeout(err) {
+			// things have gone sideways, but the command line won't know that
+			// until they attempt to execute a command and the socket fails.
+			// i think that's largely okay.
+			return
+		}
+
+		if bytesRead > 0 {
+			// I think there is technically a race condition here where the socket
+			// could have move data to write, but the user has already called exit
+			// below. I that that's tolerable for now.
+			responseCh <- string(responseBuffer[:bytesRead])
+		}
+	}
+}
+
+// does most of what cli.Basic does but does not trigger the shutdowns of the associated server
+func Managed(conn net.Conn, ch *channel.Channel) {
+	// Create channels for communication between goroutines.
+	responseCh := make(chan string)
+	sessionEnded.Store(false)
+
+	// Use a WaitGroup to wait for goroutines to finish.
+	var wg sync.WaitGroup
+
+	// Goroutine to read responses from the server.
+	wg.Add(1)
+
+	// If running in the test context inherit the channel input setting, this will let us control the
+	// input of the shell programmatically.
+	if !testing.Testing() {
+		ch.Input = os.Stdin
+	}
+	go managedBackgroundResponse(ch, &wg, conn, responseCh)
+
+	// Goroutine to handle responses and print them.
+	wg.Add(1)
+	go func(channel *channel.Channel) {
+		defer wg.Done()
+		for {
+			if channel.Shutdown.Load() || sessionEnded.Load() {
+				return
+			}
+			select {
+			case response := <-responseCh:
+				output.PrintShell(response)
+			default:
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}(ch)
+
+	go func(channel *channel.Channel) {
+		// no waitgroup for this one because blocking IO, but this should not matter
+		// since we are intentionally not trying to be a multi-implant C2 framework.
+		// There still remains the issue that you would need to hit enter to find out
+		// that the socket is dead but at least we can stop Basic() regardless of this fact.
+		// This issue of unblocking stdin is discussed at length here https://github.com/golang/go/issues/24842
+		for {
+			reader := bufio.NewReader(ch.Input)
+			command, _ := reader.ReadString('\n')
+			if channel.Shutdown.Load() {
+				break
+			}
+			if strings.TrimSpace(command) == "" {
+				continue
+			}
+			if command == "exit\n" || command == "exit" {
+				sessionEnded.Store(true)
+
+				break
+			}
+			ok := protocol.TCPWrite(conn, []byte(command))
+			if !ok {
+				sessionEnded.Store(true)
+
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}(ch)
+
+	// wait until the go routines are clean up
+	wg.Wait()
+	close(responseCh)
+}
diff --git a/c2/simpleshell/simpleshellserver.go b/c2/simpleshell/simpleshellserver.go
index c974bd0..175bac6 100644
--- a/c2/simpleshell/simpleshellserver.go
+++ b/c2/simpleshell/simpleshellserver.go
@@ -129,7 +129,9 @@ func (shellServer *Server) Run(timeout int) {
 		// Add the session for tracking first, so it can be cleaned up.
 		shellServer.Channel().AddSession(&client, client.RemoteAddr().String())
 
-		go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel)
+		if !shellServer.channel.Managed {
+			go handleSimpleConn(client, &cliLock, client.RemoteAddr(), shellServer.channel)
+		}
 	}
 }
 
diff --git a/cli/commandline.go b/cli/commandline.go
index 258ea06..dafa2a1 100644
--- a/cli/commandline.go
+++ b/cli/commandline.go
@@ -404,6 +404,13 @@ func exploitFunctionality(conf *config.Config) {
 	flag.BoolVar(&conf.DoExploit, "e", false, "Exploit the target")
 }
 
+// command line flags that control API server configuration
+func apiFlags(conf *config.Config) {
+	flag.BoolVar(&conf.UseAPI, "useapi", false, "Use API server. Will attempt to connect to 127.0.0.1:2828 if -apiport or -apiaddr are not provided")
+	flag.IntVar(&conf.APIPort, "apiport", 2828, "Specify the port of the running API server")
+	flag.StringVar(&conf.APIAddr, "apiaddr", "127.0.0.1", "Specify the ip address or hostname of the running API server")
+}
+
 // command line flags that control ssl communication with the target.
 func sslFlags(conf *config.Config) {
 	flag.BoolVar(&conf.SSL, "s", false, "Use https to communicate with the target")
@@ -547,6 +554,7 @@ func CodeExecutionCmdLineParse(conf *config.Config) bool {
 	localHostFlags(conf)
 	exploitFunctionality(conf)
 	sslFlags(conf)
+	apiFlags(conf)
 	c2Flags(&c2Selection, conf)
 	detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit")
 
@@ -612,6 +620,7 @@ func InformationDisclosureCmdLineParse(conf *config.Config) bool {
 	localHostFlags(conf)
 	exploitFunctionality(conf)
 	sslFlags(conf)
+	apiFlags(conf)
 	detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit")
 
 	flag.Usage = func() {
@@ -654,6 +663,7 @@ func WebShellCmdLineParse(conf *config.Config) bool {
 	localHostFlags(conf)
 	exploitFunctionality(conf)
 	sslFlags(conf)
+	apiFlags(conf)
 	detailsFlag := flag.Bool("details", false, "Print the implementation details for this exploit")
 
 	flag.Usage = func() {
diff --git a/cmd/cli/cmd.go b/cmd/cli/cmd.go
new file mode 100644
index 0000000..14cfad8
--- /dev/null
+++ b/cmd/cli/cmd.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+	"os/exec"
+	"bufio"
+	"strings"
+	"os"
+	"fmt"
+
+	"github.com/vulncheck-oss/go-exploit/cmd/commands/search"
+	"github.com/vulncheck-oss/go-exploit/cmd/commands/use"
+	"github.com/vulncheck-oss/go-exploit/cmd/commands/sessions"
+	"github.com/vulncheck-oss/go-exploit/api/http"
+	"github.com/confluentinc/go-prompt"
+)
+
+var ps1 = "> "
+
+func mainCommandDispatch(input string) {
+	input = strings.TrimSpace(input)
+	parts := strings.Split(input, " ")
+
+	if len(parts) == 0 {
+		return
+	}
+
+	switch parts[0] {
+	case "sessions":
+		sessions.CallSessions(parts)
+	case "search":
+		search.CallSearch(parts)
+	case "use":
+		use.CallUse(parts)
+	default:
+		if parts[0] == "" {
+			return
+		}
+		fmt.Println("Unknown command:", parts[0])
+	}
+}
+
+func mainCompleter(d prompt.Document) []prompt.Suggest {
+	s := []prompt.Suggest{
+		{Text: "sessions", Description: sessions.Description},
+		{Text: "use", Description: use.Description},
+		{Text: "exit", Description: "Exit this interface"},
+	}
+
+	return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
+}
+
+// ensures the terminal goes back to a working state when finished.
+func handleExit() {
+	rawModeOff := exec.Command("/bin/stty", "-raw", "echo")
+	rawModeOff.Stdin = os.Stdin
+	_ = rawModeOff.Run()
+	_ = rawModeOff.Wait()
+}
+
+func cliExit() {
+	fmt.Println("exiting...")
+	handleExit()
+	os.Exit(0)
+}
+
+func main() {
+	http.GetInstance().Init("127.0.0.1", 2828)
+	go http.GetInstance().Run()
+
+	fmt.Println("VULNCHECK GO-EXPLOIT CLI")
+	for {
+		// input := prompt.Input(ps1, mainCompleter) // not working, weirdly messing up all I/O, or so I suspect
+		reader := bufio.NewReader(os.Stdin)
+
+		input, err := reader.ReadString('\n')
+		if err != nil {
+			fmt.Println("Error reading input:", err)
+			return
+		}
+		if input == "exit" {
+			break
+		}
+		mainCommandDispatch(input)
+	}
+	cliExit()
+}
diff --git a/cmd/commands/sessions/sessions.go b/cmd/commands/sessions/sessions.go
new file mode 100644
index 0000000..2eeade3
--- /dev/null
+++ b/cmd/commands/sessions/sessions.go
@@ -0,0 +1,79 @@
+package sessions
+
+import (
+	"fmt"
+	"github.com/vulncheck-oss/go-exploit/c2/cli"
+	"github.com/vulncheck-oss/go-exploit/api/listenermanager"
+
+)
+
+var usage = `Usage:
+sessions -l - list all active sessions with their ids
+sessions -i <id> - interact with <id> session
+sessions -k <id> - kill <id> session
+`
+
+var Description = "Manage and interact with active sessions"
+
+
+func interact(args []string) {
+	if len(args) != 3 {
+		fmt.Println("ERROR: Invalid number of args for session interact command")
+		fmt.Println(usage)
+
+		return
+	}
+
+	id := args[2]
+
+	conn, channel, ok := listenermanager.GetSessionByID(id)
+	if !ok {
+		fmt.Println("ERROR: Failed to get session from manager using provided id")
+
+		return
+	}
+	fmt.Println("Session retrieved, dropping into shell...")
+	cli.Managed(*conn, channel)
+}
+
+func kill(args []string){
+	if len(args) != 3 {
+		fmt.Println("ERROR: Invalid number of args for session kill command")
+		fmt.Println(usage)
+
+		return
+	}
+
+	// id := args[2]
+}
+
+func list(){
+	sessionMap := listenermanager.GetSessions()
+	if len(sessionMap) == 0 {
+		fmt.Println("No sessions available")
+
+		return
+	}
+	for id, session := range sessionMap {
+		fmt.Printf("%s - %s - %s", id, session.RemoteAddr, session.ConnectionTime)
+	}
+}
+
+func CallSessions(args []string){
+	if len(args) < 2 {
+		fmt.Println("ERROR: Invalid number of args")
+		fmt.Println(usage)
+
+		return
+	}
+	switch args[1] {
+	case "-l":
+		list()
+	case "-k":
+		kill(args)
+	case "-i":
+		interact(args)
+	default:
+		fmt.Println(usage)
+	}
+}
diff --git a/config/config.go b/config/config.go
index 3559f1b..4b805ae 100644
--- a/config/config.go
+++ b/config/config.go
@@ -114,6 +114,12 @@ type Config struct {
 	FileTemplateData string
 	// File format exploit output
 	FileFormatFilePath string
+	// Disables use of the API for server spawning
+	UseAPI bool
+	// Port of the currently running API server
+	APIPort int
+	// IP ADDR of the currently running API server
+	APIAddr string
 }
 
 // Convert ExploitType to String.
diff --git a/framework.go b/framework.go
index ea02fc3..4cb2fe9 100644
--- a/framework.go
+++ b/framework.go
@@ -70,7 +70,9 @@ import (
 	"sync"
 	"sync/atomic"
 	"time"
+	"net/http"
 
+	"github.com/Masterminds/semver"
 	"github.com/vulncheck-oss/go-exploit/c2"
 	"github.com/vulncheck-oss/go-exploit/c2/channel"
 	"github.com/vulncheck-oss/go-exploit/cli"
@@ -78,6 +80,7 @@ import (
 	"github.com/vulncheck-oss/go-exploit/db"
 	"github.com/vulncheck-oss/go-exploit/output"
 	"github.com/vulncheck-oss/go-exploit/protocol"
+	"github.com/vulncheck-oss/go-exploit/api/client"
 )
 
 // The return type for CheckVersion().
@@ -306,6 +309,13 @@ func startC2Server(conf *config.Config) bool {
 			IsClient: false,
 			Shutdown: &shutdown,
 		}
+
+		if isAPIServerPresent(conf){
+			output.PrintFrameworkStatus("API server detected, will spawn or use listener there")
+
+			return client.StartListener(conf, c2channel)
+		}
+
 		// Handle the signal interrupt channel. If the signal is triggered, then trigger the done
 		// channel which will clean up the server and close cleanly.
 		go func(sigint <-chan os.Signal, channel *channel.Channel) {
@@ -428,6 +438,27 @@ func StoreVersion(conf *config.Config, version string) {
 	db.UpdateVerified(conf.Product, true, version, conf.Rhost, conf.Rport)
 }
 
+// Compare a version to a semantic version constraint using the [Masterminds semver constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints).
+// Provide a version string and a constraint and if the semver is within the constraint a boolean
+// response of whether the version is constrained or not will occur. Any errors from the constraint
+// or version will propagate through the framework errors and the value will be false.
+func CheckSemVer(version string, constraint string) bool {
+	c, err := semver.NewConstraint(constraint)
+	if err != nil {
+		output.PrintfFrameworkError("Invalid constraint: %s", err.Error())
+
+		return false
+	}
+	v, err := semver.NewVersion(version)
+	if err != nil {
+		output.PrintfFrameworkError("Invalid version: %s", err.Error())
+
+		return false
+	}
+
+	return c.Check(v)
+}
+
 // modify godebug to re-enable old cipher suites that were removed in 1.22. This does have implications for our
 // client fingerprint, and we should consider how to improve/fix that in the future. We also should be respectful
 // of other disabling this feature, so we will check for it before re-enabling it.
@@ -504,3 +535,31 @@ func RunProgram(sploit Exploit, conf *config.Config) {
 		}
 	}
 }
+
+func isAPIServerPresent(conf *config.Config) bool {
+	if !conf.UseAPI {
+		output.PrintFrameworkDebug("-useAPI flag not set, skipping API server check")
+
+		return false
+	}
+	if conf.APIAddr == "" {
+		conf.APIAddr = "127.0.0.1" // Default API Address
+	}
+	if conf.APIPort == 0 {
+		conf.APIPort = 2828 // Default API Port
+	}
+
+	url := protocol.GenerateURL(conf.APIAddr, conf.APIPort, conf.SSL, "/status")
+	resp, body, ok := protocol.HTTPSendAndRecv("GET", url, "")
+	if !ok {
+		return false
+	}
+
+	if resp.StatusCode != http.StatusOK {
+		output.PrintfFrameworkDebug("Received invalid status code from possible API server: %d", resp.StatusCode)
+
+		return false
+	}
+
+	return strings.Contains(body, "go-exploit-server-status: good")
+}
diff --git a/framework_test.go b/framework_test.go
new file mode 100644
index 0000000..0c79c48
--- /dev/null
+++ b/framework_test.go
@@ -0,0 +1,34 @@
+package exploit_test
+
+import (
+	"testing"
+
+	"github.com/vulncheck-oss/go-exploit"
+)
+
+func TestCheckSemVer_Full(t *testing.T) {
+	if !exploit.CheckSemVer("1.0.0", "<= 1.0.0") {
+		t.Error("Constraint should have passed")
+	}
+	if exploit.CheckSemVer("1.0.0", "> 1.0.0") {
+		t.Error("Constraint should not have passed")
+	}
+}
+
+func TestCheckSemVer_BadVersion(t *testing.T) {
+	if exploit.CheckSemVer("uwu", "<= 1.0.0") {
+		t.Error("Version was invalid, should not have passed")
+	}
+	if exploit.CheckSemVer("1.0.0 ", "<= 1.0.0") {
+		t.Error("Version was invalid, should not have passed")
+	}
+}
+
+func TestCheckSemVer_BadConstraint(t *testing.T) {
+	if exploit.CheckSemVer("1.0.0", "<== 1.0.0") {
+		t.Error("Constraint was invalid, should not have passed")
+	}
+	if exploit.CheckSemVer("1.0.0", "xp") {
+		t.Error("Constraint was invalid, should not have passed")
+	}
+}
diff --git a/go.mod b/go.mod
index 023522b..eb206e0 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,9 @@ module github.com/vulncheck-oss/go-exploit
 go 1.24.1
 
 require (
+	github.com/Masterminds/semver v1.5.0
+	github.com/confluentinc/go-prompt v1.0.0
+	github.com/google/uuid v1.6.0
 	github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8
 	github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906
 	golang.org/x/crypto v0.38.0
@@ -13,9 +16,12 @@ require (
 
 require (
 	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/google/uuid v1.6.0 // indirect
+	github.com/mattn/go-colorable v0.1.7 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/mattn/go-runewidth v0.0.9 // indirect
+	github.com/mattn/go-tty v0.0.3 // indirect
 	github.com/ncruces/go-strftime v0.1.9 // indirect
+	github.com/pkg/term v1.2.0-beta.2 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 	golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
 	golang.org/x/sys v0.33.0 // indirect
diff --git a/go.sum b/go.sum
index 28329c7..7c9c42a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,9 @@
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/confluentinc/go-prompt v1.0.0 h1:sxSWhXIkgMYRhirGoMcfr5okSlBjDeYxF/wcpcoWs/w=
+github.com/confluentinc/go-prompt v1.0.0/go.mod h1:4/tf63YzhSsiXnsKosCmv1H0ivpXVezZHaMpSVB+DXw=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
@@ -7,12 +13,29 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
 github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfKuoM3/FmJpt8M+w0fOy4wKneBmc=
 github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
+github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw=
+github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906 h1:qHFp1iRg6qE8xYel3bQT9x70pyxsdPLbJnM40HG3Oig=
 github.com/vjeantet/ldapserver v1.0.2-0.20240305064909-a417792e2906/go.mod h1:YvUqhu5vYhmbcLReMLrm/Tq3S7Yj43kSVFvvol6Lh6k=
 golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
@@ -23,8 +46,15 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
 golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
 golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
 golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
 golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
@@ -32,6 +62,8 @@ golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
 golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
 golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
 golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
 modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
 modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
@@ -56,3 +88,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
 modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA=
+pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
diff --git a/wrappers/wrappers.go b/wrappers/wrappers.go
new file mode 100644
index 0000000..671c096
--- /dev/null
+++ b/wrappers/wrappers.go
@@ -0,0 +1,68 @@
+package wrappers
+
+import(
+	"regexp"
+	"net/http"
+
+	"github.com/vulncheck-oss/go-exploit"
+	"github.com/vulncheck-oss/go-exploit/config"
+	"github.com/vulncheck-oss/go-exploit/output"
+	"github.com/vulncheck-oss/go-exploit/protocol"
+	"github.com/vulncheck-oss/go-exploit/c2/channel"
+	"github.com/vulncheck-oss/go-exploit/c2/httpservefile"
+)
+
+// A collection of wrappers/helper functions that have otherwise no place to go and may need to be in their own packages
+// to avoid certain import cycle errors.
+
+// Helper function for use within exploits to reduce the overall amount of boilerplate when setting up a file server to host a dynamically generated file.
+func HTTPServeFileInitAndRunWithFile(conf *config.Config, fileName string, routeName string, data[]byte) bool {
+	httpServer := httpservefile.GetInstance()
+
+	if httpServer.HTTPAddr == "" {
+		httpServer.HTTPAddr = conf.Lhost
+	}
+
+	if !httpServer.Init(&channel.Channel{HTTPAddr: httpServer.HTTPAddr, HTTPPort: httpServer.HTTPPort}) {
+		output.PrintFrameworkError("Could not start http server")
+
+		return false
+	}
+
+	httpServer.AddFile(fileName, routeName, data)
+
+	go httpServer.Run(conf.C2Timeout)
+
+	return true
+}
+
+// This removes generic version checking boiler plate that works in a great deal of cases.
+// Of course it will not work in every case but that is not the point.
+func SimpleVersionCheck(conf *config.Config, rePattern string, versionConstraint string) exploit.VersionCheckType {
+    url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
+	resp, body, ok := protocol.HTTPGetCache(url)
+    if !ok || resp == nil {
+        return exploit.Unknown
+    }
+
+    if resp.StatusCode != http.StatusOK {
+		output.PrintfFrameworkError("Version check failed: Unexpected response code during initial GET request: %d", resp.StatusCode)
+
+        return exploit.Unknown
+    }
+
+    matches := regexp.MustCompile(rePattern).FindStringSubmatch(body)
+    if len(matches) < 2 {
+		output.PrintFrameworkError("Version check failed: No matches found for the provided pattern")
+
+        return exploit.Unknown
+    }
+
+	exploit.StoreVersion(conf, matches[1])
+
+	if exploit.CheckSemVer(matches[1], versionConstraint) {
+		return exploit.Vulnerable
+	}
+
+	return exploit.NotVulnerable
+}