1
0
Эх сурвалжийг харах

Multiple HTTPS certificate support

刘河 6 жил өмнө
parent
commit
2b841adb1b

+ 2 - 2
bridge/bridge.go

@@ -162,7 +162,7 @@ func (s *Bridge) verifySuccess(c *conn.Conn) {
 func (s *Bridge) cliProcess(c *conn.Conn) {
 	//read test flag
 	if _, err := c.GetShortContent(3); err != nil {
-		logs.Info("The client %s connect error", c.Conn.RemoteAddr())
+		logs.Info("The client %s connect error", c.Conn.RemoteAddr(), err.Error())
 		return
 	}
 	//version check
@@ -173,7 +173,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
 	}
 	//write server version to client
 	c.Write([]byte(crypt.Md5(version.GetVersion())))
-	c.SetReadDeadline(5, s.tunnelType)
+	c.SetReadDeadlineByType(5, s.tunnelType)
 	var buf []byte
 	var err error
 	//get vKey from client

+ 45 - 4
client/control.go

@@ -1,6 +1,7 @@
 package client
 
 import (
+	"encoding/base64"
 	"encoding/binary"
 	"errors"
 	"github.com/cnlh/nps/lib/common"
@@ -14,6 +15,8 @@ import (
 	"io/ioutil"
 	"log"
 	"net"
+	"net/http"
+	"net/http/httputil"
 	"net/url"
 	"os"
 	"path/filepath"
@@ -180,11 +183,16 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
 			if er != nil {
 				return nil, er
 			}
-			n, er := proxy.FromURL(u, nil)
-			if er != nil {
-				return nil, er
+			switch u.Scheme {
+			case "socks5":
+				n, er := proxy.FromURL(u, nil)
+				if er != nil {
+					return nil, er
+				}
+				connection, err = n.Dial("tcp", server)
+			case "http":
+				connection, err = NewHttpProxyConn(u, server)
 			}
-			connection, err = n.Dial("tcp", server)
 		} else {
 			connection, err = net.Dial("tcp", server)
 		}
@@ -230,3 +238,36 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
 
 	return c, nil
 }
+
+func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
+	req := &http.Request{
+		Method: "CONNECT",
+		URL:    url,
+		Host:   remoteAddr,
+		Header: http.Header{},
+		Proto:  "HTTP/1.1",
+	}
+	password, _ := url.User.Password()
+	req.Header.Set("Proxy-Authorization", "Basic "+basicAuth(url.User.Username(), password))
+	b, err := httputil.DumpRequest(req, false)
+	if err != nil {
+		return nil, err
+	}
+	proxyConn, err := net.Dial("tcp", url.Host)
+	if err != nil {
+		return nil, err
+	}
+	if _, err := proxyConn.Write(b); err != nil {
+		return nil, err
+	}
+	buf := make([]byte, 1024)
+	if _, err := proxyConn.Read(buf); err != nil {
+		return nil, err
+	}
+	return proxyConn, nil
+}
+
+func basicAuth(username, password string) string {
+	auth := username + ":" + password
+	return base64.StdEncoding.EncodeToString([]byte(auth))
+}

+ 0 - 4
conf/nps.conf

@@ -7,10 +7,6 @@ http_proxy_port=80
 https_proxy_port=443
 https_just_proxy=true
 http_proxy_ip=0.0.0.0
-#certFile absolute path
-#pem_path=conf/server.pem
-#KeyFile absolute path
-#key_path=conf/server.key
 
 ##bridge
 bridge_type=tcp

+ 1 - 1
lib/config/config.go

@@ -130,7 +130,7 @@ func dealCommon(s string) *CommonConfig {
 			c.Cnf.Compress = common.GetBoolByStr(item[1])
 		case "crypt":
 			c.Cnf.Crypt = common.GetBoolByStr(item[1])
-		case "proxy_socks5_url":
+		case "proxy_url":
 			c.ProxyUrl = item[1]
 		case "rate_limit":
 			c.Client.RateLimit = common.GetIntNoErrByStr(item[1])

+ 31 - 2
lib/conn/conn.go

@@ -25,6 +25,7 @@ import (
 
 type Conn struct {
 	Conn net.Conn
+	Rb   []byte
 }
 
 //new conn
@@ -83,6 +84,26 @@ func (s *Conn) GetShortContent(l int) (b []byte, err error) {
 	return buf, binary.Read(s, binary.LittleEndian, &buf)
 }
 
+func (s *Conn) LocalAddr() net.Addr {
+	return s.Conn.LocalAddr()
+}
+
+func (s *Conn) RemoteAddr() net.Addr {
+	return s.Conn.RemoteAddr()
+}
+
+func (s *Conn) SetDeadline(t time.Time) error {
+	return s.Conn.SetDeadline(t)
+}
+
+func (s *Conn) SetWriteDeadline(t time.Time) error {
+	return s.Conn.SetWriteDeadline(t)
+}
+
+func (s *Conn) SetReadDeadline(t time.Time) error {
+	return s.Conn.SetReadDeadline(t)
+}
+
 //读取指定长度内容
 func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
 	if cLen > len(buf) {
@@ -130,7 +151,7 @@ func (s *Conn) SetAlive(tp string) {
 }
 
 //set read deadline
-func (s *Conn) SetReadDeadline(t time.Duration, tp string) {
+func (s *Conn) SetReadDeadlineByType(t time.Duration, tp string) {
 	switch s.Conn.(type) {
 	case *kcp.UDPSession:
 		s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
@@ -340,7 +361,15 @@ func (s *Conn) Write(b []byte) (int, error) {
 }
 
 //read
-func (s *Conn) Read(b []byte) (int, error) {
+func (s *Conn) Read(b []byte) (n int, err error) {
+	if s.Rb != nil {
+		if len(s.Rb) > 0 {
+			n = copy(b, s.Rb)
+			s.Rb = s.Rb[n:]
+			return
+		}
+		s.Rb = nil
+	}
 	return s.Conn.Read(b)
 }
 

+ 253 - 0
lib/crypt/clientHello.go

@@ -0,0 +1,253 @@
+package crypt
+
+import (
+	"strings"
+)
+
+type CurveID uint16
+type SignatureScheme uint16
+
+const (
+	statusTypeOCSP               uint8  = 1
+	extensionServerName          uint16 = 0
+	extensionStatusRequest       uint16 = 5
+	extensionSupportedCurves     uint16 = 10
+	extensionSupportedPoints     uint16 = 11
+	extensionSignatureAlgorithms uint16 = 13
+	extensionALPN                uint16 = 16
+	extensionSCT                 uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6
+	extensionSessionTicket       uint16 = 35
+	extensionNextProtoNeg        uint16 = 13172 // not IANA assigned
+	extensionRenegotiationInfo   uint16 = 0xff01
+	scsvRenegotiation            uint16 = 0x00ff
+)
+
+type ClientHelloMsg struct {
+	raw                          []byte
+	vers                         uint16
+	random                       []byte
+	sessionId                    []byte
+	cipherSuites                 []uint16
+	compressionMethods           []uint8
+	nextProtoNeg                 bool
+	serverName                   string
+	ocspStapling                 bool
+	scts                         bool
+	supportedCurves              []CurveID
+	supportedPoints              []uint8
+	ticketSupported              bool
+	sessionTicket                []uint8
+	supportedSignatureAlgorithms []SignatureScheme
+	secureRenegotiation          []byte
+	secureRenegotiationSupported bool
+	alpnProtocols                []string
+}
+
+func (m *ClientHelloMsg) GetServerName() string {
+	return m.serverName
+}
+
+func (m *ClientHelloMsg) Unmarshal(data []byte) bool {
+	if len(data) < 42 {
+		return false
+	}
+	m.raw = data
+	m.vers = uint16(data[4])<<8 | uint16(data[5])
+	m.random = data[6:38]
+	sessionIdLen := int(data[38])
+	if sessionIdLen > 32 || len(data) < 39+sessionIdLen {
+		return false
+	}
+	m.sessionId = data[39 : 39+sessionIdLen]
+	data = data[39+sessionIdLen:]
+	if len(data) < 2 {
+		return false
+	}
+	// cipherSuiteLen is the number of bytes of cipher suite numbers. Since
+	// they are uint16s, the number must be even.
+	cipherSuiteLen := int(data[0])<<8 | int(data[1])
+	if cipherSuiteLen%2 == 1 || len(data) < 2+cipherSuiteLen {
+		return false
+	}
+	numCipherSuites := cipherSuiteLen / 2
+	m.cipherSuites = make([]uint16, numCipherSuites)
+	for i := 0; i < numCipherSuites; i++ {
+		m.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
+		if m.cipherSuites[i] == scsvRenegotiation {
+			m.secureRenegotiationSupported = true
+		}
+	}
+	data = data[2+cipherSuiteLen:]
+	if len(data) < 1 {
+		return false
+	}
+	compressionMethodsLen := int(data[0])
+	if len(data) < 1+compressionMethodsLen {
+		return false
+	}
+	m.compressionMethods = data[1 : 1+compressionMethodsLen]
+	data = data[1+compressionMethodsLen:]
+
+	m.nextProtoNeg = false
+	m.serverName = ""
+	m.ocspStapling = false
+	m.ticketSupported = false
+	m.sessionTicket = nil
+	m.supportedSignatureAlgorithms = nil
+	m.alpnProtocols = nil
+	m.scts = false
+
+	if len(data) == 0 {
+		// ClientHello is optionally followed by extension data
+		return true
+	}
+	if len(data) < 2 {
+		return false
+	}
+
+	extensionsLength := int(data[0])<<8 | int(data[1])
+	data = data[2:]
+	if extensionsLength != len(data) {
+		return false
+	}
+
+	for len(data) != 0 {
+		if len(data) < 4 {
+			return false
+		}
+		extension := uint16(data[0])<<8 | uint16(data[1])
+		length := int(data[2])<<8 | int(data[3])
+		data = data[4:]
+		if len(data) < length {
+			return false
+		}
+
+		switch extension {
+		case extensionServerName:
+			d := data[:length]
+			if len(d) < 2 {
+				return false
+			}
+			namesLen := int(d[0])<<8 | int(d[1])
+			d = d[2:]
+			if len(d) != namesLen {
+				return false
+			}
+			for len(d) > 0 {
+				if len(d) < 3 {
+					return false
+				}
+				nameType := d[0]
+				nameLen := int(d[1])<<8 | int(d[2])
+				d = d[3:]
+				if len(d) < nameLen {
+					return false
+				}
+				if nameType == 0 {
+					m.serverName = string(d[:nameLen])
+					// An SNI value may not include a
+					// trailing dot. See
+					// https://tools.ietf.org/html/rfc6066#section-3.
+					if strings.HasSuffix(m.serverName, ".") {
+						return false
+					}
+					break
+				}
+				d = d[nameLen:]
+			}
+		case extensionNextProtoNeg:
+			if length > 0 {
+				return false
+			}
+			m.nextProtoNeg = true
+		case extensionStatusRequest:
+			m.ocspStapling = length > 0 && data[0] == statusTypeOCSP
+		case extensionSupportedCurves:
+			// https://tools.ietf.org/html/rfc4492#section-5.5.1
+			if length < 2 {
+				return false
+			}
+			l := int(data[0])<<8 | int(data[1])
+			if l%2 == 1 || length != l+2 {
+				return false
+			}
+			numCurves := l / 2
+			m.supportedCurves = make([]CurveID, numCurves)
+			d := data[2:]
+			for i := 0; i < numCurves; i++ {
+				m.supportedCurves[i] = CurveID(d[0])<<8 | CurveID(d[1])
+				d = d[2:]
+			}
+		case extensionSupportedPoints:
+			// https://tools.ietf.org/html/rfc4492#section-5.5.2
+			if length < 1 {
+				return false
+			}
+			l := int(data[0])
+			if length != l+1 {
+				return false
+			}
+			m.supportedPoints = make([]uint8, l)
+			copy(m.supportedPoints, data[1:])
+		case extensionSessionTicket:
+			// https://tools.ietf.org/html/rfc5077#section-3.2
+			m.ticketSupported = true
+			m.sessionTicket = data[:length]
+		case extensionSignatureAlgorithms:
+			// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+			if length < 2 || length&1 != 0 {
+				return false
+			}
+			l := int(data[0])<<8 | int(data[1])
+			if l != length-2 {
+				return false
+			}
+			n := l / 2
+			d := data[2:]
+			m.supportedSignatureAlgorithms = make([]SignatureScheme, n)
+			for i := range m.supportedSignatureAlgorithms {
+				m.supportedSignatureAlgorithms[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1])
+				d = d[2:]
+			}
+		case extensionRenegotiationInfo:
+			if length == 0 {
+				return false
+			}
+			d := data[:length]
+			l := int(d[0])
+			d = d[1:]
+			if l != len(d) {
+				return false
+			}
+
+			m.secureRenegotiation = d
+			m.secureRenegotiationSupported = true
+		case extensionALPN:
+			if length < 2 {
+				return false
+			}
+			l := int(data[0])<<8 | int(data[1])
+			if l != length-2 {
+				return false
+			}
+			d := data[2:length]
+			for len(d) != 0 {
+				stringLen := int(d[0])
+				d = d[1:]
+				if stringLen == 0 || stringLen > len(d) {
+					return false
+				}
+				m.alpnProtocols = append(m.alpnProtocols, string(d[:stringLen]))
+				d = d[stringLen:]
+			}
+		case extensionSCT:
+			m.scts = true
+			if length != 0 {
+				return false
+			}
+		}
+		data = data[length:]
+	}
+
+	return true
+}

+ 2 - 0
lib/file/obj.go

@@ -152,6 +152,8 @@ type Host struct {
 	Location     string //url router
 	Remark       string //remark
 	Scheme       string //http https all
+	CertFilePath     string
+	KeyFilePath      string
 	NoStore      bool
 	IsClose      bool
 	Flow         *Flow

+ 2 - 2
lib/version/version.go

@@ -1,8 +1,8 @@
 package version
 
-const VERSION = "0.20.2"
+const VERSION = "0.21.0"
 
 // Compulsory minimum version, Minimum downward compatibility to this version
 func GetVersion() string {
-	return "0.19.0"
+	return "0.21.0"
 }

+ 4 - 81
server/proxy/http.go

@@ -2,7 +2,6 @@ package proxy
 
 import (
 	"bufio"
-	"bytes"
 	"crypto/tls"
 	"github.com/cnlh/nps/bridge"
 	"github.com/cnlh/nps/lib/common"
@@ -15,7 +14,6 @@ import (
 	"net"
 	"net/http"
 	"net/http/httputil"
-	"net/url"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -24,10 +22,8 @@ import (
 
 type httpServer struct {
 	BaseServer
-	httpPort      int //http端口
-	httpsPort     int //https监听端口
-	pemPath       string
-	keyPath       string
+	httpPort      int
+	httpsPort     int
 	httpServer    *http.Server
 	httpsServer   *http.Server
 	httpsListener net.Listener
@@ -36,8 +32,6 @@ type httpServer struct {
 func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer {
 	httpPort, _ := beego.AppConfig.Int("http_proxy_port")
 	httpsPort, _ := beego.AppConfig.Int("https_proxy_port")
-	pemPath := beego.AppConfig.String("pem_path")
-	keyPath := beego.AppConfig.String("key_path")
 	return &httpServer{
 		BaseServer: BaseServer{
 			task:   c,
@@ -46,57 +40,9 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer {
 		},
 		httpPort:  httpPort,
 		httpsPort: httpsPort,
-		pemPath:   pemPath,
-		keyPath:   keyPath,
 	}
 }
 
-func (s *httpServer) processHttps(c net.Conn) {
-	buf := make([]byte, 2048)
-	n, err := c.Read(buf)
-	if err != nil {
-		return
-	}
-	var host *file.Host
-	file.GetDb().JsonDb.Hosts.Range(func(key, value interface{}) bool {
-		v := value.(*file.Host)
-		if v.Scheme != "https" && v.Scheme != "all" {
-			return true
-		}
-		if bytes.Index(buf[:n], []byte(v.Host)) >= 0 && (host == nil || len(host.Host) < len(v.Host)) {
-			host = v
-			return false
-		}
-		return true
-	})
-	if host == nil {
-		logs.Error("new https connection can't be parsed!", c.RemoteAddr().String())
-		c.Close()
-		return
-	}
-	var targetAddr string
-	r := new(http.Request)
-	r.RequestURI = "/"
-	r.URL = new(url.URL)
-	r.URL.Scheme = "https"
-	r.Host = host.Host
-	if err := s.CheckFlowAndConnNum(host.Client); err != nil {
-		logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
-		c.Close()
-		return
-	}
-	defer host.Client.AddConn()
-	if err = s.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
-		logs.Warn("auth error", err, r.RemoteAddr)
-		return
-	}
-	if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
-		logs.Warn(err.Error())
-	}
-	logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
-	s.DealClient(conn.NewConn(c), host.Client, targetAddr, buf[:n], common.CONN_TCP, nil, host.Flow)
-}
-
 func (s *httpServer) Start() error {
 	var err error
 	if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
@@ -125,30 +71,7 @@ func (s *httpServer) Start() error {
 				logs.Error(err)
 				os.Exit(0)
 			}
-			if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
-				for {
-					c, err := s.httpsListener.Accept()
-					if err != nil {
-						logs.Error(err)
-						break
-					}
-					go s.processHttps(c)
-				}
-			} else {
-				if !common.FileExists(s.pemPath) {
-					logs.Error("ssl certFile %s exist", s.keyPath)
-					os.Exit(0)
-				}
-				if !common.FileExists(s.keyPath) {
-					logs.Error("ssl keyFile %s exist", s.keyPath)
-					os.Exit(0)
-				}
-				err = s.httpsServer.ServeTLS(s.httpsListener, s.pemPath, s.keyPath)
-				if err != nil {
-					logs.Error(err)
-					os.Exit(0)
-				}
-			}
+			logs.Error(NewHttpsServer(s.httpsListener, s.bridge).Start())
 		}()
 	}
 	return nil
@@ -255,7 +178,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 				goto start
 			}
 		}
-		//根据设定,修改header和host
+		//change the host and header and set proxy setting
 		common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
 		b, err := httputil.DumpRequest(r, false)
 		if err != nil {

+ 147 - 0
server/proxy/https.go

@@ -0,0 +1,147 @@
+package proxy
+
+import (
+	"github.com/cnlh/nps/bridge"
+	"github.com/cnlh/nps/lib/common"
+	"github.com/cnlh/nps/lib/conn"
+	"github.com/cnlh/nps/lib/crypt"
+	"github.com/cnlh/nps/lib/file"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
+	"github.com/pkg/errors"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+)
+
+type HttpsServer struct {
+	httpServer
+	listener         net.Listener
+	httpsListenerMap sync.Map
+}
+
+func NewHttpsServer(l net.Listener, bridge *bridge.Bridge) *HttpsServer {
+	https := &HttpsServer{listener: l}
+	https.bridge = bridge
+	return https
+}
+
+func (https *HttpsServer) Start() error {
+	if b, err := beego.AppConfig.Bool("https_just_proxy"); err == nil && b {
+		conn.Accept(https.listener, func(c net.Conn) {
+			https.handleHttps(c)
+		})
+	} else {
+		conn.Accept(https.listener, func(c net.Conn) {
+			serverName, rb := GetServerNameFromClientHello(c)
+			var l *HttpsListener
+			if v, ok := https.httpsListenerMap.Load(serverName); ok {
+				l = v.(*HttpsListener)
+			} else {
+				r := new(http.Request)
+				r.RequestURI = "/"
+				r.URL = new(url.URL)
+				r.URL.Scheme = "https"
+				if host, err := file.GetDb().GetInfoByHost(serverName, r); err != nil {
+					c.Close()
+					logs.Notice("the url %s can't be parsed!", serverName)
+					return
+				} else {
+					if !common.FileExists(host.CertFilePath) || !common.FileExists(host.KeyFilePath) {
+						c.Close()
+						logs.Error("the key %s  cert %s file is not exist", host.KeyFilePath, host.CertFilePath)
+						return
+					}
+					l = NewHttpsListener(https.listener)
+					https.NewHttps(l, host.CertFilePath, host.KeyFilePath)
+					https.httpsListenerMap.Store(serverName, l)
+				}
+			}
+			acceptConn := conn.NewConn(c)
+			acceptConn.Rb = rb
+			l.acceptConn <- acceptConn
+		})
+	}
+	return nil
+}
+
+func (https *HttpsServer) Close() error {
+	return https.listener.Close()
+}
+
+func (https *HttpsServer) NewHttps(l net.Listener, certFile string, keyFile string) {
+	go func() {
+		logs.Error(https.NewServer(0, "https").ServeTLS(l, certFile, keyFile))
+	}()
+}
+
+func (https *HttpsServer) handleHttps(c net.Conn) {
+	hostName, rb := GetServerNameFromClientHello(c)
+	var targetAddr string
+	r := new(http.Request)
+	r.RequestURI = "/"
+	r.URL = new(url.URL)
+	r.URL.Scheme = "https"
+	r.Host = hostName
+	var host *file.Host
+	var err error
+	if host, err = file.GetDb().GetInfoByHost(hostName, r); err != nil {
+		c.Close()
+		logs.Notice("the url %s can't be parsed!", hostName)
+		return
+	}
+	if err := https.CheckFlowAndConnNum(host.Client); err != nil {
+		logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
+		c.Close()
+		return
+	}
+	defer host.Client.AddConn()
+	if err = https.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
+		logs.Warn("auth error", err, r.RemoteAddr)
+		return
+	}
+	if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
+		logs.Warn(err.Error())
+	}
+	logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
+	https.DealClient(conn.NewConn(c), host.Client, targetAddr, rb, common.CONN_TCP, nil, host.Flow)
+}
+
+type HttpsListener struct {
+	acceptConn     chan *conn.Conn
+	parentListener net.Listener
+}
+
+func NewHttpsListener(l net.Listener) *HttpsListener {
+	return &HttpsListener{parentListener: l, acceptConn: make(chan *conn.Conn)}
+}
+
+func (httpsListener *HttpsListener) Accept() (net.Conn, error) {
+	httpsConn := <-httpsListener.acceptConn
+	if httpsConn == nil {
+		return nil, errors.New("get connection error")
+	}
+	return httpsConn, nil
+}
+
+func (httpsListener *HttpsListener) Close() error {
+	return nil
+}
+
+func (httpsListener *HttpsListener) Addr() net.Addr {
+	return httpsListener.parentListener.Addr()
+}
+
+func GetServerNameFromClientHello(c net.Conn) (string, []byte) {
+	buf := make([]byte, 4096)
+	data := make([]byte, 4096)
+	n, err := c.Read(buf)
+	if err != nil {
+		return "", nil
+	}
+	copy(data, buf[:n])
+	clientHello := new(crypt.ClientHelloMsg)
+	clientHello.Unmarshal(data[5:n])
+	return clientHello.GetServerName(), buf[:n]
+}

+ 2 - 2
server/proxy/tcp.go

@@ -86,7 +86,7 @@ func NewWebServer(bridge *bridge.Bridge) *WebServer {
 
 type process func(c *conn.Conn, s *TunnelModeServer) error
 
-//tcp隧道模式
+//tcp proxy
 func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
 	targetAddr, err := s.task.Target.GetRandomTarget()
 	if err != nil {
@@ -97,7 +97,7 @@ func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
 	return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Flow)
 }
 
-//http代理模式
+//http proxy
 func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {
 	_, addr, rb, err, r := c.GetHost()
 	if err != nil {

+ 1 - 0
web/controllers/base.go

@@ -43,6 +43,7 @@ func (s *BaseController) Prepare() {
 	} else {
 		s.Data["isAdmin"] = true
 	}
+	s.Data["https_just_proxy"], _ = beego.AppConfig.Bool("https_just_proxy")
 }
 
 //加载模板

+ 5 - 1
web/controllers/index.go

@@ -251,10 +251,12 @@ func (s *IndexController) AddHost() {
 			Location:     s.GetString("location"),
 			Flow:         &file.Flow{},
 			Scheme:       s.GetString("scheme"),
+			KeyFilePath:  s.GetString("key_file_path"),
+			CertFilePath: s.GetString("cert_file_path"),
 		}
 		var err error
 		if h.Client, err = file.GetDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
-			s.AjaxErr("add error")
+			s.AjaxErr("add error the client can not be found")
 		}
 		if err := file.GetDb().NewHost(h); err != nil {
 			s.AjaxErr("add fail" + err.Error())
@@ -300,6 +302,8 @@ func (s *IndexController) EditHost() {
 			h.Remark = s.GetString("remark")
 			h.Location = s.GetString("location")
 			h.Scheme = s.GetString("scheme")
+			h.KeyFilePath = s.GetString("key_file_path")
+			h.CertFilePath = s.GetString("cert_file_path")
 			file.GetDb().JsonDb.StoreHostToJsonFile()
 		}
 		s.AjaxOk("modified success")

+ 27 - 2
web/views/index/hadd.html

@@ -19,13 +19,29 @@
                     <div class="form-group" id="scheme">
                         <label class="control-label col-sm-2" langtag="info-scheme">协议类型</label>
                         <div class="col-sm-10">
-                            <select class="form-control" name="scheme">
+                            <select id="scheme_select" class="form-control" name="scheme">
                                 <option value="all">all</option>
                                 <option value="http">http</option>
                                 <option value="https">https</option>
                             </select>
                         </div>
                     </div>
+                {{if eq false .https_just_proxy}}
+                    <div class="form-group" id="cert_file">
+                        <label class="col-sm-2 control-label" langtag="info-https-cert">https cert file路径</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="cert_file_path"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                    <div class="form-group" id="key_file">
+                        <label class="col-sm-2 control-label" langtag="info-https-key">https key file路径</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="key_file_path"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                {{end}}
                     <div class="form-group">
                         <label class="col-sm-2 control-label" langtag="info-url-router">url路由</label>
                         <div class="col-sm-10">
@@ -60,7 +76,7 @@
                         </div>
 
                     </div>
-                    <div class="form-group" id="hostchange" >
+                    <div class="form-group" id="hostchange">
                         <label class="col-sm-2 control-label" langtag="info-host-change">request host修改</label>
                         <div class="col-sm-10">
                             <input class="form-control" value="" type="text" name="hostchange"
@@ -95,5 +111,14 @@
                 }
             })
         })
+        $("#scheme_select").on("change", function () {
+            if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
+                $("#cert_file").css("display", "block")
+                $("#key_file").css("display", "block")
+            } else {
+                $("#cert_file").css("display", "none")
+                $("#key_file").css("display", "none")
+            }
+        })
     })
 </script>

