/
tshttpproxy_synology.go
140 lines (115 loc) · 3.01 KB
/
tshttpproxy_synology.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
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package tshttpproxy
import (
"bytes"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"tailscale.com/util/lineread"
)
// These vars are overridden for tests.
var (
synologyProxyConfigPath = "/etc/proxy.conf"
openSynologyProxyConf = func() (io.ReadCloser, error) {
return os.Open(synologyProxyConfigPath)
}
)
var cache struct {
sync.Mutex
httpProxy *url.URL
httpsProxy *url.URL
updated time.Time
}
func synologyProxyFromConfigCached(req *http.Request) (*url.URL, error) {
if req.URL == nil {
return nil, nil
}
cache.Lock()
defer cache.Unlock()
var err error
modtime := mtime(synologyProxyConfigPath)
if modtime != cache.updated {
cache.httpProxy, cache.httpsProxy, err = synologyProxiesFromConfig()
cache.updated = modtime
}
if req.URL.Scheme == "https" {
return cache.httpsProxy, err
}
return cache.httpProxy, err
}
func synologyProxiesFromConfig() (*url.URL, *url.URL, error) {
r, err := openSynologyProxyConf()
if err != nil {
if os.IsNotExist(err) {
return nil, nil, nil
}
return nil, nil, err
}
defer r.Close()
return parseSynologyConfig(r)
}
// parseSynologyConfig parses the Synology proxy configuration, and returns any
// http proxy, and any https proxy respectively, or an error if parsing fails.
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
cfg := map[string]string{}
if err := lineread.Reader(r, func(line []byte) error {
// accept and skip over empty lines
line = bytes.TrimSpace(line)
if len(line) == 0 {
return nil
}
key, value, ok := strings.Cut(string(line), "=")
if !ok {
return fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
}
cfg[string(key)] = string(value)
return nil
}); err != nil {
return nil, nil, err
}
if cfg["proxy_enabled"] != "yes" {
return nil, nil, nil
}
httpProxyURL := new(url.URL)
httpsProxyURL := new(url.URL)
if cfg["auth_enabled"] == "yes" {
httpProxyURL.User = url.UserPassword(cfg["proxy_user"], cfg["proxy_pwd"])
httpsProxyURL.User = url.UserPassword(cfg["proxy_user"], cfg["proxy_pwd"])
}
// As far as we are aware, synology does not support tls proxies.
httpProxyURL.Scheme = "http"
httpsProxyURL.Scheme = "http"
httpsProxyURL = addHostPort(httpsProxyURL, cfg["https_host"], cfg["https_port"])
httpProxyURL = addHostPort(httpProxyURL, cfg["http_host"], cfg["http_port"])
return httpProxyURL, httpsProxyURL, nil
}
// addHostPort adds to u the given host and port and returns the updated url, or
// if host is empty, it returns nil.
func addHostPort(u *url.URL, host, port string) *url.URL {
if host == "" {
return nil
}
if port == "" {
u.Host = host
} else {
u.Host = net.JoinHostPort(host, port)
}
return u
}
// mtime stat's path and returns its modification time. If path does not exist,
// it returns the unix epoch.
func mtime(path string) time.Time {
fi, err := os.Stat(path)
if err != nil {
return time.Unix(0, 0)
}
return fi.ModTime()
}