summaryrefslogtreecommitdiff
path: root/pkg/server/socket/proxy.go
blob: f6c4f72f45e856faae6556670812db9d6b6a701c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package socket

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"fmt"

	"tunnel/pkg/http"
	"tunnel/pkg/server/env"
	"tunnel/pkg/server/queue"
)

type status struct {
	code int
	desc string
}

type proxySocket struct {
	Proto string `opts:"default:tcp"`
}

type proxyServer struct {
	sock *proxySocket
	addr string
	auth string
	wait chan status
	env  env.Env
	conn *conn
}

func (sock *proxySocket) New(env env.Env) (Conn, error) {
	s := &proxyServer{
		sock: sock,
		auth: env.Value("proxy.auth"),
		wait: make(chan status),
		env:  env,
	}

	return s, nil
}

func (sock *proxySocket) Close() {
}

func (s *proxyServer) String() string {
	return "proxy(" + s.addr + ")"
}

func (s *proxyServer) Send(wq queue.Q) error {
	var out bytes.Buffer

	status := <-s.wait

	fmt.Fprintf(&out, "HTTP/1.0 %d %s\r\n", status.code, status.desc)
	switch status.code {
	case http.OK:
		fmt.Fprintf(&out, "Proxy-Agent: tunnel\r\n")
	case 407:
		fmt.Fprintf(&out, "Proxy-Authenticate: Basic realm=\"tunnel\"\r\n")
		fallthrough
	default:
		fmt.Fprintf(&out, "Server: tunnel\r\n")
		fmt.Fprintf(&out, "Connection: close\r\n")
	}
	fmt.Fprintf(&out, "\r\n")

	wq <- out.Bytes()

	if status.code != http.OK {
		return nil
	}

	return s.conn.Send(wq)
}

func (s *proxyServer) dial(addr string) error {
	conn, err := dial(context.TODO(), s.env, s.sock.Proto, addr)
	if err != nil {
		return err
	}

	s.addr = addr
	s.conn = conn

	return nil
}

func (s *proxyServer) Recv(rq queue.Q) error {
	r := bufio.NewReader(rq.Reader())

	req, err := http.ParseRequest(r)
	if err != nil {
		s.wait <- status{400, "Bad Request"}
		return err
	}

	// TODO check if extra data is available in reader

	if req.Method != "CONNECT" {
		s.wait <- status{400, "Bad Request"}
		return errors.New("bad method")
	}

	if s.auth != "" {
		if auth, ok := req.Header["Proxy-Authorization"]; !ok {
			s.wait <- status{407, "Proxy Authentication Required"}
			return errors.New("auth required")
		} else if !http.BasicAuthCheck(s.auth, auth) {
			s.wait <- status{401, "Unauthorized"}
			return errors.New("auth failed")
		}
	}

	if err := s.dial(req.URI); err != nil {
		s.wait <- status{500, "Unable to connect"}
		return err
	}

	s.wait <- status{200, "Connection established"}

	return queue.IoCopy(r, s.conn)
}

func (s *proxyServer) Close() (err error) {
	// TODO safe close
	if s.conn != nil {
		err = s.conn.Close()
	}

	return
}

func init() {
	register("proxy", "http connect proxy server", proxySocket{})
}