+ 27 - 1
web/views/index/hedit.html

@@ -22,13 +22,29 @@
                     <div class="form-group" id="scheme">
                         <label class="control-label col-sm-2" langtag="info-scheme">协议类型</label>
                         <div class="col-sm-10">
-                            <select class="form-control" name="scheme">
+                            <select id="scheme_select" class="form-control" name="scheme">
                                 <option {{if eq "all" .h.Scheme}}selected{{end}} value="all">all</option>
                                 <option {{if eq "http" .h.Scheme}}selected{{end}} value="http">http</option>
                                 <option {{if eq "https" .h.Scheme}}selected{{end}} value="https">https</option>
                             </select>
                         </div>
                     </div>
+                {{if eq false .https_just_proxy}}
+                    <div class="form-group" id="cert_file">
+                        <label class="col-sm-2 control-label" langtag="info-https-cert">https cert file路径</label>
+                        <div class="col-sm-10">
+                            <input value="{{.h.CertFilePath}}" class="form-control" type="text" name="cert_file_path"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                    <div class="form-group" id="key_file">
+                        <label class="col-sm-2 control-label" langtag="info-https-key">https key file路径</label>
+                        <div class="col-sm-10">
+                            <input value="{{.h.KeyFilePath}}" class="form-control" type="text" name="key_file_path"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                {{end}}
                     <div class="form-group">
                         <label class="col-sm-2 control-label" langtag="info-url-router">url路由</label>
                         <div class="col-sm-10">
