/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 93 Adaptation */ #include "squid.h" #include "adaptation/ServiceConfig.h" #include "cache_cf.h" #include "ConfigParser.h" #include "debug/Stream.h" #include "globals.h" #include "ip/tools.h" #include Adaptation::ServiceConfig::ServiceConfig(): port(-1), method(methodNone), point(pointNone), bypass(false), maxConn(-1), onOverload(srvWait), routing(false), ipv6(false) {} const char * Adaptation::ServiceConfig::methodStr() const { return Adaptation::methodStr(method); } const char * Adaptation::ServiceConfig::vectPointStr() const { return Adaptation::vectPointStr(point); } Adaptation::Method Adaptation::ServiceConfig::parseMethod(const char *str) const { if (!strncasecmp(str, "REQMOD", 6)) return Adaptation::methodReqmod; if (!strncasecmp(str, "RESPMOD", 7)) return Adaptation::methodRespmod; return Adaptation::methodNone; } Adaptation::VectPoint Adaptation::ServiceConfig::parseVectPoint(const char *service_configConfig) const { const char *t = service_configConfig; const char *q = strchr(t, '_'); if (q) t = q + 1; if (!strcmp(t, "precache")) return Adaptation::pointPreCache; if (!strcmp(t, "postcache")) return Adaptation::pointPostCache; return Adaptation::pointNone; } bool Adaptation::ServiceConfig::parse() { key = ConfigParser::NextToken(); String method_point = ConfigParser::NextToken(); if (!method_point.size()) { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "Missing vectoring point in adaptation service definition"); return false; } method = parseMethod(method_point.termedBuf()); point = parseVectPoint(method_point.termedBuf()); if (method == Adaptation::methodNone && point == Adaptation::pointNone) { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "Unknown vectoring point '" << method_point << "' in adaptation service definition"); return false; } // reset optional parameters in case we are reconfiguring bypass = routing = false; // handle optional service name=value parameters bool grokkedUri = false; bool onOverloadSet = false; std::set options; while (char *option = ConfigParser::NextToken()) { const char *name = option; const char *value = ""; if (strcmp(option, "0") == 0) { // backward compatibility name = "bypass"; value = "off"; debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: UPGRADE: Please use 'bypass=off' option to disable service bypass"); } else if (strcmp(option, "1") == 0) { // backward compatibility name = "bypass"; value = "on"; debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: UPGRADE: Please use 'bypass=on' option to enable service bypass"); } else { char *eq = strstr(option, "="); const char *sffx = strstr(option, "://"); if (!eq || (sffx && sffx < eq)) { //no "=" or has the form "icap://host?arg=val" name = "uri"; value = option; } else { // a normal name=value option *eq = '\0'; // terminate option name value = eq + 1; // skip '=' } } // Check if option is set twice if (options.find(name) != options.end()) { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "Duplicate option \"" << name << "\" in adaptation service definition"); return false; } options.insert(name); bool grokked = false; if (strcmp(name, "bypass") == 0) { grokked = grokBool(bypass, name, value); } else if (strcmp(name, "routing") == 0) grokked = grokBool(routing, name, value); else if (strcmp(name, "uri") == 0) grokked = grokkedUri = grokUri(value); else if (strcmp(name, "ipv6") == 0) { grokked = grokBool(ipv6, name, value); if (grokked && ipv6 && !Ip::EnableIpv6) debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: IPv6 is disabled. ICAP service option ignored."); } else if (strcmp(name, "max-conn") == 0) grokked = grokLong(maxConn, name, value); else if (strcmp(name, "on-overload") == 0) { grokked = grokOnOverload(onOverload, value); onOverloadSet = true; } else if (strcmp(name, "connection-encryption") == 0) { bool encrypt = false; grokked = grokBool(encrypt, name, value); connectionEncryption.configure(encrypt); } else if (strncmp(name, "ssl", 3) == 0 || strncmp(name, "tls-", 4) == 0) { #if !USE_OPENSSL debugs(3, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: adaptation option '" << name << "' requires --with-openssl. ICAP service option ignored."); #else // name prefix is "ssl" or "tls-" std::string tmp = name + (name[0] == 's' ? 3 : 4); tmp += "="; tmp += value; secure.parse(tmp.c_str()); grokked = true; #endif } else grokked = grokExtension(name, value); if (!grokked) return false; } // set default on-overload value if needed if (!onOverloadSet) onOverload = bypass ? srvBypass : srvWait; // disable the TLS NPN extension if encrypted. // Squid advertises "http/1.1", which is wrong for ICAPS. if (secure.encryptTransport) secure.parse("no-npn"); // is the service URI set? if (!grokkedUri) { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "No \"uri\" option in adaptation service definition"); return false; } debugs(3,5, cfg_filename << ':' << config_lineno << ": " << "adaptation_service " << key << ' ' << methodStr() << "_" << vectPointStr() << ' ' << bypass << routing << ' ' << uri); return true; } bool Adaptation::ServiceConfig::grokUri(const char *value) { // TODO: find core code that parses URLs and extracts various parts // AYJ: most of this is duplicate of AnyP::Uri::parse() if (!value || !*value) { debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << "empty adaptation service URI"); return false; } uri = value; // extract scheme and use it as the service_configConfig protocol const char *schemeSuffix = "://"; const String::size_type schemeEnd = uri.find(schemeSuffix); if (schemeEnd != String::npos) protocol=uri.substr(0,schemeEnd); debugs(3, 5, cfg_filename << ':' << config_lineno << ": " << "service protocol is " << protocol); if (protocol.size() == 0) return false; // skip scheme const char *s = uri.termedBuf() + protocol.size() + strlen(schemeSuffix); const char *e; bool have_port = false; int len = 0; if (*s == '[') { const char *t; if ((t = strchr(s, ']')) == nullptr) return false; ++s; len = t - s; if ((e = strchr(t, ':')) != nullptr) { have_port = true; } else if ((e = strchr(t, '/')) != nullptr) { have_port = false; } else { return false; } } else { if ((e = strchr(s, ':')) != nullptr) { have_port = true; } else if ((e = strchr(s, '/')) != nullptr) { have_port = false; } else { return false; } len = e - s; } host.assign(s, len); #if USE_OPENSSL if (secure.sslDomain.isEmpty()) secure.sslDomain.assign(host.rawBuf(), host.size()); #endif s = e; port = -1; if (have_port) { ++s; if ((e = strchr(s, '/')) != nullptr) { char *t; const unsigned long p = strtoul(s, &t, 0); if (p > 65535) // port value is too high return false; port = static_cast(p); if (t != e) // extras after the port return false; s = e; if (s[0] != '/') return false; } } // if no port, the caller may use service_configConfigs or supply the default if needed ++s; e = strchr(s, '\0'); len = e - s; if (len > 1024) { debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << "long resource name (>1024), probably wrong"); } resource.assign(s, len + 1); return true; } bool Adaptation::ServiceConfig::grokBool(bool &var, const char *name, const char *value) { if (!strcmp(value, "0") || !strcmp(value, "off")) var = false; else if (!strcmp(value, "1") || !strcmp(value, "on")) var = true; else { debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << "wrong value for boolean " << name << "; " << "'0', '1', 'on', or 'off' expected but got: " << value); return false; } return true; } bool Adaptation::ServiceConfig::grokLong(long &var, const char *name, const char *value) { char *bad = nullptr; const long p = strtol(value, &bad, 0); if (p < 0 || bad == value) { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "wrong value for " << name << "; " << "a non-negative integer expected but got: " << value); return false; } var = p; return true; } bool Adaptation::ServiceConfig::grokOnOverload(SrvBehaviour &var, const char *value) { if (strcmp(value, "block") == 0) var = srvBlock; else if (strcmp(value, "bypass") == 0) var = srvBypass; else if (strcmp(value, "wait") == 0) var = srvWait; else if (strcmp(value, "force") == 0) var = srvForce; else { debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "wrong value for on-overload; " << "'block', 'bypass', 'wait' or 'force' expected but got: " << value); return false; } return true; } bool Adaptation::ServiceConfig::grokExtension(const char *name, const char *value) { // we do not accept extensions by default debugs(3, DBG_CRITICAL, "ERROR: " << cfg_filename << ':' << config_lineno << ": " << "ERROR: unknown adaptation service option: " << name << '=' << value); return false; }