1package ieproxy
2
3import (
4	"strings"
5	"sync"
6	"unsafe"
7
8	"golang.org/x/sys/windows/registry"
9)
10
11type regeditValues struct {
12	ProxyServer   string
13	ProxyOverride string
14	ProxyEnable   uint64
15	AutoConfigURL string
16}
17
18var once sync.Once
19var windowsProxyConf ProxyConf
20
21// GetConf retrieves the proxy configuration from the Windows Regedit
22func getConf() ProxyConf {
23	once.Do(writeConf)
24	return windowsProxyConf
25}
26
27func writeConf() {
28	proxy := ""
29	proxyByPass := ""
30	autoConfigUrl := ""
31	autoDetect := false
32
33	// Try from IE first.
34	if ieCfg, err := getUserConfigFromWindowsSyscall(); err == nil {
35		defer globalFreeWrapper(ieCfg.lpszProxy)
36		defer globalFreeWrapper(ieCfg.lpszProxyBypass)
37		defer globalFreeWrapper(ieCfg.lpszAutoConfigUrl)
38
39		proxy = StringFromUTF16Ptr(ieCfg.lpszProxy)
40		proxyByPass = StringFromUTF16Ptr(ieCfg.lpszProxyBypass)
41		autoConfigUrl = StringFromUTF16Ptr(ieCfg.lpszAutoConfigUrl)
42		autoDetect = ieCfg.fAutoDetect
43	}
44
45	if proxy == "" && !autoDetect{
46		// Try WinHTTP default proxy.
47		if defaultCfg, err := getDefaultProxyConfiguration(); err == nil {
48			defer globalFreeWrapper(defaultCfg.lpszProxy)
49			defer globalFreeWrapper(defaultCfg.lpszProxyBypass)
50
51			// Always set both of these (they are a pair, it doesn't make sense to set one here and keep the value of the other from above)
52			proxy = StringFromUTF16Ptr(defaultCfg.lpszProxy)
53			proxyByPass = StringFromUTF16Ptr(defaultCfg.lpszProxyBypass)
54		}
55	}
56
57	if proxy == "" && !autoDetect {
58		// Fall back to IE registry or manual detection if nothing is found there..
59		regedit, _ := readRegedit() // If the syscall fails, backup to manual detection.
60		windowsProxyConf = parseRegedit(regedit)
61		return
62	}
63
64	// Setting the proxy settings.
65	windowsProxyConf = ProxyConf{
66		Static: StaticProxyConf{
67			Active: len(proxy) > 0,
68		},
69		Automatic: ProxyScriptConf{
70			Active: len(autoConfigUrl) > 0 || autoDetect,
71		},
72	}
73
74	if windowsProxyConf.Static.Active {
75		protocol := make(map[string]string)
76		for _, s := range strings.Split(proxy, ";") {
77			s = strings.TrimSpace(s)
78			if s == "" {
79				continue
80			}
81			pair := strings.SplitN(s, "=", 2)
82			if len(pair) > 1 {
83				protocol[pair[0]] = pair[1]
84			} else {
85				protocol[""] = pair[0]
86			}
87		}
88
89		windowsProxyConf.Static.Protocols = protocol
90		if len(proxyByPass) > 0 {
91			windowsProxyConf.Static.NoProxy = strings.Replace(proxyByPass, ";", ",", -1)
92		}
93	}
94
95	if windowsProxyConf.Automatic.Active {
96		windowsProxyConf.Automatic.PreConfiguredURL = autoConfigUrl
97	}
98}
99
100func getUserConfigFromWindowsSyscall() (*tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG, error) {
101	if err := winHttpGetIEProxyConfigForCurrentUser.Find(); err != nil {
102		return nil, err
103	}
104	p := new(tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG)
105	r, _, err := winHttpGetIEProxyConfigForCurrentUser.Call(uintptr(unsafe.Pointer(p)))
106	if rTrue(r) {
107		return p, nil
108	}
109	return nil, err
110}
111
112func getDefaultProxyConfiguration() (*tWINHTTP_PROXY_INFO, error) {
113	pInfo := new(tWINHTTP_PROXY_INFO)
114	if err := winHttpGetDefaultProxyConfiguration.Find(); err != nil {
115		return nil, err
116	}
117	r, _, err := winHttpGetDefaultProxyConfiguration.Call(uintptr(unsafe.Pointer(pInfo)))
118	if rTrue(r) {
119		return pInfo, nil
120	}
121	return nil, err
122}
123
124// OverrideEnvWithStaticProxy writes new values to the
125// http_proxy, https_proxy and no_proxy environment variables.
126// The values are taken from the Windows Regedit (should be called in init() function)
127func overrideEnvWithStaticProxy(conf ProxyConf, setenv envSetter) {
128	if conf.Static.Active {
129		for _, scheme := range []string{"http", "https"} {
130			url := mapFallback(scheme, "", conf.Static.Protocols)
131			setenv(scheme+"_proxy", url)
132		}
133		if conf.Static.NoProxy != "" {
134			setenv("no_proxy", conf.Static.NoProxy)
135		}
136	}
137}
138
139func parseRegedit(regedit regeditValues) ProxyConf {
140	protocol := make(map[string]string)
141	for _, s := range strings.Split(regedit.ProxyServer, ";") {
142		if s == "" {
143			continue
144		}
145		pair := strings.SplitN(s, "=", 2)
146		if len(pair) > 1 {
147			protocol[pair[0]] = pair[1]
148		} else {
149			protocol[""] = pair[0]
150		}
151	}
152
153	return ProxyConf{
154		Static: StaticProxyConf{
155			Active:    regedit.ProxyEnable > 0,
156			Protocols: protocol,
157			NoProxy:   strings.Replace(regedit.ProxyOverride, ";", ",", -1), // to match linux style
158		},
159		Automatic: ProxyScriptConf{
160			Active:           regedit.AutoConfigURL != "",
161			PreConfiguredURL: regedit.AutoConfigURL,
162		},
163	}
164}
165
166func readRegedit() (values regeditValues, err error) {
167	var proxySettingsPerUser uint64 = 1 // 1 is the default value to consider current user
168	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
169	if err == nil {
170		//We had used the below variable tempPrxUsrSettings, because the Golang method GetIntegerValue
171		//sets the value to zero even it fails.
172		tempPrxUsrSettings, _, err := k.GetIntegerValue("ProxySettingsPerUser")
173		if err == nil {
174			//consider the value of tempPrxUsrSettings if it is a success
175			proxySettingsPerUser = tempPrxUsrSettings
176		}
177		k.Close()
178	}
179
180	var hkey registry.Key
181	if proxySettingsPerUser == 0 {
182		hkey = registry.LOCAL_MACHINE
183	} else {
184		hkey = registry.CURRENT_USER
185	}
186
187	k, err = registry.OpenKey(hkey, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
188	if err != nil {
189		return
190	}
191	defer k.Close()
192
193	values.ProxyServer, _, err = k.GetStringValue("ProxyServer")
194	if err != nil && err != registry.ErrNotExist {
195		return
196	}
197	values.ProxyOverride, _, err = k.GetStringValue("ProxyOverride")
198	if err != nil && err != registry.ErrNotExist {
199		return
200	}
201
202	values.ProxyEnable, _, err = k.GetIntegerValue("ProxyEnable")
203	if err != nil && err != registry.ErrNotExist {
204		return
205	}
206
207	values.AutoConfigURL, _, err = k.GetStringValue("AutoConfigURL")
208	if err != nil && err != registry.ErrNotExist {
209		return
210	}
211	err = nil
212	return
213}
214