diff --git a/proxy/http/config.go b/proxy/http/config.go index d02cfda64..cbe486d5a 100644 --- a/proxy/http/config.go +++ b/proxy/http/config.go @@ -1 +1,13 @@ package http + +func (sc *ServerConfig) HasAccount(username, password string) bool { + if sc.Accounts == nil { + return false + } + + p, found := sc.Accounts[username] + if !found { + return false + } + return p == password +} diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go index 31474746d..37df52f0a 100644 --- a/proxy/http/config.pb.go +++ b/proxy/http/config.pb.go @@ -17,7 +17,8 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // Config for HTTP proxy server. type ServerConfig struct { - Timeout uint32 `protobuf:"varint,1,opt,name=timeout" json:"timeout,omitempty"` + Timeout uint32 `protobuf:"varint,1,opt,name=timeout" json:"timeout,omitempty"` + Accounts map[string]string `protobuf:"bytes,2,rep,name=accounts" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` } func (m *ServerConfig) Reset() { *m = ServerConfig{} } @@ -32,6 +33,13 @@ func (m *ServerConfig) GetTimeout() uint32 { return 0 } +func (m *ServerConfig) GetAccounts() map[string]string { + if m != nil { + return m.Accounts + } + return nil +} + // ClientConfig for HTTP proxy client. type ClientConfig struct { } @@ -49,16 +57,20 @@ func init() { func init() { proto.RegisterFile("v2ray.com/core/proxy/http/config.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 161 bytes of a gzipped FileDescriptorProto + // 238 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2b, 0x33, 0x2a, 0x4a, 0xac, 0xd4, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x2f, 0x28, 0xca, 0xaf, 0xa8, 0xd4, 0xcf, 0x28, 0x29, 0x29, 0xd0, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, - 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xa9, 0x2b, 0x4a, 0xd5, 0x03, 0xab, 0xd1, 0x03, 0xa9, 0x51, 0xd2, - 0xe0, 0xe2, 0x09, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0x72, 0x06, 0x2b, 0x16, 0x92, 0xe0, 0x62, 0x2f, - 0xc9, 0xcc, 0x4d, 0xcd, 0x2f, 0x2d, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x82, 0x71, 0x95, - 0xf8, 0xb8, 0x78, 0x9c, 0x73, 0x32, 0x53, 0xf3, 0x4a, 0x20, 0x2a, 0x9d, 0xac, 0xb9, 0x24, 0x93, - 0xf3, 0x73, 0xf5, 0xb0, 0x1a, 0x1b, 0xc0, 0x18, 0xc5, 0x02, 0xa2, 0x57, 0x31, 0x89, 0x86, 0x19, - 0x05, 0x25, 0x56, 0xea, 0x39, 0x83, 0xe4, 0x03, 0xc0, 0xf2, 0x1e, 0x25, 0x25, 0x05, 0x49, 0x6c, - 0x60, 0x47, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xae, 0x86, 0xab, 0xbe, 0x00, 0x00, - 0x00, + 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xa9, 0x2b, 0x4a, 0xd5, 0x03, 0xab, 0xd1, 0x03, 0xa9, 0x51, 0xda, + 0xc2, 0xc8, 0xc5, 0x13, 0x9c, 0x5a, 0x54, 0x96, 0x5a, 0xe4, 0x0c, 0x56, 0x2d, 0x24, 0xc1, 0xc5, + 0x5e, 0x92, 0x99, 0x9b, 0x9a, 0x5f, 0x5a, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x1b, 0x04, 0xe3, + 0x0a, 0xf9, 0x72, 0x71, 0x24, 0x26, 0x27, 0xe7, 0x97, 0xe6, 0x95, 0x14, 0x4b, 0x30, 0x29, 0x30, + 0x6b, 0x70, 0x1b, 0x19, 0xea, 0x61, 0x35, 0x54, 0x0f, 0xd9, 0x40, 0x3d, 0x47, 0xa8, 0x1e, 0xd7, + 0xbc, 0x92, 0xa2, 0xca, 0x20, 0xb8, 0x11, 0x52, 0xd6, 0x5c, 0xbc, 0x28, 0x52, 0x42, 0x02, 0x5c, + 0xcc, 0xd9, 0xa9, 0x95, 0x60, 0x5b, 0x39, 0x83, 0x40, 0x4c, 0x21, 0x11, 0x2e, 0xd6, 0xb2, 0xc4, + 0x9c, 0xd2, 0x54, 0x09, 0x26, 0xb0, 0x18, 0x84, 0x63, 0xc5, 0x64, 0xc1, 0xa8, 0xc4, 0xc7, 0xc5, + 0xe3, 0x9c, 0x93, 0x99, 0x9a, 0x57, 0x02, 0xb1, 0xc4, 0xc9, 0x9a, 0x4b, 0x32, 0x39, 0x3f, 0x17, + 0xbb, 0x73, 0x02, 0x18, 0xa3, 0x58, 0x40, 0xf4, 0x2a, 0x26, 0xd1, 0x30, 0xa3, 0xa0, 0xc4, 0x4a, + 0x3d, 0x67, 0x90, 0x7c, 0x00, 0x58, 0xde, 0xa3, 0xa4, 0xa4, 0x20, 0x89, 0x0d, 0x1c, 0x42, 0xc6, + 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x07, 0x0a, 0xdd, 0xc0, 0x4b, 0x01, 0x00, 0x00, } diff --git a/proxy/http/config.proto b/proxy/http/config.proto index 5cbfdb83c..9d96f1637 100644 --- a/proxy/http/config.proto +++ b/proxy/http/config.proto @@ -9,9 +9,10 @@ option java_multiple_files = true; // Config for HTTP proxy server. message ServerConfig { uint32 timeout = 1; + map accounts = 2; } // ClientConfig for HTTP proxy client. message ClientConfig { -} \ No newline at end of file +} diff --git a/proxy/http/server.go b/proxy/http/server.go index b13b75d46..bceef6a71 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -83,6 +83,15 @@ Start: } return trace } + + if len(s.config.Accounts) > 0 { + user, pass, ok := request.BasicAuth() + if !ok || !s.config.HasAccount(user, pass) { + _, err := conn.Write([]byte("HTTP/1.1 401 UNAUTHORIZED\r\n\r\n")) + return err + } + } + log.Trace(newError("request to Method [", request.Method, "] Host [", request.Host, "] with URL [", request.URL, "]")) conn.SetReadDeadline(time.Time{}) diff --git a/testing/scenarios/http_test.go b/testing/scenarios/http_test.go index ad7ede42c..fbd12dfbe 100644 --- a/testing/scenarios/http_test.go +++ b/testing/scenarios/http_test.go @@ -12,8 +12,8 @@ import ( "v2ray.com/core/common/serial" "v2ray.com/core/proxy/freedom" v2http "v2ray.com/core/proxy/http" - . "v2ray.com/ext/assert" v2httptest "v2ray.com/core/testing/servers/http" + . "v2ray.com/ext/assert" ) func TestHttpConformance(t *testing.T) { @@ -72,3 +72,85 @@ func TestHttpConformance(t *testing.T) { CloseAllServers(servers) } + +func TestHttpBasicAuth(t *testing.T) { + assert := With(t) + + httpServerPort := pickPort() + httpServer := &v2httptest.Server{ + Port: httpServerPort, + PathHandler: make(map[string]http.HandlerFunc), + } + _, err := httpServer.Start() + assert(err, IsNil) + defer httpServer.Close() + + serverPort := pickPort() + serverConfig := &core.Config{ + Inbound: []*proxyman.InboundHandlerConfig{ + { + ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{ + PortRange: net.SinglePortRange(serverPort), + Listen: net.NewIPOrDomain(net.LocalHostIP), + }), + ProxySettings: serial.ToTypedMessage(&v2http.ServerConfig{ + Accounts: map[string]string{ + "a": "b", + }, + }), + }, + }, + Outbound: []*proxyman.OutboundHandlerConfig{ + { + ProxySettings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + servers, err := InitializeServerConfigs(serverConfig) + assert(err, IsNil) + + { + transport := &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + return url.Parse("http://127.0.0.1:" + serverPort.String()) + }, + } + + client := &http.Client{ + Transport: transport, + } + + { + resp, err := client.Get("http://127.0.0.1:" + httpServerPort.String()) + assert(err, IsNil) + assert(resp.StatusCode, Equals, 401) + } + + { + req, err := http.NewRequest("GET", "http://127.0.0.1:"+httpServerPort.String(), nil) + assert(err, IsNil) + + req.SetBasicAuth("a", "c") + resp, err := client.Do(req) + assert(err, IsNil) + assert(resp.StatusCode, Equals, 401) + } + + { + req, err := http.NewRequest("GET", "http://127.0.0.1:"+httpServerPort.String(), nil) + assert(err, IsNil) + + req.SetBasicAuth("a", "b") + resp, err := client.Do(req) + assert(err, IsNil) + assert(resp.StatusCode, Equals, 200) + + content, err := ioutil.ReadAll(resp.Body) + assert(err, IsNil) + assert(string(content), Equals, "Home") + } + } + + CloseAllServers(servers) +}