diff --git a/configure.ac b/configure.ac index f429725822a..90e4213cfe2 100644 --- a/configure.ac +++ b/configure.ac @@ -2585,6 +2585,7 @@ AC_CONFIG_FILES([ src/base/Makefile src/clients/Makefile src/comm/Makefile + src/configuration/Makefile src/debug/Makefile src/dns/Makefile src/DiskIO/Makefile diff --git a/src/ConfigParser.cc b/src/ConfigParser.cc index 0e405eb85ae..ec537e58457 100644 --- a/src/ConfigParser.cc +++ b/src/ConfigParser.cc @@ -16,8 +16,11 @@ #include "fatal.h" #include "globals.h" #include "neighbors.h" +#include "parser/Tokenizer.h" #include "sbuf/Stream.h" +#include + bool ConfigParser::RecognizeQuotedValues = true; bool ConfigParser::StrictMode = true; std::stack ConfigParser::CfgFiles; @@ -41,14 +44,12 @@ ConfigParser::destruct() if (!CfgFiles.empty()) { std::ostringstream message; CfgFile *f = CfgFiles.top(); - message << "Bungled " << f->filePath << " line " << f->lineNo << - ": " << f->currentLine << std::endl; + message << "Bungled " << f->lineInfo() << std::endl; CfgFiles.pop(); delete f; while (!CfgFiles.empty()) { f = CfgFiles.top(); - message << " included from " << f->filePath << " line " << - f->lineNo << ": " << f->currentLine << std::endl; + message << " included from " << f->lineInfo() << std::endl; CfgFiles.pop(); delete f; } @@ -68,7 +69,7 @@ ConfigParser::strtokFile() return ConfigParser::NextToken(); static int fromFile = 0; - static FILE *wordFile = nullptr; + static std::unique_ptr wordFile; char *t; static char buf[CONFIG_LINE_LIMIT]; @@ -90,15 +91,8 @@ ConfigParser::strtokFile() *t = '\0'; - if ((wordFile = fopen(fn, "r")) == nullptr) { - debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading"); - return nullptr; - } - -#if _SQUID_WINDOWS_ - setmode(fileno(wordFile), O_TEXT); -#endif - + wordFile.reset(new Configuration::File(fn)); + wordFile->load(); fromFile = 1; } else { return t; @@ -106,26 +100,16 @@ ConfigParser::strtokFile() } /* fromFile */ - if (fgets(buf, sizeof(buf), wordFile) == nullptr) { + static SBuf line; + line = wordFile->nextLine(); + if (line.isEmpty()) { /* stop reading from file */ - fclose(wordFile); wordFile = nullptr; fromFile = 0; return nullptr; } else { - char *t2, *t3; - t = buf; - /* skip leading and trailing white space */ - t += strspn(buf, w_space); - t2 = t + strcspn(t, w_space); - t3 = t2 + strspn(t2, w_space); - - while (*t3 && *t3 != '#') { - t2 = t3 + strcspn(t3, w_space); - t3 = t2 + strspn(t2, w_space); - } - - *t2 = '\0'; + assert(line.length() < CONFIG_LINE_LIMIT); + t = const_cast(line.c_str()); } /* skip comments */ @@ -341,7 +325,7 @@ ConfigParser::NextToken() if (!token) { assert(!wordfile->isOpen()); CfgFiles.pop(); - debugs(3, 4, "CfgFiles.pop " << wordfile->filePath); + debugs(3, 4, "CfgFiles.pop " << wordfile->lineInfo()); delete wordfile; } } @@ -361,6 +345,8 @@ ConfigParser::NextToken() return nullptr; } + assert(path); // a QuotedToken cannot be nil + // The next token in current cfg file line must be a ")" char *end = NextToken(); ConfigParser::PreviewMode_ = savePreview; @@ -376,14 +362,9 @@ ConfigParser::NextToken() return nullptr; } - ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile(); - if (!path || !wordfile->startParse(path)) { - debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token); - delete wordfile; - self_destruct(); - return nullptr; - } - CfgFiles.push(wordfile); + std::unique_ptr wordfile(new ConfigParser::CfgFile(path)); + wordfile->startParse(); // throws on error + CfgFiles.push(wordfile.release()); token = nullptr; } } while (token == nullptr && !CfgFiles.empty()); @@ -608,54 +589,32 @@ ConfigParser::optionalAclList() return acls; } -bool -ConfigParser::CfgFile::startParse(char *path) -{ - assert(wordFile == nullptr); - debugs(3, 3, "Parsing from " << path); - if ((wordFile = fopen(path, "r")) == nullptr) { - debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found"); - return false; - } - -#if _SQUID_WINDOWS_ - setmode(fileno(wordFile), O_TEXT); -#endif - - filePath = path; - return getFileLine(); -} - -bool -ConfigParser::CfgFile::getFileLine() +void +ConfigParser::CfgFile::startParse() { - // Else get the next line - if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == nullptr) { - /* stop reading from file */ - fclose(wordFile); - wordFile = nullptr; - parseBuffer[0] = '\0'; - return false; - } - parsePos = parseBuffer; - currentLine = parseBuffer; - lineNo++; - return true; + assert(isOpen()); + confFileData.load(); + debugs(3, 3, "Parsing from " << confFileData.lineInfo()); } char * ConfigParser::CfgFile::parse(ConfigParser::TokenType &type) { - if (!wordFile) - return nullptr; - if (!*parseBuffer) return nullptr; char *token; while (!(token = nextElement(type))) { - if (!getFileLine()) + auto line = confFileData.nextLine(); + if (line.isEmpty()) { + *parseBuffer = 0; return nullptr; + } + + assert(line.length() < CONFIG_LINE_LIMIT); + + SBufToCstring(parseBuffer, line); + parsePos = parseBuffer; } return token; } @@ -671,9 +630,3 @@ ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type) return token; } -ConfigParser::CfgFile::~CfgFile() -{ - if (wordFile) - fclose(wordFile); -} - diff --git a/src/ConfigParser.h b/src/ConfigParser.h index f85c20ea3ff..ea40d9e0212 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -11,6 +11,7 @@ #include "acl/forward.h" #include "base/forward.h" +#include "configuration/File.h" #include "sbuf/forward.h" #include "SquidString.h" @@ -22,16 +23,6 @@ class CachePeer; class wordlist; -/** - * Limit to how long any given config line may be. - * This affects squid.conf and all included files. - * - * Behaviour when setting larger than 2KB is unknown. - * The config parser read mechanism can cope, but the other systems - * receiving the data from its buffers on such lines may not. - */ -#define CONFIG_LINE_LIMIT 2048 - /** * A configuration file Parser. Instances of this class track * parsing state and perform tokenisation. Syntax is currently @@ -161,22 +152,21 @@ class ConfigParser protected: /** - * Class used to store required information for the current - * configuration file. + * Interprets file content as a series of configuration tokens/elements instead of lines. */ class CfgFile { public: - CfgFile(): wordFile(nullptr), parsePos(nullptr), lineNo(0) { parseBuffer[0] = '\0';} - ~CfgFile(); + CfgFile(const char *path) : confFileData(path) { *parseBuffer = 0; } + /// True if the configuration file is open - bool isOpen() {return wordFile != nullptr;} + bool isOpen() const { return confFileData.isOpen(); } - /** - * Open the file given by 'path' and initializes the CfgFile object - * to start parsing - */ - bool startParse(char *path); + // filename and line number for debug display + SourceLocation lineInfo() const { return confFileData.lineInfo(); } + + /// Initializes the CfgFile object to start parsing + void startParse(); /** * Do the next parsing step: @@ -187,19 +177,17 @@ class ConfigParser char *parse(TokenType &type); private: - bool getFileLine(); ///< Read the next line from the file /** * Return the body of the next element. If the wasQuoted is given * set to true if the element was quoted. */ char *nextElement(TokenType &type); - FILE *wordFile; ///< Pointer to the file. + + private: + Configuration::File confFileData; ///< actual file manager holding unparsed file content + char parseBuffer[CONFIG_LINE_LIMIT]; ///< Temporary buffer to store data to parse - const char *parsePos; ///< The next element position in parseBuffer string - public: - std::string filePath; ///< The file path - std::string currentLine; ///< The current line to parse - int lineNo; ///< Current line number + const char *parsePos = nullptr; ///< The next element position in parseBuffer string }; /** diff --git a/src/Makefile.am b/src/Makefile.am index 2db91a28d70..67159f155e2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,7 +16,7 @@ LOADABLE_MODULES_SOURCES = \ LoadableModules.cc \ LoadableModules.h -SUBDIRS = mem time debug base anyp helper dns html ftp parser comm error eui acl format clients sbuf servers fs repl store DiskIO proxyp +SUBDIRS = mem time debug base configuration anyp helper dns html ftp parser comm error eui acl format clients sbuf servers fs repl store DiskIO proxyp if ENABLE_AUTH SUBDIRS += auth @@ -521,6 +521,7 @@ squid_LDADD = \ mem/libmem.la \ store/libstore.la \ time/libtime.la \ + configuration/libconfiguration.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1150,6 +1151,7 @@ tests_testRock_LDADD = \ mem/libmem.la \ store/libstore.la \ $(ADAPTATION_LIBS) \ + configuration/libconfiguration.la \ sbuf/libsbuf.la \ time/libtime.la \ $(top_builddir)/lib/libmisccontainers.la \ @@ -1327,6 +1329,7 @@ tests_testUfs_LDADD = \ mem/libmem.la \ store/libstore.la \ $(ADAPTATION_LIBS) \ + configuration/libconfiguration.la \ sbuf/libsbuf.la \ time/libtime.la \ $(top_builddir)/lib/libmisccontainers.la \ @@ -1492,6 +1495,7 @@ tests_testStore_LDADD= \ sbuf/libsbuf.la \ DiskIO/libdiskio.la \ ipc/libipc.la \ + configuration/libconfiguration.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -1668,6 +1672,7 @@ tests_testDiskIO_LDADD = \ dns/libdns.la \ base/libbase.la \ mem/libmem.la \ + configuration/libconfiguration.la \ sbuf/libsbuf.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ @@ -1720,6 +1725,7 @@ tests_testACLMaxUserIP_LDADD = \ acl/libacls.la \ SquidConfig.o \ ip/libip.la \ + configuration/libconfiguration.la \ parser/libparser.la \ sbuf/libsbuf.la \ base/libbase.la \ @@ -1973,6 +1979,7 @@ tests_test_http_range_LDADD = \ sbuf/libsbuf.la \ debug/libdebug.la \ store/libstore.la \ + configuration/libconfiguration.la \ $(SNMP_LIBS) \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ @@ -2131,6 +2138,7 @@ tests_testHttpReply_LDADD=\ ip/libip.la \ base/libbase.la \ ipc/libipc.la \ + configuration/libconfiguration.la \ sbuf/libsbuf.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ @@ -2363,6 +2371,7 @@ tests_testHttpRequest_LDADD = \ $(REPL_OBJS) \ $(ADAPTATION_LIBS) \ $(ESI_LIBS) \ + configuration/libconfiguration.la \ $(top_builddir)/lib/libmisccontainers.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ @@ -2635,7 +2644,6 @@ tests_testCacheManager_LDADD = \ helper/libhelper.la \ http/libhttp.la \ proxyp/libproxyp.la \ - parser/libparser.la \ ident/libident.la \ acl/libacls.la \ acl/libstate.la \ @@ -2660,6 +2668,8 @@ tests_testCacheManager_LDADD = \ $(SNMP_LIBS) \ mem/libmem.la \ store/libstore.la \ + configuration/libconfiguration.la \ + parser/libparser.la \ sbuf/libsbuf.la \ time/libtime.la \ debug/libdebug.la \ @@ -2736,6 +2746,8 @@ nodist_tests_testConfigParser_SOURCES = \ tests/stub_libmem.cc \ tests/stub_neighbors.cc tests_testConfigParser_LDADD = \ + configuration/libconfiguration.la \ + parser/libparser.la \ base/libbase.la \ $(LIBCPPUNIT_LIBS) \ $(COMPAT_LIB) \ diff --git a/src/SquidConfig.h b/src/SquidConfig.h index 8ac9a47b941..c187eaf32a2 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -224,7 +224,6 @@ class SquidConfig char *pidFilename; char *netdbFilename; char *mimeTablePathname; - char *etcHostsPath; char *visibleHostname; char *uniqueHostname; SBufList hostnameAliases; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index fafb9867b0f..c45dcd285ef 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -32,6 +32,7 @@ #include "CpuAffinityMap.h" #include "debug/Messages.h" #include "DiskIO/DiskIOModule.h" +#include "dns/EtcHosts.h" #include "eui/Config.h" #include "ExternalACL.h" #include "format/Format.h" @@ -281,15 +282,6 @@ self_destruct(void) LegacyParser.destruct(); } -static void -SetConfigFilename(char const *file_name, bool is_pipe) -{ - if (is_pipe) - cfg_filename = file_name + 1; - else - cfg_filename = file_name; -} - static const char* skip_ws(const char* s) { @@ -330,7 +322,7 @@ parseManyConfigFiles(char* files, int depth) } static void -ReplaceSubstr(char*& str, int& len, unsigned substrIdx, unsigned substrLen, const char* newSubstr) +ReplaceSubstr(char* str, int& len, unsigned substrIdx, unsigned substrLen, const char* newSubstr) { assert(str != nullptr); assert(newSubstr != nullptr); @@ -348,7 +340,7 @@ ReplaceSubstr(char*& str, int& len, unsigned substrIdx, unsigned substrLen, cons } static void -SubstituteMacro(char*& line, int& len, const char* macroName, const char* substStr) +SubstituteMacro(char* line, int& len, const char* macroName, const char* substStr) { assert(line != nullptr); assert(macroName != nullptr); @@ -359,7 +351,7 @@ SubstituteMacro(char*& line, int& len, const char* macroName, const char* substS } static void -ProcessMacros(char*& line, int& len) +ProcessMacros(char* line, int& len) { SubstituteMacro(line, len, "${service_name}", service_name.c_str()); SubstituteMacro(line, len, "${process_name}", TheKidName.c_str()); @@ -437,14 +429,9 @@ EvalBoolExpr(const char* expr) static int parseOneConfigFile(const char *file_name, unsigned int depth) { - FILE *fp = nullptr; const char *orig_cfg_filename = cfg_filename; const int orig_config_lineno = config_lineno; - char *token = nullptr; - char *tmp_line = nullptr; - int tmp_line_len = 0; int err_count = 0; - int is_pipe = 0; debugs(3, Important(68), "Processing Configuration File: " << file_name << " (depth " << depth << ")"); if (depth > 16) { @@ -452,117 +439,48 @@ parseOneConfigFile(const char *file_name, unsigned int depth) return 1; } - if (file_name[0] == '!' || file_name[0] == '|') { - fp = popen(file_name + 1, "r"); - is_pipe = 1; - } else { - fp = fopen(file_name, "r"); - } - - if (!fp) { - int xerrno = errno; - fatalf("Unable to open configuration file: %s: %s", file_name, xstrerr(xerrno)); - } - -#if _SQUID_WINDOWS_ - setmode(fileno(fp), O_TEXT); -#endif - - SetConfigFilename(file_name, bool(is_pipe)); + Configuration::File cfg(file_name); + cfg.load(); // throws on error memset(config_input_line, '\0', BUFSIZ); - config_lineno = 0; - std::vector if_states; - while (fgets(config_input_line, BUFSIZ, fp)) { - ++config_lineno; - - if ((token = strchr(config_input_line, '\n'))) - *token = '\0'; - - if ((token = strchr(config_input_line, '\r'))) - *token = '\0'; - - // strip any prefix whitespace off the line. - const char *p = skip_ws(config_input_line); - if (config_input_line != p) - memmove(config_input_line, p, strlen(p)+1); - - if (strncmp(config_input_line, "#line ", 6) == 0) { - static char new_file_name[1024]; - static char *file; - static char new_lineno; - token = config_input_line + 6; - new_lineno = strtol(token, &file, 0) - 1; - - if (file == token) - continue; /* Not a valid #line directive, may be a comment */ - - while (*file && xisspace((unsigned char) *file)) - ++file; - - if (*file) { - if (*file != '"') - continue; /* Not a valid #line directive, may be a comment */ - - xstrncpy(new_file_name, file + 1, sizeof(new_file_name)); - - if ((token = strchr(new_file_name, '"'))) - *token = '\0'; - - SetConfigFilename(new_file_name, false); - } - - config_lineno = new_lineno; - } - - if (config_input_line[0] == '#') - continue; + while (true) { + auto line = cfg.nextLine(); + cfg_filename = cfg.lineInfo().fileName; + config_lineno = cfg.lineInfo().lineNo; - if (config_input_line[0] == '\0') - continue; - - const char* append = tmp_line_len ? skip_ws(config_input_line) : config_input_line; - - size_t append_len = strlen(append); - - tmp_line = (char*)xrealloc(tmp_line, tmp_line_len + append_len + 1); - - strcpy(tmp_line + tmp_line_len, append); + if (line.isEmpty()) + break; - tmp_line_len += append_len; + assert(line.length() < BUFSIZ); + SBufToCstring(config_input_line, line); + int tmp_line_len = line.length(); - if (tmp_line[tmp_line_len-1] == '\\') { - debugs(3, 5, "parseConfigFile: tmp_line='" << tmp_line << "'"); - tmp_line[--tmp_line_len] = '\0'; - continue; - } + ProcessMacros(config_input_line, tmp_line_len); - trim_trailing_ws(tmp_line); - ProcessMacros(tmp_line, tmp_line_len); - debugs(3, (opt_parse_cfg_only?1:5), "Processing: " << tmp_line); + debugs(3, (opt_parse_cfg_only?1:5), "Processing: " << config_input_line); - if (const char* expr = FindStatement(tmp_line, "if")) { + if (const char* expr = FindStatement(config_input_line, "if")) { if_states.push_back(EvalBoolExpr(expr)); // store last if-statement meaning - } else if (FindStatement(tmp_line, "endif")) { + } else if (FindStatement(config_input_line, "endif")) { if (!if_states.empty()) if_states.pop_back(); // remove last if-statement meaning else fatalf("'endif' without 'if'\n"); - } else if (FindStatement(tmp_line, "else")) { + } else if (FindStatement(config_input_line, "else")) { if (!if_states.empty()) if_states.back() = !if_states.back(); else fatalf("'else' without 'if'\n"); } else if (if_states.empty() || if_states.back()) { // test last if-statement meaning if present /* Handle includes here */ - if (tmp_line_len >= 9 && strncmp(tmp_line, "include", 7) == 0 && xisspace(tmp_line[7])) { - err_count += parseManyConfigFiles(tmp_line + 8, depth + 1); + if (tmp_line_len >= 9 && strncmp(config_input_line, "include", 7) == 0 && xisspace(config_input_line[7])) { + err_count += parseManyConfigFiles(config_input_line + 8, depth + 1); } else { try { - if (!parse_line(tmp_line)) { - debugs(3, DBG_CRITICAL, ConfigParser::CurrentLocation() << ": unrecognized: '" << tmp_line << "'"); + if (!parse_line(config_input_line)) { + debugs(3, DBG_CRITICAL, ConfigParser::CurrentLocation() << ": unrecognized: '" << config_input_line << "'"); ++err_count; } } catch (...) { @@ -573,26 +491,14 @@ parseOneConfigFile(const char *file_name, unsigned int depth) } } - safe_free(tmp_line); - tmp_line_len = 0; - } + if (!if_states.empty()) fatalf("if-statement without 'endif'\n"); - if (is_pipe) { - int ret = pclose(fp); - - if (ret != 0) - fatalf("parseConfigFile: '%s' failed with exit code %d\n", file_name, ret); - } else { - fclose(fp); - } - - SetConfigFilename(orig_cfg_filename, false); + cfg_filename = orig_cfg_filename; config_lineno = orig_config_lineno; - xfree(tmp_line); return err_count; } @@ -1456,6 +1362,28 @@ parseBytesUnits(const char *unit) return 0; } +static void +parse_SBuf(SBuf *s) +{ + *s = ConfigParser::NextQuotedToken(); +} + +static void +dump_SBuf(StoreEntry * entry, const char *name, SBuf &s) +{ + if (!s.isEmpty()) { + entry->append(name, strlen(name)); + entry->append(" ", 1); + entry->append(s.rawContent(), s.length()); + } +} + +static void +free_SBuf(SBuf *s) +{ + s->clear(); +} + static void parse_SBufList(SBufList * list) { diff --git a/src/cf.data.depend b/src/cf.data.depend index fa896248cf7..1256a26990a 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -78,7 +78,7 @@ Security::KeyLog* acl size_t IpAddress_list string -string +SBuf time_msec time_t time_nanoseconds diff --git a/src/cf.data.pre b/src/cf.data.pre index 62130996f07..8872074d669 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -10151,9 +10151,9 @@ DOC_START DOC_END NAME: hosts_file -TYPE: string +TYPE: SBuf DEFAULT: @DEFAULT_HOSTS@ -LOC: Config.etcHostsPath +LOC: Dns::EtcHosts::Path DOC_START Location of the host-local IP name-address associations database. Most Operating Systems have such a file on different diff --git a/src/configuration/File.cc b/src/configuration/File.cc new file mode 100644 index 00000000000..e9a0a320d07 --- /dev/null +++ b/src/configuration/File.cc @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#include "squid.h" +#include "configuration/File.h" +#include "parser/Tokenizer.h" +#include "sbuf/Stream.h" + +#if HAVE_SYS_STAT_H +#include +#endif + +Configuration::File::File(const char *path) : + filePath(path) +{ + if (filePath[0] == '!' || filePath[0] == '|') { + isPipe = true; + filePath.erase(0,1); + } +} + +Configuration::File::~File() +{ + if (fd) { + if (isPipe) + pclose(fd); + else + fclose(fd); + } +} + +void +Configuration::File::load() +{ + debugs(3, 2, "Loading " << (isPipe ? "pipe" : "file") << " " << filePath); + + if (isPipe && !(fd = popen(filePath.c_str(), "r"))) + throw TextException(ToSBuf("configuration pipe : ", filePath, " not found"), lineInfo()); + + else if (!(fd = fopen(filePath.c_str(), "r"))) + throw TextException(ToSBuf("configuration file : ", filePath, " not found"), lineInfo()); + +#if _SQUID_WINDOWS_ + setmode(fileno(fd), O_TEXT); +#endif + + // try to load the entire file into fileData + off_t readSz = 0; + while (!feof(fd) && !ferror(fd)) { + SBuf parseBuffer; + // limit at 1/2 max capacity, so we can combine two SBuf later + off_t len = SBuf::maxSize/2; + auto *p = parseBuffer.rawAppendStart(len); + auto n = fread(p, 1, len, fd); + debugs(3, 2, "Loaded " << n << " bytes (at " << readSz << ") from " << filePath); + if (n > 0) { + readSz += n; + parseBuffer.rawAppendFinish(p, n); + fileData.emplace_back(parseBuffer); + } + } +} + +SBuf +Configuration::File::nextLine() +{ + if (fileData.empty()) + return SBuf(); + + SBuf lineBuf; + while (!lineBuf.isEmpty() || !fileData.empty()) { + auto eol = lineBuf.find('\n'); + while (eol == SBuf::npos && !fileData.empty()) { + auto nextBlob = fileData.front(); + if (lineBuf.length() + nextBlob.length() >= SBuf::maxSize) + throw TextException(ToSBuf("line too long at ", lineBuf.length(), " bytes"), lineInfo()); + lineBuf.append(nextBlob); + debugs(3, 2, "Process chunk " << fileData.size() << " of " << filePath); + fileData.pop_front(); + eol = lineBuf.find('\n'); + } + lineNo++; + + debugs(3, 2, "Process line " << lineNo << " of " << filePath); + debugs(3, 9, lineBuf); + ::Parser::Tokenizer tok(lineBuf.substr(0, eol)); + lineBuf.chop(eol+1); + + /// trim prefix whitespace + (void)tok.skipAll(CharacterSet::WSP); + + /// trim CRLF terminator + static const CharacterSet crlf = (CharacterSet::CR + CharacterSet::LF); + (void)tok.skipAllTrailing(crlf); + + /// if line ends with \-escape, append the next line before parsing + static const CharacterSet wrap("line-wrap", "\\"); + if (tok.skipOneTrailing(wrap)) { + SBuf tmp = tok.remaining(); + tmp.append(lineBuf); + lineBuf = tmp; + debugs(3, 2, "Found wrap on line " << lineNo << " of " << filePath); + continue; + } + + /// trim any trailing whitespace + (void)tok.skipAllTrailing(CharacterSet::WSP); + + /// ignore ( '#' comment ) lines + if (tok.skip('#')) { + /// handle ( '#line ' 1*DIGIT ' "' filepath '"' ) syntax + static const SBuf ln("line"); + if (tok.skip(ln)) { + (void)tok.skipAll(CharacterSet::WSP); + int64_t num = 0; + SBuf name; + if (tok.int64(num, 10, false) && tok.skipAll(CharacterSet::WSP) && + tok.skipOne(CharacterSet::DQUOTE) && tok.skipOneTrailing(CharacterSet::DQUOTE)) { + lineNo = num; + filePath = tok.remaining().toStdString(); + debugs(3, 2, "switch line context to " << lineInfo()); + continue; + } + } + debugs(3, 2, "Skip comment line " << lineNo << " of " << filePath); + continue; + } + + /// ignore empty lines + if (tok.atEnd()) { + debugs(3, 2, "Skip empty line " << lineNo << " of " << filePath); + continue; + } + + // found a line. push lineBuf back onto fileData for later + fileData.push_front(lineBuf); + return tok.remaining(); + } + + return SBuf(); +} diff --git a/src/configuration/File.h b/src/configuration/File.h new file mode 100644 index 00000000000..0d52a3a957c --- /dev/null +++ b/src/configuration/File.h @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#ifndef _SQUID__SRC_CONFIGURATION_FILE_H +#define _SQUID__SRC_CONFIGURATION_FILE_H + +#include "base/Here.h" +#include "configuration/forward.h" +#include "sbuf/SBuf.h" + +#include +#include + +namespace Configuration +{ + +/** + * Class used to store required information from a + * configuration file. + */ +class File +{ +public: + File(const char *path); + ~File(); + + /// \return true if the configuration file is open + bool isOpen() const { return bool(fd); } + + /// Provide the next line of configuration. + /// \return the next line to be parsed from this file, or if none an empty SBuf. + SBuf nextLine(); + + /// \return the configuration file name and line number being processed + SourceLocation lineInfo() const { return SourceLocation("parsing", filePath.c_str(), lineNo); } + + /// open and load contents from the file + void load(); + +private: + std::string filePath; + FILE *fd = nullptr; + + /// Random-size blocks of raw file bytes, in fread(2) order. + /// We do not concatenate these blocks to avoid overflowing SBuf. + SBufList fileData; + + int lineNo = 0; ///< Current line number being parsed + + /// Whether this is a FIFO pipe instead of a regular file. + bool isPipe = false; +}; + +} // namespace Configuration + +#endif /* _SQUID__SRC_CONFIGURATION_FILE_H */ diff --git a/src/configuration/Makefile.am b/src/configuration/Makefile.am new file mode 100644 index 00000000000..e80781d3a76 --- /dev/null +++ b/src/configuration/Makefile.am @@ -0,0 +1,15 @@ +## 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. +## + +include $(top_srcdir)/src/Common.am + +noinst_LTLIBRARIES = libconfiguration.la + +libconfiguration_la_SOURCES = \ + File.cc \ + File.h \ + forward.h diff --git a/src/configuration/forward.h b/src/configuration/forward.h new file mode 100644 index 00000000000..0c2e5aa5565 --- /dev/null +++ b/src/configuration/forward.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef _SQUID__SRC_CONFIGURATION_FORWARD_H +#define _SQUID__SRC_CONFIGURATION_FORWARD_H + +/// Squid Configuration management +namespace Configuration +{ + +/** + * Limit to how long any given config line may be. + * This affects squid.conf and all included files. + * + * Behaviour when setting larger than 2KB is unknown. + * The config parser read mechanism can cope, but the other systems + * receiving the data from its buffers on such lines may not. + */ +#define CONFIG_LINE_LIMIT 2048 + +class File; + +} // namespace Configuration + +#endif /* _SQUID__SRC_CONFIGURATION_FORWARD_H */ diff --git a/src/dns/EtcHosts.cc b/src/dns/EtcHosts.cc new file mode 100644 index 00000000000..25b41946dc4 --- /dev/null +++ b/src/dns/EtcHosts.cc @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#include "squid.h" +#include "base/CharacterSet.h" +#include "configuration/File.h" +#include "dns/EtcHosts.h" +#include "fqdncache.h" +#include "ipcache.h" +#include "parser/Tokenizer.h" +#include "SquidConfig.h" + +DefineRunnerRegistratorIn(Dns, EtcHosts); + +SBuf Dns::EtcHosts::Path; + +void +Dns::EtcHosts::parse() +{ + if (Path.isEmpty() || Path.cmp("none") == 0) + return; + + etcHostsFile = new Configuration::File(Path.c_str()); + etcHostsFile->load(); + + auto line = etcHostsFile->nextLine(); + while (!line.isEmpty()) { + + ::Parser::Tokenizer tok(line); + + // field 1: IP address + static const auto ipChars = CharacterSet("ip",":.") + CharacterSet::HEXDIG; + SBuf addr; + if (!tok.prefix(addr, ipChars)) { + /* invalid address, ignore and try next line. */ + debugs(1, DBG_IMPORTANT, "WARNING: invalid IP address at " << etcHostsFile->lineInfo()); + line = etcHostsFile->nextLine(); + continue; + } + debugs(1, 5, "address is '" << addr << "'"); + + // field 2: list of hostnames + SBufList hosts; + while (!tok.atEnd()) { + + (void)tok.skipAll(CharacterSet::WSP); + if (tok.skip('#')) + break; // ignore trailing comment + + static const auto hostChars = CharacterSet("host",".-_") + CharacterSet::ALPHA + CharacterSet::DIGIT; + SBuf hostname; + if (!tok.prefix(hostname, hostChars)) { + if (!tok.atEnd()) + debugs(1, DBG_IMPORTANT, "WARNING: invalid hostname at " << etcHostsFile->lineInfo()); + break; + } + + debugs(1, 5, "got hostname '" << hostname << "'"); + + // TODO: obey /etc/resolv.conf NDOTS configuration + /* For IPV6 addresses also check for a colon */ + if (Config.appendDomain && hostname.find('.') != SBuf::npos && hostname.find(':') != SBuf::npos) { + hostname.append(Config.appendDomain); + } + + if (ipcacheAddEntryFromHosts(hostname, addr)) { + /* invalid address, continuing is useless */ + hosts.clear(); + break; + } + hosts.emplace_back(hostname); + } + + if (!hosts.empty()) + fqdncacheAddEntryFromHosts(addr, hosts); + + line = etcHostsFile->nextLine(); + } +} + +void +Dns::EtcHosts::clear() +{ + delete etcHostsFile; + etcHostsFile = nullptr; +} diff --git a/src/dns/EtcHosts.h b/src/dns/EtcHosts.h new file mode 100644 index 00000000000..ae75fa38c5a --- /dev/null +++ b/src/dns/EtcHosts.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef SQUID_SRC_DNS_ETCHOSTS_H +#define SQUID_SRC_DNS_ETCHOSTS_H + +#include "base/RunnersRegistry.h" +#include "configuration/forward.h" +#include "dns/forward.h" +#include "sbuf/SBuf.h" + +namespace Dns +{ + +/// specialized parser for /etc/hosts file +class EtcHosts : public RegisteredRunner +{ +public: + /// squid.conf etc_hosts setting (if any) + static SBuf Path; + + ~EtcHosts() { clear(); } + + /* RegisteredRunner API */ + void finalizeConfig() override { parse(); } + void startReconfigure() override { clear(); } + void syncConfig() override { parse(); } + +private: + void parse(); + void clear(); + + Configuration::File *etcHostsFile = nullptr; +}; + +} // namespace Dns + +#endif /* SQUID_SRC_DNS_ETCHOSTS_H */ diff --git a/src/dns/Makefile.am b/src/dns/Makefile.am index b5e4df89a18..d7eb5c5e47a 100644 --- a/src/dns/Makefile.am +++ b/src/dns/Makefile.am @@ -10,6 +10,8 @@ include $(top_srcdir)/src/Common.am noinst_LTLIBRARIES = libdns.la libdns_la_SOURCES = \ + EtcHosts.cc \ + EtcHosts.h \ LookupDetails.cc \ LookupDetails.h \ forward.h \ diff --git a/src/dns/forward.h b/src/dns/forward.h index 0a0f57f804c..29a6e245dbc 100644 --- a/src/dns/forward.h +++ b/src/dns/forward.h @@ -19,6 +19,7 @@ typedef void IDNSCB(void *cbdata, const rfc1035_rr *answer, const int recordsInA namespace Dns { +class EtcHosts; class LookupDetails; void Init(void); diff --git a/src/fqdncache.cc b/src/fqdncache.cc index 78670fb2352..d74a8bf92d7 100644 --- a/src/fqdncache.cc +++ b/src/fqdncache.cc @@ -633,9 +633,9 @@ fqdncache_restart(void) \param hostnames list of hostnames for the addr */ void -fqdncacheAddEntryFromHosts(char *addr, SBufList &hostnames) +fqdncacheAddEntryFromHosts(SBuf &addr, SBufList &hostnames) { - fqdncache_entry *fce= fqdncache_get(addr); + auto *fce = fqdncache_get(addr.c_str()); if (fce) { if (1 == fce->flags.fromhosts) { fqdncacheUnlockEntry(fce); @@ -647,11 +647,11 @@ fqdncacheAddEntryFromHosts(char *addr, SBufList &hostnames) } } - fce = new fqdncache_entry(addr); + fce = new fqdncache_entry(addr.c_str()); int j = 0; for (auto &h : hostnames) { - fce->names[j] = xstrdup(h.c_str()); + fce->names[j] = SBufToCstring(h); Tolower(fce->names[j]); ++j; diff --git a/src/fqdncache.h b/src/fqdncache.h index 262c8997a1c..122736b2ca4 100644 --- a/src/fqdncache.h +++ b/src/fqdncache.h @@ -29,7 +29,7 @@ void fqdncache_init(void); void fqdnStats(StoreEntry *); void fqdncache_restart(void); void fqdncache_purgelru(void *); -void fqdncacheAddEntryFromHosts(char *addr, SBufList &hostnames); +void fqdncacheAddEntryFromHosts(SBuf &addr, SBufList &hostnames); const char *fqdncache_gethostbyaddr(const Ip::Address &, int flags); void fqdncache_nbgethostbyaddr(const Ip::Address &, FQDNH *, void *); diff --git a/src/ipcache.cc b/src/ipcache.cc index 05eba860c39..4107b6a4411 100644 --- a/src/ipcache.cc +++ b/src/ipcache.cc @@ -1119,48 +1119,44 @@ ipcache_restart(void) \param name Hostname to be linked with IP \param ipaddr IP Address to be cached. * - \retval 0 Success. - \retval 1 IP address is invalid or other error. + \retval true Success. + \retval false IP address is invalid or other error. */ -int -ipcacheAddEntryFromHosts(const char *name, const char *ipaddr) +bool +ipcacheAddEntryFromHosts(SBuf &name, SBuf &ipaddr) { - ipcache_entry *i; - Ip::Address ip; - if (!(ip = ipaddr)) { - if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) { - debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'"); - } else { - debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'"); - } + if (!(ip = ipaddr.c_str())) { + if (ipaddr.find(':') != SBuf::npos) + debugs(14, 3, "skipping IPv6 address: " << ipaddr); + else + debugs(14, DBG_IMPORTANT, "ERROR: Bad IP address '" << ipaddr << "'"); - return 1; + return false; } if (!Ip::EnableIpv6 && ip.isIPv6()) { debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip); - return 1; + return false; } - if ((i = ipcache_get(name))) { - if (1 == i->flags.fromhosts) { + if (auto *i = ipcache_get(name.c_str())) { + if (1 == i->flags.fromhosts) ipcacheUnlockEntry(i); - } else if (i->locks > 0) { - debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: cannot add static entry for locked name '" << name << "'"); - return 1; - } else { + else if (i->locks > 0) { + debugs(14, DBG_IMPORTANT, "ERROR: cannot add static entry for locked name '" << name << "'"); + return false; + } else ipcacheRelease(i); - } } - i = new ipcache_entry(name); + auto *i = new ipcache_entry(name.c_str()); i->addrs.pushUnique(ip); i->flags.fromhosts = true; ipcacheAddEntry(i); ipcacheLockEntry(i); - return 0; + return true; } #if SQUID_SNMP diff --git a/src/ipcache.h b/src/ipcache.h index 4e5d969d298..08622cb9526 100644 --- a/src/ipcache.h +++ b/src/ipcache.h @@ -235,7 +235,7 @@ void ipcache_init(void); void ipcacheMarkBadAddr(const char *name, const Ip::Address &); void ipcacheMarkGoodAddr(const char *name, const Ip::Address &); void ipcache_restart(void); -int ipcacheAddEntryFromHosts(const char *name, const char *ipaddr); +bool ipcacheAddEntryFromHosts(SBuf &name, SBuf &ipaddr); /* inlined implementations */ diff --git a/src/main.cc b/src/main.cc index 51859775035..20d96a8dde5 100644 --- a/src/main.cc +++ b/src/main.cc @@ -31,7 +31,7 @@ #include "CpuAffinity.h" #include "debug/Messages.h" #include "DiskIO/DiskIOModule.h" -#include "dns/forward.h" +#include "dns/EtcHosts.h" #include "errorpage.h" #include "event.h" #include "EventLoop.h" @@ -928,7 +928,6 @@ mainReconfigureFinish(void *) Debug::UseCacheLog(); ipcache_restart(); /* clear stuck entries */ fqdncache_restart(); /* sigh, fqdncache too */ - parseEtcHosts(); errorInitialize(); /* reload error pages */ accessLogInit(); @@ -1146,8 +1145,6 @@ mainInitialize(void) fqdncache_init(); - parseEtcHosts(); - Dns::Init(); #if USE_SSL_CRTD @@ -1455,6 +1452,7 @@ RegisterModules() CallRunnerRegistrator(SharedSessionCacheRr); CallRunnerRegistrator(TransientsRr); CallRunnerRegistratorIn(Dns, ConfigRr); + CallRunnerRegistratorIn(Dns, EtcHosts); #if HAVE_DISKIO_MODULE_IPCIO CallRunnerRegistrator(IpcIoRr); diff --git a/src/tests/stub_fqdncache.cc b/src/tests/stub_fqdncache.cc index cf46779dc65..d236e653571 100644 --- a/src/tests/stub_fqdncache.cc +++ b/src/tests/stub_fqdncache.cc @@ -18,6 +18,6 @@ void fqdncache_init(void) STUB void fqdnStats(StoreEntry *) STUB void fqdncache_restart(void) STUB void fqdncache_purgelru(void *) STUB -void fqdncacheAddEntryFromHosts(char *, SBufList &) STUB +void fqdncacheAddEntryFromHosts(SBuf &, SBufList &) STUB const char *fqdncache_gethostbyaddr(const Ip::Address &, int) STUB_RETVAL(nullptr) void fqdncache_nbgethostbyaddr(const Ip::Address &, FQDNH *, void *) STUB diff --git a/src/tests/stub_ipcache.cc b/src/tests/stub_ipcache.cc index d83699b0f68..65dabf686fc 100644 --- a/src/tests/stub_ipcache.cc +++ b/src/tests/stub_ipcache.cc @@ -21,5 +21,5 @@ void ipcache_init(void) STUB void ipcacheMarkBadAddr(const char *, const Ip::Address &) STUB void ipcacheMarkGoodAddr(const char *, const Ip::Address &) STUB void ipcache_restart(void) STUB -int ipcacheAddEntryFromHosts(const char *, const char *) STUB_RETVAL(-1) +bool ipcacheAddEntryFromHosts(SBuf &, SBuf &) STUB_RETVAL(-1) diff --git a/src/tests/stub_tools.cc b/src/tests/stub_tools.cc index 338de5053ce..aee3fbaa57c 100644 --- a/src/tests/stub_tools.cc +++ b/src/tests/stub_tools.cc @@ -64,7 +64,6 @@ void setSystemLimits(void) STUB void squid_signal(int, SIGHDLR *, int) STUB void logsFlush(void) STUB void debugObj(int, int, const char *, void *, ObjPackMethod) STUB -void parseEtcHosts(void) STUB int getMyPort(void) STUB_RETVAL(0) void setUmask(mode_t) STUB void strwordquote(MemBuf *, const char *) STUB diff --git a/src/tools.cc b/src/tools.cc index ae3eb0a5d83..35c2b24253a 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -945,98 +945,6 @@ debugObj(int section, int level, const char *label, void *obj, ObjPackMethod pm) mb.clean(); } -void -parseEtcHosts(void) -{ - char buf[1024]; - char buf2[512]; - char *nt = buf; - char *lt = buf; - - if (!Config.etcHostsPath) - return; - - if (0 == strcmp(Config.etcHostsPath, "none")) - return; - - FILE *fp = fopen(Config.etcHostsPath, "r"); - - if (!fp) { - int xerrno = errno; - debugs(1, DBG_IMPORTANT, "parseEtcHosts: '" << Config.etcHostsPath << "' : " << xstrerr(xerrno)); - return; - } - -#if _SQUID_WINDOWS_ - setmode(fileno(fp), O_TEXT); -#endif - - while (fgets(buf, 1024, fp)) { /* for each line */ - - if (buf[0] == '#') /* MS-windows likes to add comments */ - continue; - - strtok(buf, "#"); /* chop everything following a comment marker */ - - lt = buf; - - char *addr = buf; - - debugs(1, 5, "etc_hosts: line is '" << buf << "'"); - - nt = strpbrk(lt, w_space); - - if (nt == nullptr) /* empty line */ - continue; - - *nt = '\0'; /* null-terminate the address */ - - debugs(1, 5, "etc_hosts: address is '" << addr << "'"); - - lt = nt + 1; - - SBufList hosts; - - while ((nt = strpbrk(lt, w_space))) { - char *host = nullptr; - - if (nt == lt) { /* multiple spaces */ - debugs(1, 5, "etc_hosts: multiple spaces, skipping"); - lt = nt + 1; - continue; - } - - *nt = '\0'; - debugs(1, 5, "etc_hosts: got hostname '" << lt << "'"); - - /* For IPV6 addresses also check for a colon */ - if (Config.appendDomain && !strchr(lt, '.') && !strchr(lt, ':')) { - /* I know it's ugly, but it's only at reconfig */ - strncpy(buf2, lt, sizeof(buf2)-1); - strncat(buf2, Config.appendDomain, sizeof(buf2) - strlen(lt) - 1); - buf2[sizeof(buf2)-1] = '\0'; - host = buf2; - } else { - host = lt; - } - - if (ipcacheAddEntryFromHosts(host, addr) != 0) { - /* invalid address, continuing is useless */ - hosts.clear(); - break; - } - hosts.emplace_back(SBuf(host)); - - lt = nt + 1; - } - - if (!hosts.empty()) - fqdncacheAddEntryFromHosts(addr, hosts); - } - - fclose (fp); -} - int getMyPort(void) { diff --git a/src/tools.h b/src/tools.h index f3c808a6ee0..413ae1f0527 100644 --- a/src/tools.h +++ b/src/tools.h @@ -22,7 +22,6 @@ extern int DebugSignal; /// Default is APP_SHORTNAME ('squid'). extern SBuf service_name; -void parseEtcHosts(void); int getMyPort(void); void setUmask(mode_t mask); void strwordquote(MemBuf * mb, const char *str);