forked from takyo101/lantern-android
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
176 lines (139 loc) · 4.01 KB
/
config.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
package client
import (
"compress/gzip"
"crypto/x509"
"errors"
"io"
"io/ioutil"
"log"
"net/http"
"reflect"
"time"
"github.com/getlantern/fronted"
"github.com/getlantern/keyman"
"github.com/getlantern/yaml"
)
const (
httpIfNoneMatch = "If-None-Match"
httpEtag = "Etag"
)
var httpDefaultClient = &http.Client{Timeout: time.Second * 5}
var lastCloudConfigETag string
type clientCfg struct {
FrontedServers []frontedServer `yaml:"frontedservers"`
MasqueradeSets map[string][]*fronted.Masquerade `yaml:"masqueradesets"`
}
// config provides client configuration.
type config struct {
Client clientCfg `yaml:"client"`
TrustedCAs []*ca `yaml:"trustedcas"`
}
var (
// errFailedConfigRequest is returned when the server replies with a non-200
// status code to our request for a configuration file.
errFailedConfigRequest = errors.New(`Could not get configuration file.`)
// errInvalidConfiguration is returned in case the configuration file is
// downloaded but has no useful data.
errInvalidConfiguration = errors.New(`Invalid configuration file.`)
errConfigurationUnchanged = errors.New(`Configuration remain unchanged.`)
)
const (
cloudConfigCA = ``
// URL of the configuration file. Remember to use HTTPs.
remoteConfigURL = `https://s3.amazonaws.com/lantern_config/cloud.1.6.0.yaml.gz`
)
// pullConfigFile attempts to retrieve a configuration file over the network,
// then it decompresses it and returns the file's raw bytes.
func pullConfigFile(cli *http.Client) ([]byte, error) {
var err error
var req *http.Request
var res *http.Response
if cli == nil {
return nil, errors.New("Missing HTTP client.")
}
if req, err = http.NewRequest("GET", remoteConfigURL, nil); err != nil {
return nil, err
}
if lastCloudConfigETag != "" {
// Don't bother fetching if unchanged.
req.Header.Set(httpIfNoneMatch, lastCloudConfigETag)
}
if res, err = cli.Do(req); err != nil {
return nil, err
}
// Has changed?
if res.StatusCode == http.StatusNotModified {
log.Printf("Configuration file has not changed since last pull.\n")
return nil, errConfigurationUnchanged
}
// Expecting 200 OK
if res.StatusCode != http.StatusOK {
return nil, errFailedConfigRequest
}
// Saving ETAG
lastCloudConfigETag = res.Header.Get(httpEtag)
// Using a gzip reader as we're getting a compressed file.
var body io.ReadCloser
if body, err = gzip.NewReader(res.Body); err != nil {
return nil, err
}
defer body.Close()
// Uncompressing bytes.
return ioutil.ReadAll(body)
}
// defaultConfig returns the embedded configuration.
func defaultConfig() *config {
cfg := &config{
Client: clientCfg{
FrontedServers: defaultFrontedServerList,
MasqueradeSets: defaultMasqueradeSets,
},
TrustedCAs: defaultTrustedCAs,
}
return cfg
}
// getConfig attempts to provide a
func getConfig() (*config, error) {
var err error
var buf []byte
var cfg config
// Attempt to download configuration file.
if buf, err = pullConfigFile(httpDefaultClient); err != nil {
return defaultConfig(), err
}
if err = cfg.updateFrom(buf); err != nil {
return defaultConfig(), err
}
return &cfg, nil
}
func (c *config) updateFrom(buf []byte) error {
var err error
var newCfg config
// Attempt to parse configuration file.
if err = yaml.Unmarshal(buf, &newCfg); err != nil {
return err
}
// Making sure we can actually use this configuration.
if len(newCfg.Client.FrontedServers) > 0 && len(newCfg.Client.MasqueradeSets) > 0 && len(newCfg.TrustedCAs) > 0 {
if reflect.DeepEqual(newCfg, *c) {
return errConfigurationUnchanged
}
*c = newCfg
return nil
}
return errInvalidConfiguration
}
func (c *config) getTrustedCerts() []string {
certs := make([]string, 0, len(c.TrustedCAs))
for _, ca := range c.TrustedCAs {
certs = append(certs, ca.Cert)
}
return certs
}
func (c *config) getTrustedCertPool() (certPool *x509.CertPool, err error) {
trustedCerts := c.getTrustedCerts()
if certPool, err = keyman.PoolContainingCerts(trustedCerts...); err != nil {
return nil, err
}
return certPool, nil
}