forked from SenseUnit/dumbproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
217 lines (195 loc) · 6.35 KB
/
main.go
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package main
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/crypto/bcrypt"
)
var (
home, _ = os.UserHomeDir()
version = "undefined"
)
func perror(msg string) {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, msg)
}
func arg_fail(msg string) {
perror(msg)
perror("Usage:")
flag.PrintDefaults()
os.Exit(2)
}
type CSVArg []string
func (a *CSVArg) Set(s string) error {
*a = strings.Split(s, ",")
return nil
}
func (a *CSVArg) String() string {
if a == nil {
return "<nil>"
}
if *a == nil {
return "<empty>"
}
return strings.Join(*a, ",")
}
type CLIArgs struct {
bind_address string
auth string
verbosity int
timeout time.Duration
cert, key, cafile string
list_ciphers bool
ciphers string
disableHTTP2 bool
showVersion bool
autocert bool
autocertWhitelist CSVArg
autocertDir string
autocertACME string
autocertEmail string
autocertHTTP string
passwd string
passwdCost int
positionalArgs []string
proxy []string
}
func parse_args() CLIArgs {
var args CLIArgs
flag.StringVar(&args.bind_address, "bind-address", ":8080", "HTTP proxy listen address")
flag.StringVar(&args.auth, "auth", "none://", "auth parameters")
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
flag.DurationVar(&args.timeout, "timeout", 10*time.Second, "timeout for network operations")
flag.StringVar(&args.cert, "cert", "", "enable TLS and use certificate")
flag.StringVar(&args.key, "key", "", "key for TLS certificate")
flag.StringVar(&args.cafile, "cafile", "", "CA file to authenticate clients with certificates")
flag.BoolVar(&args.list_ciphers, "list-ciphers", false, "list ciphersuites")
flag.StringVar(&args.ciphers, "ciphers", "", "colon-separated list of enabled ciphers")
flag.BoolVar(&args.disableHTTP2, "disable-http2", false, "disable HTTP2")
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
flag.BoolVar(&args.autocert, "autocert", false, "issue TLS certificates automatically")
flag.Var(&args.autocertWhitelist, "autocert-whitelist", "restrict autocert domains to this comma-separated list")
flag.StringVar(&args.autocertDir, "autocert-dir", filepath.Join(home, ".dumbproxy", "autocert"), "path to autocert cache")
flag.StringVar(&args.autocertACME, "autocert-acme", autocert.DefaultACMEDirectory, "custom ACME endpoint")
flag.StringVar(&args.autocertEmail, "autocert-email", "", "email used for ACME registration")
flag.StringVar(&args.autocertHTTP, "autocert-http", "", "listen address for HTTP-01 challenges handler of ACME")
flag.StringVar(&args.passwd, "passwd", "", "update given htpasswd file and add/set password for username. "+
"Username and password can be passed as positional arguments or requested interactively")
flag.IntVar(&args.passwdCost, "passwd-cost", bcrypt.MinCost, "bcrypt password cost (for -passwd mode)")
flag.Func("proxy", "upstream proxy URL. Can be repeated multiple times to chain proxies. Examples: socks5h://127.0.0.1:9050; https://user:password@example.com:443", func(p string) error {
args.proxy = append(args.proxy, p)
return nil
})
flag.Parse()
args.positionalArgs = flag.Args()
return args
}
func run() int {
args := parse_args()
if args.showVersion {
fmt.Println(version)
return 0
}
if args.list_ciphers {
list_ciphers()
return 0
}
if args.passwd != "" {
if err := passwd(args.passwd, args.passwdCost, args.positionalArgs...); err != nil {
log.Fatalf("can't set password: %v", err)
}
return 0
}
logWriter := NewLogWriter(os.Stderr)
defer logWriter.Close()
mainLogger := NewCondLogger(log.New(logWriter, "MAIN : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
proxyLogger := NewCondLogger(log.New(logWriter, "PROXY : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
authLogger := NewCondLogger(log.New(logWriter, "AUTH : ",
log.LstdFlags|log.Lshortfile),
args.verbosity)
auth, err := NewAuth(args.auth, authLogger)
if err != nil {
mainLogger.Critical("Failed to instantiate auth provider: %v", err)
return 3
}
defer auth.Stop()
var dialer Dialer = new(net.Dialer)
for _, proxyURL := range args.proxy {
newDialer, err := proxyDialerFromURL(proxyURL, dialer)
if err != nil {
mainLogger.Critical("Failed to create dialer for proxy %q: %v", proxyURL, err)
return 3
}
dialer = newDialer
}
server := http.Server{
Addr: args.bind_address,
Handler: NewProxyHandler(args.timeout, auth, maybeWrapWithContextDialer(dialer), proxyLogger),
ErrorLog: log.New(logWriter, "HTTPSRV : ", log.LstdFlags|log.Lshortfile),
ReadTimeout: 0,
ReadHeaderTimeout: 0,
WriteTimeout: 0,
IdleTimeout: 0,
}
if args.disableHTTP2 {
server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
mainLogger.Info("Starting proxy server...")
if args.cert != "" {
cfg, err1 := makeServerTLSConfig(args.cert, args.key, args.cafile, args.ciphers)
if err1 != nil {
mainLogger.Critical("TLS config construction failed: %v", err1)
return 3
}
server.TLSConfig = cfg
err = server.ListenAndServeTLS("", "")
} else if args.autocert {
m := &autocert.Manager{
Cache: autocert.DirCache(args.autocertDir),
Prompt: autocert.AcceptTOS,
Client: &acme.Client{DirectoryURL: args.autocertACME},
Email: args.autocertEmail,
}
if args.autocertWhitelist != nil {
m.HostPolicy = autocert.HostWhitelist([]string(args.autocertWhitelist)...)
}
if args.autocertHTTP != "" {
go func() {
log.Fatalf("HTTP-01 ACME challenge server stopped: %v",
http.ListenAndServe(args.autocertHTTP, m.HTTPHandler(nil)))
}()
}
cfg := m.TLSConfig()
cfg, err = updateServerTLSConfig(cfg, args.cafile, args.ciphers)
if err != nil {
mainLogger.Critical("TLS config construction failed: %v", err)
return 3
}
server.TLSConfig = cfg
err = server.ListenAndServeTLS("", "")
mainLogger.Info("Proxy server started.")
} else {
mainLogger.Info("Proxy server started.")
err = server.ListenAndServe()
}
mainLogger.Critical("Server terminated with a reason: %v", err)
mainLogger.Info("Shutting down...")
return 0
}
func main() {
os.Exit(run())
}