From dc5e130d3398acdd019f4e7b1785795fad43446b Mon Sep 17 00:00:00 2001
From: Maodanping <673698750@qq.com>
Date: Mon, 29 Aug 2016 10:53:02 +0800
Subject: [PATCH 1/2] utils/vhost: supprot http authentication

---
 conf/frps.ini               |  5 ++++
 src/cmd/frpc/control.go     |  2 ++
 src/cmd/frps/control.go     |  2 ++
 src/models/client/config.go | 10 +++++++
 src/models/config/config.go |  2 ++
 src/models/msg/msg.go       |  2 ++
 src/models/server/server.go |  9 ++++---
 src/utils/vhost/http.go     | 54 +++++++++++++++++++++++++++++++++----
 src/utils/vhost/https.go    | 10 ++++---
 src/utils/vhost/vhost.go    | 34 ++++++++++++++++++-----
 10 files changed, 111 insertions(+), 19 deletions(-)

diff --git a/conf/frps.ini b/conf/frps.ini
index 6163bc98..45ec8586 100644
--- a/conf/frps.ini
+++ b/conf/frps.ini
@@ -41,6 +41,11 @@ auth_token = 123
 # if proxy type equals http, custom_domains must be set separated by commas
 custom_domains = web01.yourdomain.com,web01.yourdomain2.com
 
+# http username and password are safety certification for http protoc
+# if not set, you can access this custom_domains without certification
+http_username = admin
+http_password = admin
+
 [web02]
 # if type equals https, vhost_https_port must be set
 type = https
diff --git a/src/cmd/frpc/control.go b/src/cmd/frpc/control.go
index d39a45d4..dcb8b0d7 100644
--- a/src/cmd/frpc/control.go
+++ b/src/cmd/frpc/control.go
@@ -150,6 +150,8 @@ func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) {
 		ProxyType:         cli.Type,
 		PoolCount:         cli.PoolCount,
 		HostHeaderRewrite: cli.HostHeaderRewrite,
+		HttpUserName:      cli.HttpUserName,
+		HttpPassWord:      cli.HttpPassWord,
 		Timestamp:         nowTime,
 	}
 	if cli.PrivilegeMode {
diff --git a/src/cmd/frps/control.go b/src/cmd/frps/control.go
index 15620ff6..81e449cd 100644
--- a/src/cmd/frps/control.go
+++ b/src/cmd/frps/control.go
@@ -287,6 +287,8 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 		s.UseEncryption = req.UseEncryption
 		s.UseGzip = req.UseGzip
 		s.HostHeaderRewrite = req.HostHeaderRewrite
+		s.HttpUserName = req.HttpUserName
+		s.HttpPassWord = req.HttpPassWord
 		if req.PoolCount > server.MaxPoolCount {
 			s.PoolCount = server.MaxPoolCount
 		} else if req.PoolCount < 0 {
diff --git a/src/models/client/config.go b/src/models/client/config.go
index ddc5d1d4..ca5ec5b7 100644
--- a/src/models/client/config.go
+++ b/src/models/client/config.go
@@ -156,6 +156,16 @@ func LoadConf(confFile string) (err error) {
 				if ok {
 					proxyClient.HostHeaderRewrite = tmpStr
 				}
+				//http_username
+				tmpStr, ok = section["http_username"]
+				if ok {
+					proxyClient.HttpUserName = tmpStr
+				}
+				//http_password
+				tmpStr, ok = section["http_password"]
+				if ok {
+					proxyClient.HttpPassWord = tmpStr
+				}
 			}
 
 			// privilege_mode
diff --git a/src/models/config/config.go b/src/models/config/config.go
index 325dcb9b..9cf0071e 100644
--- a/src/models/config/config.go
+++ b/src/models/config/config.go
@@ -24,4 +24,6 @@ type BaseConf struct {
 	PrivilegeToken    string
 	PoolCount         int64
 	HostHeaderRewrite string
+	HttpUserName      string
+	HttpPassWord      string
 }
diff --git a/src/models/msg/msg.go b/src/models/msg/msg.go
index 253511af..f065df13 100644
--- a/src/models/msg/msg.go
+++ b/src/models/msg/msg.go
@@ -35,6 +35,8 @@ type ControlReq struct {
 	RemotePort        int64    `json:"remote_port"`
 	CustomDomains     []string `json:"custom_domains, omitempty"`
 	HostHeaderRewrite string   `json:"host_header_rewrite"`
+	HttpUserName      string   `json:"http_username"`
+	HttpPassWord      string   `json:"http_password"`
 	Timestamp         int64    `json:"timestamp"`
 }
 
diff --git a/src/models/server/server.go b/src/models/server/server.go
index d1502116..86019ea7 100644
--- a/src/models/server/server.go
+++ b/src/models/server/server.go
@@ -72,6 +72,8 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) {
 	}
 	p.CustomDomains = req.CustomDomains
 	p.HostHeaderRewrite = req.HostHeaderRewrite
+	p.HttpUserName = req.HttpUserName
+	p.HttpPassWord = req.HttpPassWord
 	return
 }
 
