Directory: | cvmfs/ |
---|---|
File: | cvmfs/wpad.cc |
Date: | 2025-07-13 02:35:07 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 3 | 153 | 2.0% |
Branches: | 5 | 297 | 1.7% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | */ | ||
4 | |||
5 | |||
6 | #include "wpad.h" | ||
7 | |||
8 | #include <fcntl.h> | ||
9 | |||
10 | #include <cstdarg> | ||
11 | #include <cstdio> | ||
12 | #include <cstdlib> | ||
13 | #include <string> | ||
14 | #include <vector> | ||
15 | |||
16 | #include "network/download.h" | ||
17 | #include "pacparser.h" | ||
18 | #include "statistics.h" | ||
19 | #include "util/logging.h" | ||
20 | #include "util/posix.h" | ||
21 | #include "util/string.h" | ||
22 | |||
23 | using namespace std; // NOLINT | ||
24 | |||
25 | namespace download { | ||
26 | |||
27 | const char *kAutoPacLocation = "http://wpad/wpad.dat"; | ||
28 | |||
29 | ✗ | static int PrintPacError(const char *fmt, va_list argp) { | |
30 | ✗ | char *msg = NULL; | |
31 | |||
32 | ✗ | const int retval = vasprintf(&msg, fmt, argp); | |
33 | ✗ | assert(retval != -1); // else: out of memory | |
34 | |||
35 | ✗ | LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr, "(pacparser) %s", msg); | |
36 | ✗ | free(msg); | |
37 | ✗ | return retval; | |
38 | } | ||
39 | |||
40 | |||
41 | ✗ | static string PacProxy2Cvmfs(const string &pac_proxy, | |
42 | const bool report_errors) { | ||
43 | ✗ | const int log_flags = report_errors ? kLogDebug | kLogSyslogWarn : kLogDebug; | |
44 | ✗ | if (pac_proxy == "") | |
45 | ✗ | return "DIRECT"; | |
46 | |||
47 | ✗ | string cvmfs_proxy; | |
48 | ✗ | vector<string> components = SplitString(pac_proxy, ';'); | |
49 | ✗ | for (unsigned i = 0; i < components.size(); ++i) { | |
50 | // Remove white spaces | ||
51 | ✗ | string next_proxy; | |
52 | ✗ | for (unsigned j = 0; j < components[i].length(); ++j) { | |
53 | ✗ | if ((components[i][j] != ' ') && (components[i][j] != '\t')) | |
54 | ✗ | next_proxy.push_back(components[i][j]); | |
55 | } | ||
56 | |||
57 | // No SOCKS support | ||
58 | ✗ | if (HasPrefix(next_proxy, "SOCKS", false)) { | |
59 | ✗ | LogCvmfs(kLogDownload, log_flags, | |
60 | "no support for SOCKS proxy, skipping %s", | ||
61 | ✗ | next_proxy.substr(5).c_str()); | |
62 | ✗ | continue; | |
63 | } | ||
64 | |||
65 | ✗ | if ((next_proxy != "DIRECT") && !HasPrefix(next_proxy, "PROXY", false)) { | |
66 | ✗ | LogCvmfs(kLogDownload, log_flags, "invalid proxy definition: %s", | |
67 | next_proxy.c_str()); | ||
68 | ✗ | continue; | |
69 | } | ||
70 | |||
71 | ✗ | if (HasPrefix(next_proxy, "PROXY", false)) | |
72 | ✗ | next_proxy = next_proxy.substr(5); | |
73 | |||
74 | ✗ | if (cvmfs_proxy == "") | |
75 | ✗ | cvmfs_proxy = next_proxy; | |
76 | else | ||
77 | ✗ | cvmfs_proxy += ";" + next_proxy; | |
78 | } | ||
79 | |||
80 | ✗ | return cvmfs_proxy; | |
81 | } | ||
82 | |||
83 | |||
84 | ✗ | static bool ParsePac(const char *pac_data, const size_t size, | |
85 | DownloadManager *download_manager, string *proxies) { | ||
86 | ✗ | *proxies = ""; | |
87 | |||
88 | ✗ | pacparser_set_error_printer(PrintPacError); | |
89 | ✗ | bool retval = pacparser_init(); | |
90 | ✗ | if (!retval) | |
91 | ✗ | return false; | |
92 | |||
93 | ✗ | const string pac_string(pac_data, size); | |
94 | ✗ | LogCvmfs(kLogDownload, kLogDebug, "PAC script is:\n%s", pac_string.c_str()); | |
95 | ✗ | retval = pacparser_parse_pac_string(pac_string.c_str()); | |
96 | ✗ | if (!retval) { | |
97 | ✗ | pacparser_cleanup(); | |
98 | ✗ | return false; | |
99 | } | ||
100 | |||
101 | // For every stratum 1: get proxy | ||
102 | ✗ | vector<string> host_list; | |
103 | ✗ | vector<int> rtt; | |
104 | unsigned current_host; | ||
105 | ✗ | download_manager->GetHostInfo(&host_list, &rtt, ¤t_host); | |
106 | ✗ | for (unsigned i = 0; i < host_list.size(); ++i) { | |
107 | ✗ | const size_t hostname_begin = 7; // Strip http:// or file:// | |
108 | ✗ | const size_t hostname_end = host_list[i].find_first_of(":/", | |
109 | hostname_begin); | ||
110 | ✗ | const size_t hostname_len = (hostname_end == string::npos) | |
111 | ✗ | ? string::npos | |
112 | : hostname_end - hostname_begin; | ||
113 | ✗ | const string hostname = (hostname_begin > host_list[i].length()) | |
114 | ? "localhost" | ||
115 | ✗ | : host_list[i].substr(hostname_begin, | |
116 | ✗ | hostname_len); | |
117 | ✗ | const string url = host_list[i] + "/.cvmfspublished"; | |
118 | |||
119 | // pac_proxy is freed by JavaScript GC | ||
120 | ✗ | char *pac_proxy = pacparser_find_proxy(url.c_str(), hostname.c_str()); | |
121 | ✗ | if (pac_proxy == NULL) { | |
122 | ✗ | pacparser_cleanup(); | |
123 | ✗ | return false; | |
124 | } | ||
125 | ✗ | if (*proxies == "") { | |
126 | ✗ | *proxies = PacProxy2Cvmfs(pac_proxy, true); | |
127 | ✗ | if (*proxies == "") { | |
128 | ✗ | LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn, | |
129 | "no valid proxy found (%s returned from pac file)", pac_proxy); | ||
130 | ✗ | pacparser_cleanup(); | |
131 | ✗ | return false; | |
132 | } | ||
133 | } else { | ||
134 | ✗ | const string alt_proxies = PacProxy2Cvmfs(pac_proxy, false); | |
135 | ✗ | if (*proxies != alt_proxies) { | |
136 | ✗ | LogCvmfs(kLogDownload, kLogDebug, | |
137 | "proxy settings for host %s differ from proxy settings for " | ||
138 | "other hosts (%s / %s). Not using proxy setting %s.", | ||
139 | ✗ | host_list[i].c_str(), proxies->c_str(), alt_proxies.c_str(), | |
140 | alt_proxies.c_str()); | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
145 | ✗ | pacparser_cleanup(); | |
146 | ✗ | return true; | |
147 | } | ||
148 | |||
149 | |||
150 | ✗ | string AutoProxy(DownloadManager *download_manager) { | |
151 | ✗ | char *http_env = getenv("http_proxy"); | |
152 | ✗ | if (http_env) { | |
153 | ✗ | LogCvmfs(kLogDownload, kLogDebug | kLogSyslog, | |
154 | "CernVM-FS: " | ||
155 | "using HTTP proxy server(s) %s from http_proxy environment", | ||
156 | http_env); | ||
157 | ✗ | return string(http_env); | |
158 | } | ||
159 | |||
160 | ✗ | vector<string> pac_paths; | |
161 | ✗ | char *pac_env = getenv("CVMFS_PAC_URLS"); | |
162 | ✗ | if (pac_env != NULL) | |
163 | ✗ | pac_paths = SplitString(pac_env, ';'); | |
164 | |||
165 | // Try downloading from each of the PAC URLs | ||
166 | ✗ | for (unsigned i = 0; i < pac_paths.size(); ++i) { | |
167 | ✗ | if (pac_paths[i] == "auto") { | |
168 | ✗ | LogCvmfs(kLogDownload, kLogDebug, "resolving auto proxy config to %s", | |
169 | kAutoPacLocation); | ||
170 | ✗ | pac_paths[i] = string(kAutoPacLocation); | |
171 | } | ||
172 | ✗ | LogCvmfs(kLogDownload, kLogDebug, "looking for proxy config at %s", | |
173 | ✗ | pac_paths[i].c_str()); | |
174 | ✗ | cvmfs::MemSink pac_memsink; | |
175 | ✗ | download::JobInfo download_pac(&pac_paths[i], false, false, NULL, | |
176 | ✗ | &pac_memsink); | |
177 | ✗ | int retval = download_manager->Fetch(&download_pac); | |
178 | ✗ | if (retval == download::kFailOk) { | |
179 | ✗ | string proxies; | |
180 | ✗ | retval = ParsePac(reinterpret_cast<char *>(pac_memsink.data()), | |
181 | pac_memsink.pos(), | ||
182 | download_manager, | ||
183 | &proxies); | ||
184 | ✗ | if (!retval) { | |
185 | ✗ | LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn, | |
186 | ✗ | "failed to parse pac file %s", pac_paths[i].c_str()); | |
187 | } else { | ||
188 | ✗ | if (proxies != "") { | |
189 | ✗ | LogCvmfs(kLogDownload, kLogDebug | kLogSyslog, | |
190 | "CernVM-FS: " | ||
191 | "using HTTP proxy server(s) %s from pac file %s", | ||
192 | ✗ | proxies.c_str(), pac_paths[i].c_str()); | |
193 | ✗ | return proxies; | |
194 | } | ||
195 | } | ||
196 | |||
197 | ✗ | LogCvmfs(kLogDownload, kLogDebug, "no proxy settings found in %s", | |
198 | ✗ | pac_paths[i].c_str()); | |
199 | } | ||
200 | } | ||
201 | |||
202 | ✗ | return ""; | |
203 | } | ||
204 | |||
205 | |||
206 | 686 | string ResolveProxyDescription(const string &cvmfs_proxies, | |
207 | const std::string &path_fallback_cache, | ||
208 | DownloadManager *download_manager) { | ||
209 |
4/6✓ Branch 1 taken 394 times.
✓ Branch 2 taken 292 times.
✓ Branch 4 taken 394 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 686 times.
✗ Branch 7 not taken.
|
686 | if ((cvmfs_proxies == "") || (cvmfs_proxies.find("auto") == string::npos)) |
210 |
1/2✓ Branch 1 taken 686 times.
✗ Branch 2 not taken.
|
686 | return cvmfs_proxies; |
211 | |||
212 | ✗ | int empty_auto = -1; | |
213 | ✗ | vector<string> lb_groups = SplitString(cvmfs_proxies, ';'); | |
214 | ✗ | for (unsigned i = 0; i < lb_groups.size(); ++i) { | |
215 | ✗ | if (lb_groups[i] != "auto") | |
216 | ✗ | continue; | |
217 | |||
218 | ✗ | lb_groups[i] = AutoProxy(download_manager); | |
219 | ✗ | if (lb_groups[i].empty()) | |
220 | ✗ | empty_auto = static_cast<int>(i); | |
221 | } | ||
222 | |||
223 | ✗ | if (empty_auto != -1) | |
224 | ✗ | lb_groups.erase(lb_groups.begin() + static_cast<unsigned>(empty_auto)); | |
225 | ✗ | string discovered_proxies = JoinStrings(lb_groups, ";"); | |
226 | |||
227 | ✗ | if (!path_fallback_cache.empty()) { | |
228 | ✗ | if (empty_auto != -1) { | |
229 | ✗ | string cached_proxies; | |
230 | ✗ | const int fd = open(path_fallback_cache.c_str(), O_RDONLY); | |
231 | ✗ | if (fd >= 0) { | |
232 | ✗ | const bool retval = SafeReadToString(fd, &cached_proxies); | |
233 | ✗ | close(fd); | |
234 | ✗ | if (retval) { | |
235 | ✗ | LogCvmfs(kLogDownload, kLogSyslog | kLogDebug, | |
236 | "using cached proxy settings from %s", | ||
237 | path_fallback_cache.c_str()); | ||
238 | ✗ | return cached_proxies; | |
239 | } | ||
240 | } | ||
241 | ✗ | } else { | |
242 | ✗ | const bool retval = SafeWriteToFile(discovered_proxies, | |
243 | path_fallback_cache, 0660); | ||
244 | ✗ | if (!retval) { | |
245 | ✗ | LogCvmfs(kLogDownload, kLogSyslogWarn | kLogDebug, | |
246 | "failed to write proxy settings into %s", | ||
247 | path_fallback_cache.c_str()); | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | |||
252 | ✗ | return discovered_proxies; | |
253 | } | ||
254 | |||
255 | |||
256 | ✗ | static void AltCvmfsLogger(const LogSource source, const int mask, | |
257 | const char *msg) { | ||
258 | ✗ | FILE *log_output = NULL; | |
259 | ✗ | if (mask & kLogStdout) | |
260 | ✗ | log_output = stdout; | |
261 | ✗ | else if (mask & kLogStderr || mask & kLogSyslogWarn || mask & kLogSyslogErr) | |
262 | ✗ | log_output = stderr; | |
263 | ✗ | if (log_output) | |
264 | ✗ | fprintf(log_output, "%s\n", msg); | |
265 | } | ||
266 | |||
267 | |||
268 | ✗ | int MainResolveProxyDescription(int argc, char **argv) { | |
269 | ✗ | SetAltLogFunc(AltCvmfsLogger); | |
270 | ✗ | if (argc < 4) { | |
271 | ✗ | LogCvmfs(kLogDownload, kLogStderr, "arguments missing"); | |
272 | ✗ | return 1; | |
273 | } | ||
274 | ✗ | perf::Statistics statistics; | |
275 | ✗ | const string proxy_configuration = argv[2]; | |
276 | ✗ | const string host_list = argv[3]; | |
277 | |||
278 | DownloadManager download_manager( | ||
279 | ✗ | 1, perf::StatisticsTemplate("pac", &statistics)); | |
280 | ✗ | download_manager.SetHostChain(host_list); | |
281 | const string resolved_proxies = ResolveProxyDescription( | ||
282 | ✗ | proxy_configuration, "", &download_manager); | |
283 | |||
284 | ✗ | LogCvmfs(kLogDownload, kLogStdout, "%s", resolved_proxies.c_str()); | |
285 | ✗ | return resolved_proxies == ""; | |
286 | } | ||
287 | |||
288 | } // namespace download | ||
289 |