]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ConfigParser.cc
Fix configuration file parsing bugs, related to quoted strings
[thirdparty/squid.git] / src / ConfigParser.cc
1
2 /*
3 *
4 * SQUID Web Proxy Cache http://www.squid-cache.org/
5 * ----------------------------------------------------------
6 *
7 * Squid is the result of efforts by numerous individuals from
8 * the Internet community; see the CONTRIBUTORS file for full
9 * details. Many organizations have provided support for Squid's
10 * development; see the SPONSORS file for full details. Squid is
11 * Copyrighted (C) 2001 by the Regents of the University of
12 * California; see the COPYRIGHT file for full details. Squid
13 * incorporates software developed and/or copyrighted by other
14 * sources; see the CREDITS file for full details.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
29 *
30 *
31 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
32 */
33
34 #include "squid.h"
35 #include "cache_cf.h"
36 #include "ConfigParser.h"
37 #include "Debug.h"
38 #include "fatal.h"
39 #include "globals.h"
40
41 bool ConfigParser::RecognizeQuotedValues = true;
42 bool ConfigParser::StrictMode = true;
43 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
44 ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken;
45 const char *ConfigParser::CfgLine = NULL;
46 const char *ConfigParser::CfgPos = NULL;
47 std::queue<char *> ConfigParser::CfgLineTokens_;
48 std::queue<std::string> ConfigParser::Undo_;
49 bool ConfigParser::AllowMacros_ = false;
50 bool ConfigParser::ParseQuotedOrToEol_ = false;
51 bool ConfigParser::PreviewMode_ = false;
52
53 static const char *SQUID_ERROR_TOKEN = "[invalid token]";
54
55 void
56 ConfigParser::destruct()
57 {
58 shutting_down = 1;
59 if (!CfgFiles.empty()) {
60 std::ostringstream message;
61 CfgFile *f = CfgFiles.top();
62 message << "Bungled " << f->filePath << " line " << f->lineNo <<
63 ": " << f->currentLine << std::endl;
64 CfgFiles.pop();
65 delete f;
66 while (!CfgFiles.empty()) {
67 f = CfgFiles.top();
68 message << " included from " << f->filePath << " line " <<
69 f->lineNo << ": " << f->currentLine << std::endl;
70 CfgFiles.pop();
71 delete f;
72 }
73 message << " included from " << cfg_filename << " line " <<
74 config_lineno << ": " << config_input_line << std::endl;
75 std::string msg = message.str();
76 fatalf("%s", msg.c_str());
77 } else
78 fatalf("Bungled %s line %d: %s",
79 cfg_filename, config_lineno, config_input_line);
80 }
81
82 void
83 ConfigParser::TokenPutBack(const char *tok)
84 {
85 assert(tok);
86 Undo_.push(tok);
87 }
88
89 char *
90 ConfigParser::Undo()
91 {
92 LOCAL_ARRAY(char, undoToken, CONFIG_LINE_LIMIT);
93 if (!Undo_.empty()) {
94 strncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
95 undoToken[sizeof(undoToken) - 1] = '\0';
96 if (!PreviewMode_)
97 Undo_.pop();
98 return undoToken;
99 }
100 return NULL;
101 }
102
103 char *
104 ConfigParser::strtokFile()
105 {
106 if (RecognizeQuotedValues)
107 return ConfigParser::NextToken();
108
109 static int fromFile = 0;
110 static FILE *wordFile = NULL;
111
112 char *t;
113 LOCAL_ARRAY(char, buf, CONFIG_LINE_LIMIT);
114
115 if ((t = ConfigParser::Undo()))
116 return t;
117
118 do {
119
120 if (!fromFile) {
121 ConfigParser::TokenType tokenType;
122 t = ConfigParser::NextElement(tokenType);
123 if (!t) {
124 return NULL;
125 } else if (*t == '\"' || *t == '\'') {
126 /* quote found, start reading from file */
127 debugs(3, 8,"Quoted token found : " << t);
128 char *fn = ++t;
129
130 while (*t && *t != '\"' && *t != '\'')
131 ++t;
132
133 *t = '\0';
134
135 if ((wordFile = fopen(fn, "r")) == NULL) {
136 debugs(3, DBG_CRITICAL, "Can not open file " << t << " for reading");
137 return NULL;
138 }
139
140 #if _SQUID_WINDOWS_
141 setmode(fileno(wordFile), O_TEXT);
142 #endif
143
144 fromFile = 1;
145 } else {
146 return t;
147 }
148 }
149
150 /* fromFile */
151 if (fgets(buf, CONFIG_LINE_LIMIT, wordFile) == NULL) {
152 /* stop reading from file */
153 fclose(wordFile);
154 wordFile = NULL;
155 fromFile = 0;
156 return NULL;
157 } else {
158 char *t2, *t3;
159 t = buf;
160 /* skip leading and trailing white space */
161 t += strspn(buf, w_space);
162 t2 = t + strcspn(t, w_space);
163 t3 = t2 + strspn(t2, w_space);
164
165 while (*t3 && *t3 != '#') {
166 t2 = t3 + strcspn(t3, w_space);
167 t3 = t2 + strspn(t2, w_space);
168 }
169
170 *t2 = '\0';
171 }
172
173 /* skip comments */
174 /* skip blank lines */
175 } while ( *t == '#' || !*t );
176
177 return t;
178 }
179
180 char *
181 ConfigParser::UnQuote(const char *token, const char **next)
182 {
183 const char *errorStr = NULL;
184 const char *errorPos = NULL;
185 char quoteChar = *token;
186 assert(quoteChar == '"' || quoteChar == '\'');
187 LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
188 const char *s = token + 1;
189 char *d = UnQuoted;
190 /* scan until the end of the quoted string, handling escape sequences*/
191 while (*s && *s != quoteChar && !errorStr && (d - UnQuoted) < sizeof(UnQuoted)) {
192 if (*s == '\\') {
193 s++;
194 switch (*s) {
195 case 'r':
196 *d = '\r';
197 break;
198 case 'n':
199 *d = '\n';
200 break;
201 case 't':
202 *d = '\t';
203 break;
204 default:
205 if (isalnum(*s)) {
206 errorStr = "Unsupported escape sequence";
207 errorPos = s;
208 }
209 *d = *s;
210 break;
211 }
212 #if 0
213 } else if (*s == '$' && quoteChar == '"') {
214 errorStr = "Unsupported cfg macro";
215 errorPos = s;
216 #endif
217 } else if (*s == '%' && quoteChar == '"' && (!AllowMacros_ )) {
218 errorStr = "Macros are not supported here";
219 errorPos = s;
220 } else
221 *d = *s;
222 ++s;
223 ++d;
224 }
225
226 if (*s != quoteChar && !errorStr) {
227 errorStr = "missing quote char at the end of quoted string";
228 errorPos = s - 1;
229 }
230 // The end of token
231 *d = '\0';
232
233 // We are expecting a separator after quoted string, space or one of "()#"
234 if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
235 errorStr = "Expecting space after the end of quoted token";
236 errorPos = token;
237 }
238
239 if (errorStr) {
240 if (PreviewMode_)
241 strncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
242 else {
243 debugs(3, DBG_CRITICAL, errorStr << ": " << errorPos);
244 self_destruct();
245 }
246 }
247
248 if (next)
249 *next = s + 1;
250 return UnQuoted;
251 }
252
253 void
254 ConfigParser::SetCfgLine(char *line)
255 {
256 CfgLine = line;
257 CfgPos = line;
258 while (!CfgLineTokens_.empty()) {
259 char *token = CfgLineTokens_.front();
260 CfgLineTokens_.pop();
261 free(token);
262 }
263 }
264
265 char *
266 ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
267 {
268 if (!nextToken || *nextToken == '\0')
269 return NULL;
270 type = ConfigParser::SimpleToken;
271 nextToken += strspn(nextToken, w_space);
272
273 if (*nextToken == '#')
274 return NULL;
275
276 if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
277 type = ConfigParser::QuotedToken;
278 char *token = xstrdup(UnQuote(nextToken, &nextToken));
279 CfgLineTokens_.push(token);
280 return token;
281 }
282
283 const char *tokenStart = nextToken;
284 const char *sep;
285 if (ConfigParser::ParseQuotedOrToEol_)
286 sep = "\n";
287 else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
288 sep = w_space;
289 else
290 sep = w_space "(";
291 nextToken += strcspn(nextToken, sep);
292
293 if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
294 if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
295 type = ConfigParser::FunctionParameters;
296 else {
297 if (PreviewMode_) {
298 char *err = xstrdup(SQUID_ERROR_TOKEN);
299 CfgLineTokens_.push(err);
300 return err;
301 } else {
302 debugs(3, DBG_CRITICAL, "Unknown cfg function: " << tokenStart);
303 self_destruct();
304 }
305 }
306 } else
307 type = ConfigParser::SimpleToken;
308
309 char *token = NULL;
310 if (nextToken - tokenStart) {
311 if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) {
312 bool tokenIsNumber = true;
313 for (const char *s = tokenStart; s != nextToken; ++s) {
314 const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:", *s) ||
315 (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
316
317 if (!isdigit(*s))
318 tokenIsNumber = false;
319
320 if (!isValidChar) {
321 if (PreviewMode_) {
322 char *err = xstrdup(SQUID_ERROR_TOKEN);
323 CfgLineTokens_.push(err);
324 return err;
325 } else {
326 debugs(3, DBG_CRITICAL, "Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
327 self_destruct();
328 }
329 }
330 }
331 }
332 token = xstrndup(tokenStart, nextToken - tokenStart + 1);
333 CfgLineTokens_.push(token);
334 }
335
336 if (*nextToken != '\0' && *nextToken != '#') {
337 ++nextToken;
338 }
339
340 return token;
341 }
342
343 char *
344 ConfigParser::NextElement(ConfigParser::TokenType &type)
345 {
346 const char *pos = CfgPos;
347 char *token = TokenParse(pos, type);
348 // If not in preview mode the next call of this method should start
349 // parsing after the end of current token.
350 // For function "parameters(...)" we need always to update current parsing
351 // position to allow parser read the arguments of "parameters(..)"
352 if (!PreviewMode_ || type == FunctionParameters)
353 CfgPos = pos;
354 // else next call will read the same token
355 return token;
356 }
357
358 char *
359 ConfigParser::NextToken()
360 {
361 char *token = NULL;
362 if ((token = ConfigParser::Undo())) {
363 debugs(3, 6, "TOKEN (undone): " << token);
364 return token;
365 }
366
367 do {
368 while (token == NULL && !CfgFiles.empty()) {
369 ConfigParser::CfgFile *wordfile = CfgFiles.top();
370 token = wordfile->parse(LastTokenType);
371 if (!token) {
372 assert(!wordfile->isOpen());
373 CfgFiles.pop();
374 debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
375 delete wordfile;
376 }
377 }
378
379 if (!token)
380 token = NextElement(LastTokenType);
381
382 if (token && LastTokenType == ConfigParser::FunctionParameters) {
383 //Disable temporary preview mode, we need to parse function parameters
384 const bool savePreview = ConfigParser::PreviewMode_;
385 ConfigParser::PreviewMode_ = false;
386
387 char *path = NextToken();
388 if (LastTokenType != ConfigParser::QuotedToken) {
389 debugs(3, DBG_CRITICAL, "Quoted filename missing: " << token);
390 self_destruct();
391 return NULL;
392 }
393
394 // The next token in current cfg file line must be a ")"
395 char *end = NextToken();
396 ConfigParser::PreviewMode_ = savePreview;
397 if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
398 debugs(3, DBG_CRITICAL, "missing ')' after " << token << "(\"" << path << "\"");
399 self_destruct();
400 return NULL;
401 }
402
403 if (CfgFiles.size() > 16) {
404 debugs(3, DBG_CRITICAL, "WARNING: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
405 self_destruct();
406 return NULL;
407 }
408
409 ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile();
410 if (!path || !wordfile->startParse(path)) {
411 debugs(3, DBG_CRITICAL, "Error opening config file: " << token);
412 delete wordfile;
413 self_destruct();
414 return NULL;
415 }
416 CfgFiles.push(wordfile);
417 token = NULL;
418 }
419 } while (token == NULL && !CfgFiles.empty());
420
421 return token;
422 }
423
424 char *
425 ConfigParser::PeekAtToken()
426 {
427 PreviewMode_ = true;
428 char *token = NextToken();
429 PreviewMode_ = false;
430 return token;
431 }
432
433 char *
434 ConfigParser::NextQuotedOrToEol()
435 {
436 ParseQuotedOrToEol_ = true;
437 char *token = NextToken();
438 ParseQuotedOrToEol_ = false;
439
440 // Assume end of current config line
441 // Close all open configuration files for this config line
442 while (!CfgFiles.empty()) {
443 ConfigParser::CfgFile *wordfile = CfgFiles.top();
444 CfgFiles.pop();
445 delete wordfile;
446 }
447
448 return token;
449 }
450
451 char *
452 ConfigParser::RegexStrtokFile()
453 {
454 if (ConfigParser::RecognizeQuotedValues) {
455 debugs(3, DBG_CRITICAL, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
456 self_destruct();
457 }
458 char * token = strtokFile();
459 return token;
460 }
461
462 char *
463 ConfigParser::RegexPattern()
464 {
465 if (ConfigParser::RecognizeQuotedValues) {
466 debugs(3, DBG_CRITICAL, "Can not read regex expresion while configuration_includes_quoted_values is enabled");
467 self_destruct();
468 }
469
470 char * token = NextToken();
471 return token;
472 }
473
474 char *
475 ConfigParser::NextQuotedToken()
476 {
477 const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
478 ConfigParser::RecognizeQuotedValues = true;
479 char *token = NextToken();
480 ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
481 return token;
482 }
483
484 const char *
485 ConfigParser::QuoteString(const String &var)
486 {
487 static String quotedStr;
488 const char *s = var.termedBuf();
489 bool needQuote = false;
490
491 for (const char *l = s; !needQuote && *l != '\0'; ++l )
492 needQuote = !isalnum(*l);
493
494 if (!needQuote)
495 return s;
496
497 quotedStr.clean();
498 quotedStr.append('"');
499 for (; *s != '\0'; ++s) {
500 if (*s == '"' || *s == '\\')
501 quotedStr.append('\\');
502 quotedStr.append(*s);
503 }
504 quotedStr.append('"');
505 return quotedStr.termedBuf();
506 }
507
508 bool
509 ConfigParser::CfgFile::startParse(char *path)
510 {
511 assert(wordFile == NULL);
512 debugs(3, 3, "Parsing from " << path);
513 if ((wordFile = fopen(path, "r")) == NULL) {
514 debugs(3, DBG_CRITICAL, "file :" << path << " not found");
515 return false;
516 }
517
518 #if _SQUID_WINDOWS_
519 setmode(fileno(wordFile), O_TEXT);
520 #endif
521
522 filePath = path;
523 return getFileLine();
524 }
525
526 bool
527 ConfigParser::CfgFile::getFileLine()
528 {
529 // Else get the next line
530 if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
531 /* stop reading from file */
532 fclose(wordFile);
533 wordFile = NULL;
534 parseBuffer[0] = '\0';
535 return false;
536 }
537 parsePos = parseBuffer;
538 currentLine = parseBuffer;
539 lineNo++;
540 return true;
541 }
542
543 char *
544 ConfigParser::CfgFile::parse(ConfigParser::TokenType &type)
545 {
546 if (!wordFile)
547 return NULL;
548
549 if (!*parseBuffer)
550 return NULL;
551
552 char *token;
553 while (!(token = nextElement(type))) {
554 if (!getFileLine())
555 return NULL;
556 }
557 return token;
558 }
559
560 char *
561 ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type)
562 {
563 const char *pos = parsePos;
564 char *token = TokenParse(pos, type);
565 if (!PreviewMode_ || type == FunctionParameters)
566 parsePos = pos;
567 // else next call will read the same token;
568 return token;
569 }
570
571 ConfigParser::CfgFile::~CfgFile()
572 {
573 if (wordFile)
574 fclose(wordFile);
575 }