@@ -88,7 +90,8 @@ func (p *ProxyServer) Init() {
 
 func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
 	if p.Name != p2.Name || p.AuthToken != p2.AuthToken || p.Type != p2.Type ||
-		p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite {
+		p.BindAddr != p2.BindAddr || p.ListenPort != p2.ListenPort || p.HostHeaderRewrite != p2.HostHeaderRewrite ||
+		p.HttpUserName != p2.HttpUserName || p.HttpPassWord != p2.HttpPassWord {
 		return false
 	}
 	if len(p.CustomDomains) != len(p2.CustomDomains) {
@@ -122,7 +125,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
 		p.listeners = append(p.listeners, l)
 	} else if p.Type == "http" {
 		for _, domain := range p.CustomDomains {
-			l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite)
+			l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
 			if err != nil {
 				return err
 			}
@@ -130,7 +133,7 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
 		}
 	} else if p.Type == "https" {
 		for _, domain := range p.CustomDomains {
-			l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite)
+			l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
 			if err != nil {
 				return err
 			}
diff --git a/src/utils/vhost/http.go b/src/utils/vhost/http.go
index 0d1d8e07..623931a8 100644
--- a/src/utils/vhost/http.go
+++ b/src/utils/vhost/http.go
@@ -17,6 +17,7 @@ package vhost
 import (
 	"bufio"
 	"bytes"
+	"encoding/base64"
 	"fmt"
 	"io"
 	"net"
@@ -33,21 +34,29 @@ type HttpMuxer struct {
 	*VhostMuxer
 }
 
-func GetHttpHostname(c *conn.Conn) (_ net.Conn, routerName string, err error) {
+func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err error) {
+	reqInfoMap := make(map[string]string, 0)
 	sc, rd := newShareConn(c.TcpConn)
 
 	request, err := http.ReadRequest(bufio.NewReader(rd))
 	if err != nil {
-		return sc, "", err
+		return sc, reqInfoMap, err
 	}
+	// hostName
 	tmpArr := strings.Split(request.Host, ":")
-	routerName = tmpArr[0]
+	reqInfoMap["Host"] = tmpArr[0]
+
+	// Authorization
+	authStr := request.Header.Get("Authorization")
+	if authStr != "" {
+		reqInfoMap["Authorization"] = authStr
+	}
 	request.Body.Close()
-	return sc, routerName, nil
+	return sc, reqInfoMap, nil
 }
 
 func NewHttpMuxer(listener *conn.Listener, timeout time.Duration) (*HttpMuxer, error) {
-	mux, err := NewVhostMuxer(listener, GetHttpHostname, HttpHostNameRewrite, timeout)
+	mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, HttpHostNameRewrite, timeout)
 	return &HttpMuxer{mux}, err
 }
 
@@ -169,3 +178,38 @@ func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error
 	retBuf.Write(peek)
 	return retBuf.Bytes(), err
 }
+
+func HttpAuthFunc(c *conn.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
+	s := strings.SplitN(authorization, " ", 2)
+	if len(s) != 2 {
+		res := noAuthResponse()
+		res.Write(c.TcpConn)
+		return
+	}
+	b, err := base64.StdEncoding.DecodeString(s[1])
+	if err != nil {
+		return
+	}
+	pair := strings.SplitN(string(b), ":", 2)
+	if len(pair) != 2 {
+		return
+	}
+	if pair[0] != userName || pair[1] != passWord {
+		return
+	}
+	return true, nil
+}
+
+func noAuthResponse() *http.Response {
+	header := make(map[string][]string)
+	header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
+	res := &http.Response{
+		Status:     "401 Not authorized",
+		StatusCode: 401,
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Header:     header,
+	}
+	return res
+}
diff --git a/src/utils/vhost/https.go b/src/utils/vhost/https.go
index a82011b4..54a860a4 100644
--- a/src/utils/vhost/https.go
+++ b/src/utils/vhost/https.go
@@ -48,7 +48,7 @@ type HttpsMuxer struct {
 }
 
 func NewHttpsMuxer(listener *conn.Listener, timeout time.Duration) (*HttpsMuxer, error) {
-	mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, timeout)
+	mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout)
 	return &HttpsMuxer{mux}, err
 }
 
@@ -178,11 +178,13 @@ func readHandshake(rd io.Reader) (host string, err error) {
 	return
 }
 
