This is a fork of: https://github.com/roglew/puppy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

585 lines
14 KiB

package puppy
import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/deckarep/golang-set"
)
const (
ProxyStopped = iota
ProxyStarting
ProxyRunning
)
var getNextConnId = IdCounter()
var getNextListenerId = IdCounter()
type internalAddr struct{}
func (internalAddr) Network() string {
return "<internal network>"
}
func (internalAddr) String() string {
return "<internal connection>"
}
/*
ProxyConn which is the same as a net.Conn but implements Peek() and variales to store target host data
*/
type ProxyConn interface {
net.Conn
Id() int
Logger() *log.Logger
// Set the CA certificate to be used to sign TLS connections
SetCACertificate(*tls.Certificate)
// If the connection tries to start TLS, attempt to strip it so that further reads will get the decrypted text, otherwise it will just pass the plaintext
StartMaybeTLS(hostname string) (bool, error)
// Have all requests produced by this connection have the given destination information. Removes the need for requests generated by this connection to be aware they are being submitted through a proxy
SetTransparentMode(destHost string, destPort int, useTLS bool)
// End transparent mode
EndTransparentMode()
}
type proxyAddr struct {
Host string
Port int // can probably do a uint16 or something but whatever
UseTLS bool
}
type proxyConn struct {
Addr *proxyAddr
logger *log.Logger
id int
conn net.Conn // Wrapped connection
readReq *http.Request // A replaced request
caCert *tls.Certificate
mtx sync.Mutex
transparentMode bool
}
// Encode the destination information to be stored in the remote address
func EncodeRemoteAddr(host string, port int, useTLS bool) string {
var tlsInt int
if useTLS {
tlsInt = 1
} else {
tlsInt = 0
}
return fmt.Sprintf("%s/%d/%d", host, port, tlsInt)
}
// Decode destination information from a remote address
func DecodeRemoteAddr(addrStr string) (host string, port int, useTLS bool, err error) {
parts := strings.Split(addrStr, "/")
if len(parts) != 3 {
err = fmt.Errorf("Error parsing addrStr: %s", addrStr)
return
}
host = parts[0]
port, err = strconv.Atoi(parts[1])
if err != nil {
return
}
useTLSInt, err := strconv.Atoi(parts[2])
if err != nil {
return
}
if useTLSInt == 0 {
useTLS = false
} else {
useTLS = true
}
return
}
func (a *proxyAddr) Network() string {
return EncodeRemoteAddr(a.Host, a.Port, a.UseTLS)
}
func (a *proxyAddr) String() string {
return EncodeRemoteAddr(a.Host, a.Port, a.UseTLS)
}
//// bufferedConn and wrappers
type bufferedConn struct {
reader *bufio.Reader
net.Conn // Embed conn
}
func (c bufferedConn) Peek(n int) ([]byte, error) {
return c.reader.Peek(n)
}
func (c bufferedConn) Read(p []byte) (int, error) {
return c.reader.Read(p)
}
//// Implement net.Conn
func (c *proxyConn) Read(b []byte) (n int, err error) {
if c.readReq != nil {
buf := new(bytes.Buffer)
c.readReq.Write(buf)
s := buf.String()
n = 0
for n = 0; n < len(b) && n < len(s); n++ {
b[n] = s[n]
}
c.readReq = nil
return n, nil
}
if c.conn == nil {
return 0, fmt.Errorf("ProxyConn %d does not have an active connection", c.Id())
}
return c.conn.Read(b)
}
func (c *proxyConn) Write(b []byte) (n int, err error) {
return c.conn.Write(b)
}
func (c *proxyConn) Close() error {
return c.conn.Close()
}
func (c *proxyConn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
func (c *proxyConn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
func (c *proxyConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func (c *proxyConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *proxyConn) RemoteAddr() net.Addr {
// RemoteAddr encodes the destination server for this connection
return c.Addr
}
//// Implement ProxyConn
func (pconn *proxyConn) Id() int {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
return pconn.id
}
func (pconn *proxyConn) Logger() *log.Logger {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
return pconn.logger
}
func (pconn *proxyConn) SetCACertificate(cert *tls.Certificate) {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
pconn.caCert = cert
}
func (pconn *proxyConn) StartMaybeTLS(hostname string) (bool, error) {
// Prepares to start doing TLS if the client starts. Returns whether TLS was started
// Wrap the ProxyConn's net.Conn in a bufferedConn
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
bufConn := bufferedConn{bufio.NewReader(pconn.conn), pconn.conn}
usingTLS := false
// Guess if we're doing TLS
byte, err := bufConn.Peek(1)
if err != nil {
return false, err
}
if byte[0] == '\x16' {
usingTLS = true
}
if usingTLS {
if err != nil {
return false, err
}
cert, err := signHost(*pconn.caCert, []string{hostname})
if err != nil {
return false, err
}
config := &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
ServerName: hostname,
}
tlsConn := tls.Server(bufConn, config)
pconn.conn = tlsConn
return true, nil
} else {
pconn.conn = bufConn
return false, nil
}
}
func (pconn *proxyConn) SetTransparentMode(destHost string, destPort int, useTLS bool) {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
pconn.Addr = &proxyAddr{Host: destHost,
Port: destPort,
UseTLS: useTLS,
}
pconn.transparentMode = true
}
func (pconn *proxyConn) EndTransparentMode() {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
pconn.transparentMode = false
}
func newProxyConn(c net.Conn, l *log.Logger) *proxyConn {
// converts a connection into a proxyConn
a := proxyAddr{Host: "", Port: -1, UseTLS: false}
p := proxyConn{Addr: &a, logger: l, conn: c, readReq: nil}
p.id = getNextConnId()
p.transparentMode = false
return &p
}
func (pconn *proxyConn) returnRequest(req *http.Request) {
pconn.mtx.Lock()
defer pconn.mtx.Unlock()
pconn.readReq = req
}
/*
Implements net.Listener. Listeners can be added. Will accept
connections on each listener and read HTTP messages from the
connection. Will attempt to spoof TLS from incoming HTTP
requests. Accept() returns a ProxyConn which transmists one
unencrypted HTTP request and contains the intended destination for
each request in the RemoteAddr.
*/
type ProxyListener struct {
net.Listener
// The current state of the listener
State int
inputListeners mapset.Set
mtx sync.Mutex
logger *log.Logger
outputConns chan ProxyConn
inputConns chan *inputConn
outputConnDone chan struct{}
inputConnDone chan struct{}
listenWg sync.WaitGroup
caCert *tls.Certificate
}
type inputConn struct {
listener *ProxyListener
conn net.Conn
transparentMode bool
transparentAddr *proxyAddr
}
type listenerData struct {
Id int
Listener net.Listener
}
func newListenerData(listener net.Listener) *listenerData {
l := listenerData{}
l.Id = getNextListenerId()
l.Listener = listener
return &l
}
// NewProxyListener creates and starts a new proxy listener that will log to the given logger
func NewProxyListener(logger *log.Logger) *ProxyListener {
var useLogger *log.Logger
if logger != nil {
useLogger = logger
} else {
useLogger = log.New(ioutil.Discard, "[*] ", log.Lshortfile)
}
l := ProxyListener{logger: useLogger, State: ProxyStarting}
l.inputListeners = mapset.NewSet()
l.outputConns = make(chan ProxyConn)
l.inputConns = make(chan *inputConn)
l.outputConnDone = make(chan struct{})
l.inputConnDone = make(chan struct{})
// Translate connections
l.listenWg.Add(1)
go func() {
l.logger.Println("Starting connection translator...")
defer l.listenWg.Done()
for {
select {
case <-l.outputConnDone:
l.logger.Println("Output channel closed. Shutting down translator.")
return
case inconn := <-l.inputConns:
go func() {
err := l.translateConn(inconn)
if err != nil {
l.logger.Println("Could not translate connection:", err)
}
}()
}
}
}()
l.State = ProxyRunning
l.logger.Println("Proxy Started")
return &l
}
// Accept accepts a new connection from any of its listeners
func (listener *ProxyListener) Accept() (net.Conn, error) {
if listener.outputConns == nil ||
listener.inputConns == nil ||
listener.outputConnDone == nil ||
listener.inputConnDone == nil {
return nil, fmt.Errorf("Listener not initialized! Cannot accept connection.")
}
select {
case <-listener.outputConnDone:
listener.logger.Println("Cannot accept connection, ProxyListener is closed")
return nil, fmt.Errorf("Connection is closed")
case c := <-listener.outputConns:
listener.logger.Println("Connection", c.Id(), "accepted from ProxyListener")
return c, nil
}
}
// Close closes all of the listeners associated with the ProxyListener
func (listener *ProxyListener) Close() error {
listener.mtx.Lock()
defer listener.mtx.Unlock()
listener.logger.Println("Closing ProxyListener...")
listener.State = ProxyStopped
close(listener.outputConnDone)
close(listener.inputConnDone)
close(listener.outputConns)
close(listener.inputConns)
it := listener.inputListeners.Iterator()
for elem := range it.C {
l := elem.(*listenerData)
l.Listener.Close()
listener.logger.Println("Closed listener", l.Id)
}
listener.logger.Println("ProxyListener closed")
listener.listenWg.Wait()
return nil
}
func (listener *ProxyListener) Addr() net.Addr {
return internalAddr{}
}
// AddListener adds a listener for the ProxyListener to listen on
func (listener *ProxyListener) AddListener(inlisten net.Listener) error {
listener.mtx.Lock()
defer listener.mtx.Unlock()
return listener.addListener(inlisten, false, nil)
}
// AddTransparentListener is the same as AddListener, but all of the connections will be in transparent mode
func (listener *ProxyListener) AddTransparentListener(inlisten net.Listener, destHost string, destPort int, useTLS bool) error {
listener.mtx.Lock()
defer listener.mtx.Unlock()
addr := &proxyAddr{
Host: destHost,
Port: destPort,
UseTLS: useTLS,
}
return listener.addListener(inlisten, true, addr)
}
func (listener *ProxyListener) addListener(inlisten net.Listener, transparentMode bool, destAddr *proxyAddr) error {
listener.logger.Println("Adding listener to ProxyListener:", inlisten)
il := newListenerData(inlisten)
l := listener
listener.listenWg.Add(1)
go func() {
defer l.listenWg.Done()
for {
c, err := il.Listener.Accept()
if err != nil {
// TODO: verify that the connection is actually closed and not some other error
l.logger.Println("Listener", il.Id, "closed")
return
}
l.logger.Println("Received conn form listener", il.Id)
newConn := &inputConn{
conn: c,
listener: nil,
transparentMode: transparentMode,
transparentAddr: destAddr,
}
l.inputConns <- newConn
}
}()
listener.inputListeners.Add(il)
l.logger.Println("Listener", il.Id, "added to ProxyListener")
return nil
}
// RemoveListener closes a listener and removes it from the ProxyListener. Does not kill active connections.
func (listener *ProxyListener) RemoveListener(inlisten net.Listener) error {
listener.mtx.Lock()
defer listener.mtx.Unlock()
listener.inputListeners.Remove(inlisten)
inlisten.Close()
listener.logger.Println("Listener removed:", inlisten)
return nil
}
// TKTK working here
// Take in a connection, strip TLS, get destination info, and push a ProxyConn to the listener.outputConnection channel
func (listener *ProxyListener) translateConn(inconn *inputConn) error {
pconn := newProxyConn(inconn.conn, listener.logger)
pconn.SetCACertificate(listener.GetCACertificate())
if inconn.transparentMode {
pconn.SetTransparentMode(inconn.transparentAddr.Host,
inconn.transparentAddr.Port,
inconn.transparentAddr.UseTLS)
}
var host string = ""
var port int = -1
var useTLS bool = false
request, err := http.ReadRequest(bufio.NewReader(pconn))
if err != nil {
listener.logger.Println(err)
return err
}
// Get parsed host and port
parsed_host, sport, err := net.SplitHostPort(request.URL.Host)
if err != nil {
// Assume that that URL.Host is the hostname and doesn't contain a port
host = request.URL.Host
port = -1
} else {
parsed_port, err := strconv.Atoi(sport)
if err != nil {
// Assume that that URL.Host is the hostname and doesn't contain a port
return fmt.Errorf("Error parsing hostname: %s", err)
}
host = parsed_host
port = parsed_port
}
// Handle CONNECT and TLS
if request.Method == "CONNECT" {
// Respond that we connected
resp := http.Response{Status: "Connection established", Proto: "HTTP/1.1", ProtoMajor: 1, StatusCode: 200}
err := resp.Write(inconn.conn)
if err != nil {
listener.logger.Println("Could not write CONNECT response:", err)
return err
}
usedTLS, err := pconn.StartMaybeTLS(host)
if err != nil {
listener.logger.Println("Error starting maybeTLS:", err)
return err
}
useTLS = usedTLS
} else {
// Put the request back
pconn.returnRequest(request)
useTLS = false
}
// Guess the port if we have to
if port == -1 {
if useTLS {
port = 443
} else {
port = 80
}
}
if !pconn.transparentMode {
pconn.Addr.Host = host
pconn.Addr.Port = port
pconn.Addr.UseTLS = useTLS
}
var useTLSStr string
if pconn.Addr.UseTLS {
useTLSStr = "YES"
} else {
useTLSStr = "NO"
}
pconn.Logger().Printf("Received connection to: Host='%s', Port=%d, UseTls=%s", pconn.Addr.Host, pconn.Addr.Port, useTLSStr)
// Put the conn in the output channel
listener.outputConns <- pconn
return nil
}
// SetCACertificate sets which certificate the listener should be used when spoofing TLS
func (listener *ProxyListener) SetCACertificate(caCert *tls.Certificate) {
listener.mtx.Lock()
defer listener.mtx.Unlock()
listener.caCert = caCert
}
// SetCACertificate gets which certificate the listener is using when spoofing TLS
func (listener *ProxyListener) GetCACertificate() *tls.Certificate {
listener.mtx.Lock()
defer listener.mtx.Unlock()
return listener.caCert
}