@@ -99,5 +115,15 @@
                 }
             })
         })
+        $("#scheme_select").on("change", function () {
+            if ($("#scheme_select").val() == "all" || $("#scheme_select").val() == "https") {
+                $("#cert_file").css("display", "block")
+                $("#key_file").css("display", "block")
+            } else {
+                $("#cert_file").css("display", "none")
+                $("#key_file").css("display", "none")
+            }
+        })
     })
+
 </script>

+ 11 - 9
web/views/index/hlist.html

@@ -68,6 +68,8 @@
                     + '<b langtag="info-compress">压缩</b>:' + row.Client.Cnf.Compress + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
                     + '<b langtag="info-web-auth-username">basic权限认证用户名</b>:' + row.Client.Cnf.U + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
                     + '<b langtag="info-web-auth-password">basic权限认证密码</b>:' + row.Client.Cnf.P + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
+                    + '<b langtag="info-https-cert">cert file路径</b>:' + row.CertFilePath + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
+                    + '<b langtag="info-https-key">key file路径</b>:' + row.KeyFilePath + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
                     + '<b langtag="info-header-change">request header修改</b>:' + row.HeaderChange + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
                     + '<b langtag="info-host-change">request host 修改</b>:' + row.HostChange + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
         },
@@ -77,13 +79,13 @@
                 field: 'Id',//域值
                 title: 'id',//标题
                 visible: true,//false表示不显示