-func GetHttpsHostname(c *conn.Conn) (sc net.Conn, routerName string, err error) {
+func GetHttpsHostname(c *conn.Conn) (sc net.Conn, _ map[string]string, err error) {
+	reqInfoMap := make(map[string]string, 0)
 	sc, rd := newShareConn(c.TcpConn)
 	host, err := readHandshake(rd)
 	if err != nil {
-		return sc, "", err
+		return sc, reqInfoMap, err
 	}
-	return sc, host, nil
+	reqInfoMap["Host"] = host
+	return sc, reqInfoMap, nil
 }
diff --git a/src/utils/vhost/vhost.go b/src/utils/vhost/vhost.go
index 12c8164c..fa33b324 100644
--- a/src/utils/vhost/vhost.go
+++ b/src/utils/vhost/vhost.go
@@ -1,5 +1,3 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -26,23 +24,26 @@ import (
 	"github.com/fatedier/frp/src/utils/conn"
 )
 
-type muxFunc func(*conn.Conn) (net.Conn, string, error)
+type muxFunc func(*conn.Conn) (net.Conn, map[string]string, error)
+type httpAuthFunc func(*conn.Conn, string, string, string) (bool, error)
 type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
 
 type VhostMuxer struct {
 	listener    *conn.Listener
 	timeout     time.Duration
 	vhostFunc   muxFunc
+	authFunc    httpAuthFunc
 	rewriteFunc hostRewriteFunc
 	registryMap map[string]*Listener
 	mutex       sync.RWMutex
 }
 
-func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
+func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
 	mux = &VhostMuxer{
 		listener:    listener,
 		timeout:     timeout,
 		vhostFunc:   vhostFunc,
+		authFunc:    authFunc,
 		rewriteFunc: rewriteFunc,
 		registryMap: make(map[string]*Listener),
 	}
@@ -51,7 +52,7 @@ func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, rewriteFunc hostR
 }
 
 // listen for a new domain name, if rewriteHost is not empty  and rewriteFunc is not nil, then rewrite the host header to rewriteHost
-func (v *VhostMuxer) Listen(name string, rewriteHost string) (l *Listener, err error) {
+func (v *VhostMuxer) Listen(name string, rewriteHost, userName, passWord string) (l *Listener, err error) {
 	v.mutex.Lock()
 	defer v.mutex.Unlock()
 	if _, exist := v.registryMap[name]; exist {
@@ -61,6 +62,8 @@ func (v *VhostMuxer) Listen(name string, rewriteHost string) (l *Listener, err e
 	l = &Listener{
 		name:        name,
 		rewriteHost: rewriteHost,
+		userName:    userName,
+		passWord:    passWord,
 		mux:         v,
 		accept:      make(chan *conn.Conn),
 	}
@@ -109,13 +112,13 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 		return
 	}
 
-	sConn, name, err := v.vhostFunc(c)
+	sConn, reqInfoMap, err := v.vhostFunc(c)
 	if err != nil {
 		c.Close()
 		return
 	}
 
-	name = strings.ToLower(name)
+	name := strings.ToLower(reqInfoMap["Host"])
 	// get listener by hostname
 	l, ok := v.getListener(name)
 	if !ok {
@@ -123,6 +126,20 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 		return
 	}
 
+	// if authFunc is exist and userName/password is set
+	// verify user access
+	fmt.Printf("reqInfo: %+v\n", reqInfoMap)
+	if l.mux.authFunc != nil &&
+		l.userName != "" && l.passWord != "" {
+		bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
+		if bAccess == false || err != nil {
+			res := noAuthResponse()
+			res.Write(c.TcpConn)
+			c.Close()
+			return
+		}
+	}
+
 	if err = sConn.SetDeadline(time.Time{}); err != nil {
 		c.Close()
 		return
@@ -135,6 +152,8 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 type Listener struct {
 	name        string
 	rewriteHost string
+	userName    string
+	passWord    string
 	mux         *VhostMuxer // for closing VhostMuxer
 	accept      chan *conn.Conn
 }
@@ -154,6 +173,7 @@ func (l *Listener) Accept() (*conn.Conn, error) {
 		}
 		conn.SetTcpConn(sConn)
 	}
+
 	return conn, nil
 }
 

From 78c68457816e172b4468330cc0b034308c1a3a6b Mon Sep 17 00:00:00 2001
From: Maodanping <673698750@qq.com>
Date: Mon, 29 Aug 2016 11:02:59 +0800
Subject: [PATCH 2/2] utils/vhost: remove fmt.Printf method

---
 src/utils/vhost/vhost.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/utils/vhost/vhost.go b/src/utils/vhost/vhost.go
index fa33b324..c355bc71 100644
--- a/src/utils/vhost/vhost.go
+++ b/src/utils/vhost/vhost.go
@@ -128,7 +128,6 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 
 	// if authFunc is exist and userName/password is set
 	// verify user access
-	fmt.Printf("reqInfo: %+v\n", reqInfoMap)
 	if l.mux.authFunc != nil &&
 		l.userName != "" && l.passWord != "" {
 		bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])