| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/wpad.cc |
| Date: | 2025-11-09 02:35:23 |
| 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 | 761 | string ResolveProxyDescription(const string &cvmfs_proxies, | |
| 207 | const std::string &path_fallback_cache, | ||
| 208 | DownloadManager *download_manager) { | ||
| 209 |
4/6✓ Branch 1 taken 570 times.
✓ Branch 2 taken 191 times.
✓ Branch 4 taken 570 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 761 times.
✗ Branch 7 not taken.
|
761 | if ((cvmfs_proxies == "") || (cvmfs_proxies.find("auto") == string::npos)) |
| 210 |
1/2✓ Branch 1 taken 761 times.
✗ Branch 2 not taken.
|
761 | 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 |