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 "" } func (internalAddr) String() string { return "" } /* 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 }