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.
496 lines
11 KiB
496 lines
11 KiB
8 years ago
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"crypto/tls"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/deckarep/golang-set"
|
||
|
)
|
||
|
|
||
|
/*
|
||
|
func logReq(req *http.Request, logger *log.Logger) {
|
||
|
buf := new(bytes.Buffer)
|
||
|
req.Write(buf)
|
||
|
s := buf.String()
|
||
|
logger.Print(s)
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
const (
|
||
|
ProxyStopped = iota
|
||
|
ProxyStarting
|
||
|
ProxyRunning
|
||
|
)
|
||
|
|
||
|
var getNextConnId = IdCounter()
|
||
|
var getNextListenerId = IdCounter()
|
||
|
|
||
|
/*
|
||
|
A type representing the "Addr" for our internal connections
|
||
|
*/
|
||
|
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)
|
||
|
|
||
|
SetCACertificate(*tls.Certificate)
|
||
|
StartMaybeTLS(hostname string) (bool, error)
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// ProxyAddr implementations/functions
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
return pconn.id
|
||
|
}
|
||
|
|
||
|
func (pconn *proxyConn) Logger() *log.Logger {
|
||
|
return pconn.logger
|
||
|
}
|
||
|
|
||
|
func (pconn *proxyConn) SetCACertificate(cert *tls.Certificate) {
|
||
|
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
|
||
|
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},
|
||
|
}
|
||
|
tlsConn := tls.Server(bufConn, config)
|
||
|
pconn.conn = tlsConn
|
||
|
return true, nil
|
||
|
} else {
|
||
|
pconn.conn = bufConn
|
||
|
return false, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func NewProxyConn(c net.Conn, l *log.Logger) *proxyConn {
|
||
|
a := proxyAddr{Host:"", Port:-1, UseTLS:false}
|
||
|
p := proxyConn{Addr:&a, logger:l, conn:c, readReq:nil}
|
||
|
p.id = getNextConnId()
|
||
|
return &p
|
||
|
}
|
||
|
|
||
|
func (pconn *proxyConn) returnRequest(req *http.Request) {
|
||
|
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.
|
||
|
*/
|
||
|
type ProxyListener struct {
|
||
|
net.Listener
|
||
|
|
||
|
State int
|
||
|
|
||
|
inputListeners mapset.Set
|
||
|
mtx sync.Mutex
|
||
|
logger *log.Logger
|
||
|
outputConns chan ProxyConn
|
||
|
inputConns chan net.Conn
|
||
|
outputConnDone chan struct{}
|
||
|
inputConnDone chan struct{}
|
||
|
listenWg sync.WaitGroup
|
||
|
caCert *tls.Certificate
|
||
|
}
|
||
|
|
||
|
type listenerData struct {
|
||
|
Id int
|
||
|
Listener net.Listener
|
||
|
}
|
||
|
|
||
|
func newListenerData(listener net.Listener) (*listenerData) {
|
||
|
l := listenerData{}
|
||
|
l.Id = getNextListenerId()
|
||
|
l.Listener = listener
|
||
|
return &l
|
||
|
}
|
||
|
|
||
|
func NewProxyListener(logger *log.Logger) *ProxyListener {
|
||
|
l := ProxyListener{logger: logger, State: ProxyStarting}
|
||
|
l.inputListeners = mapset.NewSet()
|
||
|
|
||
|
l.outputConns = make(chan ProxyConn)
|
||
|
l.inputConns = make(chan net.Conn)
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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{}
|
||
|
}
|
||
|
|
||
|
// Add a listener for the ProxyListener to listen on
|
||
|
func (listener *ProxyListener) AddListener(inlisten net.Listener) error {
|
||
|
listener.mtx.Lock()
|
||
|
defer listener.mtx.Unlock()
|
||
|
|
||
|
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)
|
||
|
l.inputConns <- c
|
||
|
}
|
||
|
}()
|
||
|
listener.inputListeners.Add(il)
|
||
|
l.logger.Println("Listener", il.Id, "added to ProxyListener")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close a listener and remove it from the slistener. 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
|
||
|
}
|
||
|
|
||
|
// Take in a connection, strip TLS, get destination info, and push a ProxyConn to the listener.outputConnection channel
|
||
|
func (listener *ProxyListener) translateConn(inconn net.Conn) error {
|
||
|
pconn := NewProxyConn(inconn, listener.logger)
|
||
|
pconn.SetCACertificate(listener.GetCACertificate())
|
||
|
|
||
|
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)
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func (listener *ProxyListener) SetCACertificate(caCert *tls.Certificate) {
|
||
|
listener.mtx.Lock()
|
||
|
defer listener.mtx.Unlock()
|
||
|
|
||
|
listener.caCert = caCert
|
||
|
}
|
||
|
|
||
|
func (listener *ProxyListener) GetCACertificate() *tls.Certificate {
|
||
|
listener.mtx.Lock()
|
||
|
defer listener.mtx.Unlock()
|
||
|
|
||
|
return listener.caCert
|
||
|
}
|