Skip to content

Commit

Permalink
Merge branch 'develop', version 0.8
Browse files Browse the repository at this point in the history
  • Loading branch information
cyfdecyf committed Aug 10, 2013
2 parents 34a790b + 7209fd4 commit 93f8590
Show file tree
Hide file tree
Showing 20 changed files with 900 additions and 593 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
0.8 (2013-08-10)
* Share server connections between different clients
* Add tunnelAllowedPort option to limit ports CONNECT method can connect to
* Avoid timeout too soon for frequently visited direct sites
* Fix reporting malformed requests in two cases when request has body:
- Authenticate requests
- Error occured before request is sent
* Support multi-lined headers
* Change client connection timeout to 15s
* Change as direct delta to 15
* Provide ARMv5 binary

0.7.6 (2013-07-28)
* Fix bug for close connection response with no body
* Fix response not keep alive by default
Expand Down
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

COW 是一个利用二级代理自动化穿越防火墙的 HTTP 代理服务器。它能自动检测被墙网站,仅对这些网站使用二级代理。

当前版本:0.7.6 [CHANGELOG](CHANGELOG)
当前版本:0.8 [CHANGELOG](CHANGELOG)
[![Build Status](https://travis-ci.org/cyfdecyf/cow.png?branch=master)](https://travis-ci.org/cyfdecyf/cow)

**欢迎在 develop branch 进行开发并发送 pull request :)**
Expand All @@ -24,7 +24,7 @@ COW 的设计目标是自动化,理想情况下用户无需关心哪些网站

- **OS X, Linux:** 执行以下命令(也可用于更新)

curl -s -L https://github.com/cyfdecyf/cow/raw/master/install-cow.sh | bash
curl -L git.io/cow | bash

- 该安装脚本在 OS X 上可将 COW 设置为登录时启动
- [Linux 启动脚本](doc/init.d/cow),如何使用请参考注释(Debian 测试通过,其他 Linux 发行版应该也可使用)
Expand Down Expand Up @@ -106,13 +106,12 @@ COW 默认配置下检测到被墙后,过两分钟再次尝试直连也是为

贡献代码:

@tevino: http parent proxy basic authentication
@xupefei: 提供 cow-hide.exe 以在 windows 上在后台执行 cow.exe
- @tevino: http parent proxy basic authentication
- @xupefei: 提供 cow-hide.exe 以在 windows 上在后台执行 cow.exe

Bug reporter:

GitHub users: glacjay, trawor, Blaskyy, lucifer9, zellux, xream, hieixu, fantasticfears, perrywky, JayXon, graminc, WingGao, polong, dallascao

Twitter users: @shao222
- GitHub users: glacjay, trawor, Blaskyy, lucifer9, zellux, xream, hieixu, fantasticfears, perrywky, JayXon, graminc, WingGao, polong, dallascao
- Twitter users: 特别感谢 @shao222 多次帮助测试新版并报告了不少 bug, @xixitalk

@glacjay 对 0.3 版本的 COW 提出了让它更加自动化的建议,使我重新考虑 COW 的设计目标并且改进成 0.5 版本之后的工作方式。
70 changes: 26 additions & 44 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,6 @@ func Authenticate(conn *clientConn, r *Request) (err error) {
if authIP(clientIP) { // IP is allowed
return
}
/*
// No user specified
if auth.user == "" {
sendErrorPage(conn, "403 Forbidden", "Access forbidden",
"You are not allowed to use the proxy.")
return errShouldClose
}
*/
err = authUserPasswd(conn, r)
if err == nil {
auth.authed.add(clientIP)
Expand Down Expand Up @@ -227,51 +219,47 @@ func genNonce() string {

func calcRequestDigest(kv map[string]string, ha1, method string) string {
// Refer to rfc2617 section 3.2.2.1 Request-Digest
buf := bytes.NewBufferString(ha1)
buf.WriteByte(':')
buf.WriteString(kv["nonce"])
buf.WriteByte(':')
buf.WriteString(kv["nc"])
buf.WriteByte(':')
buf.WriteString(kv["cnonce"])
buf.WriteByte(':')
buf.WriteString("auth") // qop value
buf.WriteByte(':')
buf.WriteString(md5sum(method + ":" + kv["uri"]))

return md5sum(buf.String())
arr := []string{
ha1,
kv["nonce"],
kv["nc"],
kv["cnonce"],
"auth",
md5sum(method + ":" + kv["uri"]),
}
return md5sum(strings.Join(arr, ":"))
}

func checkProxyAuthorization(conn *clientConn, r *Request) error {
debug.Println("authorization:", r.ProxyAuthorization)
if debug {
debug.Printf("cli(%s) authorization: %s\n", conn.RemoteAddr(), r.ProxyAuthorization)
}

arr := strings.SplitN(r.ProxyAuthorization, " ", 2)
if len(arr) != 2 {
errl.Println("auth: malformed ProxyAuthorization header:", r.ProxyAuthorization)
return errBadRequest
return errors.New("auth: malformed ProxyAuthorization header: " + r.ProxyAuthorization)
}
if strings.ToLower(strings.TrimSpace(arr[0])) != "digest" {
errl.Println("auth: client using unsupported authenticate method:", arr[0])
return errBadRequest
return errors.New("auth: method " + arr[0] + " unsupported, must use digest")
}
authHeader := parseKeyValueList(arr[1])
if len(authHeader) == 0 {
errl.Println("auth: empty authorization list")
return errBadRequest
return errors.New("auth: empty authorization list")
}
nonceTime, err := strconv.ParseInt(authHeader["nonce"], 16, 64)
if err != nil {
return err
return fmt.Errorf("auth: nonce %v", err)
}
// If nonce time too early, reject. iOS will create a new connection to do
// authenticate.
// authentication.
if time.Now().Sub(time.Unix(nonceTime, 0)) > time.Minute {
return errAuthRequired
}

user := authHeader["username"]
au, ok := auth.user[user]
if !ok {
errl.Println("auth: no such user:", authHeader["username"])
errl.Printf("cli(%s) auth: no such user: %s\n", conn.RemoteAddr(), authHeader["username"])
return errAuthRequired
}

Expand All @@ -280,30 +268,26 @@ func checkProxyAuthorization(conn *clientConn, r *Request) error {
_, portStr, _ := net.SplitHostPort(conn.LocalAddr().String())
port, _ := strconv.Atoi(portStr)
if uint16(port) != au.port {
errl.Println("auth: user", user, "port not match")
errl.Printf("cli(%s) auth: user %s port not match\n", conn.RemoteAddr(), user)
return errAuthRequired
}
}

if authHeader["qop"] != "auth" {
msg := "auth: qop wrong: " + authHeader["qop"]
errl.Println(msg)
return errors.New(msg)
return errors.New("auth: qop wrong: " + authHeader["qop"])
}

response, ok := authHeader["response"]
if !ok {
msg := "auth: no request-digest"
errl.Println(msg)
return errors.New(msg)
return errors.New("auth: no request-digest response")
}

au.initHA1(user)
digest := calcRequestDigest(authHeader, au.ha1, r.Method)
if response == digest {
return nil
}
errl.Println("auth: digest not match, maybe password wrong")
errl.Printf("cli(%s) auth: digest not match, maybe password wrong", conn.RemoteAddr())
return errAuthRequired
}

Expand All @@ -328,15 +312,13 @@ func authUserPasswd(conn *clientConn, r *Request) (err error) {
}
buf := new(bytes.Buffer)
if err := auth.template.Execute(buf, data); err != nil {
errl.Println("Error generating auth response:", err)
return errInternal
return fmt.Errorf("error generating auth response: %v", err)
}
if debug {
if bool(debug) && verbose {
debug.Printf("authorization response:\n%s", buf.String())
}
if _, err := conn.Write(buf.Bytes()); err != nil {
errl.Println("Sending auth response error:", err)
return errShouldClose
return fmt.Errorf("send auth response error: %v", err)
}
return errAuthRequired
}
57 changes: 38 additions & 19 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"flag"
"fmt"
"github.com/cyfdecyf/bufio"
"io"
"net"
"os"
"path"
Expand All @@ -15,7 +14,7 @@ import (
)

const (
version = "0.7.6"
version = "0.8"
defaultListenAddr = "127.0.0.1:7777"
)

Expand All @@ -26,17 +25,27 @@ const (
loadBalanceHash
)

// allow the same tunnel ports as polipo
var defaultTunnelAllowedPort = []string{
"22", "80", "443", // ssh, http, https
"873", // rsync
"143", "220", "585", "993", // imap, imap3, imap4-ssl, imaps
"109", "110", "473", "995", // pop2, pop3, hybrid-pop, pop3s
"5222", "5269", // jabber-client, jabber-server
"2401", // cvspserver
"9418", // git
}

type Config struct {
RcFile string // config file
ListenAddr []string
LogFile string
AlwaysProxy bool
LoadBalance LoadBalanceMode

SshServer []string
TunnelAllowedPort map[string]bool // allowed ports to create tunnel

// http parent proxy
hasHttpParent bool
SshServer []string

// authenticate client
UserPasswd string
Expand All @@ -54,6 +63,8 @@ type Config struct {

// not configurable in config file
PrintVer bool

hasHttpParent bool // not config option
}

var config Config
Expand Down Expand Up @@ -83,6 +94,11 @@ func init() {
config.AuthTimeout = 2 * time.Hour
config.DialTimeout = defaultDialTimeout
config.ReadTimeout = defaultReadTimeout

config.TunnelAllowedPort = make(map[string]bool)
for _, port := range defaultTunnelAllowedPort {
config.TunnelAllowedPort[port] = true
}
}

// Whether command line options specifies listen addr
Expand Down Expand Up @@ -158,9 +174,6 @@ func (p configParser) ParseListen(val string) {
return
}
arr := strings.Split(val, ",")
if config.ListenAddr == nil {
config.ListenAddr = make([]string, 0, len(arr))
}
for _, s := range arr {
s = strings.TrimSpace(s)
host, _, err := net.SplitHostPort(s)
Expand Down Expand Up @@ -196,6 +209,17 @@ func (p configParser) ParseAddrInPAC(val string) {
}
}

func (p configParser) ParseTunnelAllowedPort(val string) {
arr := strings.Split(val, ",")
for _, s := range arr {
s = strings.TrimSpace(s)
if _, err := strconv.Atoi(s); err != nil {
Fatal("tunnel allowed ports", err)
}
config.TunnelAllowedPort[s] = true
}
}

// error checking is done in check config

func (p configParser) ParseSocksParent(val string) {
Expand Down Expand Up @@ -388,23 +412,15 @@ func parseConfig(path string) {

IgnoreUTF8BOM(f)

fr := bufio.NewReader(f)
scanner := bufio.NewScanner(f)

parser := reflect.ValueOf(configParser{})
zeroMethod := reflect.Value{}

var line string
var n int
for {
for scanner.Scan() {
n++
line, err = ReadLine(fr)
if err == io.EOF {
return
} else if err != nil {
Fatalf("Error reading rc file: %v\n", err)
}

line = strings.TrimSpace(line)
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
Expand All @@ -427,6 +443,9 @@ func parseConfig(path string) {
args := []reflect.Value{reflect.ValueOf(val)}
method.Call(args)
}
if scanner.Err() != nil {
Fatalf("Error reading rc file: %v\n", scanner.Err())
}
}

func updateConfig(nc *Config) {
Expand Down
29 changes: 29 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,32 @@ func TestParseListen(t *testing.T) {
t.Error("multiple listen address parse error")
}
}

func TestTunnelAllowedPort(t *testing.T) {
parser := configParser{}
parser.ParseTunnelAllowedPort("1, 2, 3, 4, 5")
parser.ParseTunnelAllowedPort("6")
parser.ParseTunnelAllowedPort("7")
parser.ParseTunnelAllowedPort("8")

testData := []struct {
port string
allowed bool
}{
{"80", true}, // default allowd ports
{"443", true},
{"1", true},
{"3", true},
{"5", true},
{"7", true},
{"8080", false},
{"8388", false},
}

for _, td := range testData {
allowed := config.TunnelAllowedPort[td.port]
if allowed != td.allowed {
t.Errorf("port %s allowed %v, got %v\n", td.port, td.allowed, allowed)
}
}
}
Loading

0 comments on commit 93f8590

Please sign in to comment.