-               
+
             },
             {
                 field: 'Id',//域值
                 title: 'client id',//标题
                 visible: true,//false表示不显示
-               
+
                 formatter: function (value, row, index) {
                     return row.Client.Id
                 }
@@ -92,25 +94,25 @@
                 field: 'Remark',//域值
                 title: 'remark',//标题
                 visible: true,//false表示不显示
-               
+
             },
             {
                 field: 'Host',//域值
                 title: 'host',//标题
                 visible: true,//false表示不显示
-               
+
             },
             {
                 field: 'Scheme',//域值
                 title: 'scheme',//标题
                 visible: true,//false表示不显示
-               
+
             },
             {
                 field: 'Target',//域值
                 title: 'target',//标题
                 visible: true,//false表示不显示
-               
+
                 formatter: function (value, row, index) {
                     return row.Target.TargetStr
                 }
@@ -119,13 +121,13 @@
                 field: 'Location',//域值
                 title: 'location',//标题
                 visible: true,//false表示不显示
-               
+
             },
             {
                 field: '',//域值
                 title: 'client status',//内容
                 visible: true,//false表示不显示
-               
+
                 formatter: function (value, row, index) {
                     if (row.Client.IsConnect) {
                         return '<span class="badge badge-primary">online</span>'
@@ -138,7 +140,7 @@
                 field: 'option',//域值
                 title: 'option',//内容
                 visible: true,//false表示不显示
-               
+
                 formatter: function (value, row, index) {
                     btn_group = '<div class="btn-group">'
                     btn = `<button onclick="del(` + row.Id + `)" class="btn-danger"><i class="fa fa-trash"></i></button><button onclick="edit(` + row.Id + `)" class="btn-primary"><i class="fa fa-edit"></i></button></div>`