
I believe reading the code is the best way to understand the tactics used by
the coder to drive the machine from moment to moment. However, I think that
this is insufficient for understanding the overall strategy implemented by 
the coder to approach the problem. This document is therefore intended as
an overview of the revsh codebase from a high level, showing how all of the
different files and functions fit together.

################################################################################
# Basic Strategy
################################################################################

The basic strategy is pretty simple. I've implemented a message bus style 
messaging system for passing "messages" between the control node and the target
node. These messages can be of many different types, all defined in the
protocol.h header file. 

The main work is done in the broker() loop that does select() based
non-blocking io. The file-descriptor (fd) for the tty has priority. Next in
priority is the message bus fd. After that the fds for socks listeners or
established connections (including tun/tap virtual networking interfaces) are
handled in a loop. These connections live in a linked list with a primitive
form of round-robin scheduling.

That's it. 


Some design decisions should be explained:
	- I decided to use select() because we are hackers and our tools need to work
	  on the broadest range of machines.
	- I decided on a single threaded application for the same reason.
	- I decided on a single connection between control and target for stealth.
	- I decided to use OpenSSL because it is available on the broadest set of 
	  machines. However, the version differences are still a pain to deal with
	  so we end up always building statically. I may try to switch to LibreSSL
	  at some point. I don't rule out writing my own crypto either (though I'd
	  prefer not to.) Also, Anonymous Diffie-Hellman is useful for us here but
	  out of favor generally. As such, OpenSSL is one of the only libraries to
	  support it.



################################################################################
# revsh
################################################################################

# Initialization

- revsh.c
	-- main()
		--- Perform basic initialization.
		--- Calls the appropriate conductor, either do_control() or do_target() as
		    appropriate.
	-- clean_io() tries to do a proper cleanup of the io struct so we can do 
	   another loop in keepalive mode. There is a better way to do this, but this
	   is how legacy happens. I'll clean it up someday, but for now it works.

# Conductors

- control.c
	-- do_control()
		--- Acts as the conductor for control nodes.
		--- Calls init_io_control() to setup the io layer.
		--- Calls negotiate_protocol() to setup the messaging layer.
		--- Talks with the remote partner to synchronize the revsh environment.
		--- Calls broker().
		--- After broker() completes, performs cleanup and exits.

- target.c
	-- do_target()
		--- Acts as the conductor for target nodes.
		--- Calls init_io_target() to setup the io layer.
		--- Calls negotiate_protocol() to set up the messaging layer.
		--- Talks with the remote partner to synchronize the revsh environment.
		--- Sets up a pseudoterminal.
		--- fork()s a child which exec()s a shell.
		--- uses remote_printf() to send back info about the target system.
		--- Calls broker().
		--- After broker() completes, performs cleanup and exits.


# Broker Loop

- broker.c
	-- broker() runs the show.
	-- Uses message_pull() and message_push() to read and write data.
	-- Reads data from either the local or remote file descriptors, then writes it
	   down the other.
	-- Local data falls into three categories:
		--- Data to/from the local stdin connection (tty / shell).
		--- Data from a local proxy listener. (This data is used to create a data
		    connection.)
		--- Data to / from an established data connection.
	-- Remote data is only of one type:
		--- Data to / from the message bus.
	-- Calls the appropriate "handler" function to handle the case it's landed
	   in.

- handler.c
	-- Contains code to "handle" all of the various cases found within the
	   broker() loop.
	-- Everything in handler.c was once flat inside the broker() loop. This was
	   a giant mess. It was broken out, case by case, into it's own file. Things
	   are much cleaner now.
	-- Naming convention for these functions are "handle_FROM-WHERE_DO-WHAT()".
		--- E.g. handle_message_dt_proxy_ht_create() will handle data:
			---- Coming from the message bus.
			---- Requesting the creation of a corresponding proxy node.
	-- For the full list of handler functions, see the common.h header file.


# Messaging Layer

- message.c
	-- messages are atomic and have priority over all other socket activity.
		--- This is achieved with a second select() call inside of the messaging
		    code. Because everything relies on the message bus, if messages can't be
		    sent / received, then nothing else happens until they can.
	-- message_pull()
		--- Uses remote_read() to pull data from the socket, and fills out the
			appropriate message data structures.
	-- message_push()
		--- Reads from the appropriate message data structures, and uses
			remote_write() to push data down the socket.


# IO Layer

- io.c
	-- Includes either the io_ssl.c or io_nossl.c code, as appropriate.

- io_ssl.c / io_nossl.c
	-- remote_read_plaintext()
		--- read() from our socket, w/out SSL involvement.
	-- remote_write_plaintext()
		--- write() to our socket, w/out SSL involvement.
	-- remote_read_encrypted()
		--- read() from our socket, w/SSL involvement.
	-- remote_write_encrypted()
		--- write() to our socket, w/SSL involvement.
	-- init_io_control()
		--- Setup the network layer for the control node.
	-- init_io_target()
		--- Setup the network layer for the target node.

Note: the remote_read_*() and remote_write_*() functions are called through
      function pointers named remote_read() and remote_write() which are setup
      during initialization.


# Proxy Support

- proxy.c
	-- Contains helper code related to the establishing and tearing down of
	   proxies and their data connections.


# Reporting

- report.c
	-- report_error()
		--- First, regardless of which end calls this, if we are verbose, print the
		    error to the local fd acting as stderr.
		--- Then, if were are control and logging, drop the error to the log file.
		--- If we are target, send a DT_ERROR message back to the control to make
		    the decision to log or not log.
	-- report_log()
		--- Called by report_error() to handle the logging component.
		--- Called directly by the rest of the code in situations where we want to
		    log an even that isn't really an error. (e.g. successful connections)


# Escape Sequence Processing

- escseq.c
	-- escape_check()
		--- This is the primary function for processing escape sequences from the
		    tty. It is semi-recursive in order to handle potential escape sequences
		    surrounded by normal non-escape related input. It handles a chunk of
		    data appropriately, regardless of message or escape data, then shifts
		    the remaining message buffer, then calls itself to handle to
		    remaining message data.
	-- The remaining functions either assist in the consume -> shift -> recurse
	   model or handle the payload of a valid sequence command. 

Note: I have kept the supported escape sequences to be a strict subset of the
      ssh escape sequences. These should, in theory, always interact well 
      with each other.


# Misc. Headers

- common.h
	-- A header file with all the common defines and includes.

- protocol.h
	-- A header file that defines the underlying messaging protocol.

- config.h
	-- A header file with all the defines that the user will probably want to
	   tweek to fit their own case.

- helper.h
	-- A header file to define the "helper" objects.
		--- These are just structs that allow for organization of the topics they
		    help with.
			---- E.g. struct message_helper is an object that encases everything
			     needed when dealing with passing a message over the message bus. 
	-- The io_helper struct is invoked once and represents the state of the I/O
	   at any given point. The code started out life as a simple tty broker
	   pass through. As it grew, it became much more complex and became a giant
	   state machine. This state is kept inside the single io_helper struct and
	   is global. I really don't like globals, but this was a case where it made
	   sense. As an I/O application, all of the code needs to know the state of
	   the machine at any given moment. Also, making this global greatly cleaned
	   up the readability of the code.


################################################################################
# in_the_key_of_c
################################################################################

- in_the_key_of_c.c
	-- During initial compilation, takes RSA keypair data and converts it into C
	   source code representations.
		--- This is useful for including the keypair data in the revsh source code,
		    and baking the crypto into the binary. (This is a bad tactic for
		    enterprise software. This is a very useful tactic for a "hostile"
		    binary.)

