1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 04 Error Generation */
10
11 #include "squid.h"
12 #include "cache_cf.h"
13 #include "clients/forward.h"
14 #include "comm/Connection.h"
15 #include "comm/Write.h"
16 #include "err_detail_type.h"
17 #include "errorpage.h"
18 #include "fde.h"
19 #include "fs_io.h"
20 #include "html_quote.h"
21 #include "HttpHeaderTools.h"
22 #include "HttpReply.h"
23 #include "HttpRequest.h"
24 #include "MemBuf.h"
25 #include "MemObject.h"
26 #include "rfc1738.h"
27 #include "SquidConfig.h"
28 #include "Store.h"
29 #include "tools.h"
30 #include "wordlist.h"
31 #if USE_AUTH
32 #include "auth/UserRequest.h"
33 #endif
34 #include "SquidTime.h"
35 #if USE_OPENSSL
36 #include "ssl/ErrorDetailManager.h"
37 #endif
38
39 /**
40 \defgroup ErrorPageInternal Error Page Internals
41 \ingroup ErrorPageAPI
42 *
43 \section Abstract Abstract:
44 * These routines are used to generate error messages to be
45 * sent to clients. The error type is used to select between
46 * the various message formats. (formats are stored in the
47 * Config.errorDirectory)
48 */
49
50 #if !defined(DEFAULT_SQUID_ERROR_DIR)
51 /** Where to look for errors if config path fails.
52 \note Please use ./configure --datadir=/path instead of patching
53 */
54 #define DEFAULT_SQUID_ERROR_DIR DEFAULT_SQUID_DATA_DIR"/errors"
55 #endif
56
57 /// \ingroup ErrorPageInternal
58 CBDATA_CLASS_INIT(ErrorState);
59
60 /* local types */
61
62 /// \ingroup ErrorPageInternal
63 typedef struct {
64 int id;
65 char *page_name;
66 Http::StatusCode page_redirect;
67 } ErrorDynamicPageInfo;
68
69 /* local constant and vars */
70
71 /**
72 \ingroup ErrorPageInternal
73 *
74 \note hard coded error messages are not appended with %S
75 * automagically to give you more control on the format
76 */
77 static const struct {
78 int type; /* and page_id */
79 const char *text;
80 }
81
82 error_hard_text[] = {
83
84 {
85 ERR_SQUID_SIGNATURE,
86 "\n<br>\n"
87 "<hr>\n"
88 "<div id=\"footer\">\n"
89 "Generated %T by %h (%s)\n"
90 "</div>\n"
91 "</body></html>\n"
92 },
93 {
94 TCP_RESET,
95 "reset"
96 }
97 };
98
99 /// \ingroup ErrorPageInternal
100 static std::vector<ErrorDynamicPageInfo *> ErrorDynamicPages;
101
102 /* local prototypes */
103
104 /// \ingroup ErrorPageInternal
105 static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
106
107 /// \ingroup ErrorPageInternal
108 static char **error_text = NULL;
109
110 /// \ingroup ErrorPageInternal
111 static int error_page_count = 0;
112
113 /// \ingroup ErrorPageInternal
114 static MemBuf error_stylesheet;
115
116 static const char *errorFindHardText(err_type type);
117 static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
118 static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
119 static IOCB errorSendComplete;
120
121 /// \ingroup ErrorPageInternal
122 /// manages an error page template
123 class ErrorPageFile: public TemplateFile
124 {
125 public:
ErrorPageFile(const char * name,const err_type code)126 ErrorPageFile(const char *name, const err_type code) : TemplateFile(name,code) {textBuf.init();}
127
128 /// The template text data read from disk
text()129 const char *text() { return textBuf.content(); }
130
131 private:
132 /// stores the data read from disk to a local buffer
parse(const char * buf,int len,bool)133 virtual bool parse(const char *buf, int len, bool) {
134 if (len)
135 textBuf.append(buf, len);
136 return true;
137 }
138
139 MemBuf textBuf; ///< A buffer to store the error page
140 };
141
142 /// \ingroup ErrorPageInternal
operator ++(err_type & anErr)143 err_type &operator++ (err_type &anErr)
144 {
145 int tmp = (int)anErr;
146 anErr = (err_type)(++tmp);
147 return anErr;
148 }
149
150 /// \ingroup ErrorPageInternal
operator -(err_type const & anErr,err_type const & anErr2)151 int operator - (err_type const &anErr, err_type const &anErr2)
152 {
153 return (int)anErr - (int)anErr2;
154 }
155
156 void
errorInitialize(void)157 errorInitialize(void)
158 {
159 err_type i;
160 const char *text;
161 error_page_count = ERR_MAX + ErrorDynamicPages.size();
162 error_text = static_cast<char **>(xcalloc(error_page_count, sizeof(char *)));
163
164 for (i = ERR_NONE, ++i; i < error_page_count; ++i) {
165 safe_free(error_text[i]);
166
167 if ((text = errorFindHardText(i))) {
168 /**\par
169 * Index any hard-coded error text into defaults.
170 */
171 error_text[i] = xstrdup(text);
172
173 } else if (i < ERR_MAX) {
174 /**\par
175 * Index precompiled fixed template files from one of two sources:
176 * (a) default language translation directory (error_default_language)
177 * (b) admin specified custom directory (error_directory)
178 */
179 ErrorPageFile errTmpl(err_type_str[i], i);
180 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
181 } else {
182 /** \par
183 * Index any unknown file names used by deny_info.
184 */
185 ErrorDynamicPageInfo *info = ErrorDynamicPages.at(i - ERR_MAX);
186 assert(info && info->id == i && info->page_name);
187
188 const char *pg = info->page_name;
189 if (info->page_redirect != Http::scNone)
190 pg = info->page_name +4;
191
192 if (strchr(pg, ':') == NULL) {
193 /** But only if they are not redirection URL. */
194 ErrorPageFile errTmpl(pg, ERR_MAX);
195 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
196 }
197 }
198 }
199
200 error_stylesheet.reset();
201
202 // look for and load stylesheet into global MemBuf for it.
203 if (Config.errorStylesheet) {
204 ErrorPageFile tmpl("StylesSheet", ERR_MAX);
205 tmpl.loadFromFile(Config.errorStylesheet);
206 error_stylesheet.appendf("%s",tmpl.text());
207 }
208
209 #if USE_OPENSSL
210 Ssl::errorDetailInitialize();
211 #endif
212 }
213
214 void
errorClean(void)215 errorClean(void)
216 {
217 if (error_text) {
218 int i;
219
220 for (i = ERR_NONE + 1; i < error_page_count; ++i)
221 safe_free(error_text[i]);
222
223 safe_free(error_text);
224 }
225
226 while (!ErrorDynamicPages.empty()) {
227 errorDynamicPageInfoDestroy(ErrorDynamicPages.back());
228 ErrorDynamicPages.pop_back();
229 }
230
231 error_page_count = 0;
232
233 #if USE_OPENSSL
234 Ssl::errorDetailClean();
235 #endif
236 }
237
238 /// \ingroup ErrorPageInternal
239 static const char *
errorFindHardText(err_type type)240 errorFindHardText(err_type type)
241 {
242 int i;
243
244 for (i = 0; i < error_hard_text_count; ++i)
245 if (error_hard_text[i].type == type)
246 return error_hard_text[i].text;
247
248 return NULL;
249 }
250
TemplateFile(const char * name,const err_type code)251 TemplateFile::TemplateFile(const char *name, const err_type code): silent(false), wasLoaded(false), templateName(name), templateCode(code)
252 {
253 assert(name);
254 }
255
256 bool
loadDefault()257 TemplateFile::loadDefault()
258 {
259 if (loaded()) // already loaded?
260 return true;
261
262 /** test error_directory configured location */
263 if (Config.errorDirectory) {
264 char path[MAXPATHLEN];
265 snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
266 loadFromFile(path);
267 }
268
269 #if USE_ERR_LOCALES
270 /** test error_default_language location */
271 if (!loaded() && Config.errorDefaultLanguage) {
272 if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
273 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "Unable to load default error language files. Reset to backups.");
274 }
275 }
276 #endif
277
278 /* test default location if failed (templates == English translation base templates) */
279 if (!loaded()) {
280 tryLoadTemplate("templates");
281 }
282
283 /* giving up if failed */
284 if (!loaded()) {
285 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
286 parse("Internal Error: Missing Template ", 33, '\0');
287 parse(templateName.termedBuf(), templateName.size(), '\0');
288 }
289
290 return true;
291 }
292
293 bool
tryLoadTemplate(const char * lang)294 TemplateFile::tryLoadTemplate(const char *lang)
295 {
296 assert(lang);
297
298 char path[MAXPATHLEN];
299 /* TODO: prep the directory path string to prevent snprintf ... */
300 snprintf(path, sizeof(path), "%s/%s/%s",
301 DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf());
302 path[MAXPATHLEN-1] = '\0';
303
304 if (loadFromFile(path))
305 return true;
306
307 #if HAVE_GLOB
308 if ( strlen(lang) == 2) {
309 /* TODO glob the error directory for sub-dirs matching: <tag> '-*' */
310 /* use first result. */
311 debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
312 }
313 #endif
314
315 return false;
316 }
317
318 bool
loadFromFile(const char * path)319 TemplateFile::loadFromFile(const char *path)
320 {
321 int fd;
322 char buf[4096];
323 ssize_t len;
324
325 if (loaded()) // already loaded?
326 return true;
327
328 fd = file_open(path, O_RDONLY | O_TEXT);
329
330 if (fd < 0) {
331 /* with dynamic locale negotiation we may see some failures before a success. */
332 if (!silent && templateCode < TCP_RESET) {
333 int xerrno = errno;
334 debugs(4, DBG_CRITICAL, "ERROR: loading file '" << path << "': " << xstrerr(xerrno));
335 }
336 wasLoaded = false;
337 return wasLoaded;
338 }
339
340 while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
341 if (!parse(buf, len, false)) {
342 debugs(4, DBG_CRITICAL, "ERROR: parsing error in template file: " << path);
343 wasLoaded = false;
344 return wasLoaded;
345 }
346 }
347 parse(buf, 0, true);
348
349 if (len < 0) {
350 int xerrno = errno;
351 debugs(4, DBG_CRITICAL, MYNAME << "ERROR: failed to fully read: '" << path << "': " << xstrerr(xerrno));
352 }
353
354 file_close(fd);
355
356 wasLoaded = true;
357 return wasLoaded;
358 }
359
strHdrAcptLangGetItem(const String & hdr,char * lang,int langLen,size_t & pos)360 bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
361 {
362 while (pos < hdr.size()) {
363
364 /* skip any initial whitespace. */
365 while (pos < hdr.size() && xisspace(hdr[pos]))
366 ++pos;
367
368 /*
369 * Header value format:
370 * - sequence of whitespace delimited tags
371 * - each tag may suffix with ';'.* which we can ignore.
372 * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
373 * with preference given to an exact match.
374 */
375 bool invalid_byte = false;
376 char *dt = lang;
377 while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (lang + (langLen -1)) ) {
378 if (!invalid_byte) {
379 #if USE_HTTP_VIOLATIONS
380 // if accepting violations we may as well accept some broken browsers
381 // which may send us the right code, wrong ISO formatting.
382 if (hdr[pos] == '_')
383 *dt = '-';
384 else
385 #endif
386 *dt = xtolower(hdr[pos]);
387 // valid codes only contain A-Z, hyphen (-) and *
388 if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
389 invalid_byte = true;
390 else
391 ++dt; // move to next destination byte.
392 }
393 ++pos;
394 }
395 *dt = '\0'; // nul-terminated the filename content string before system use.
396
397 // if we terminated the tag on garbage or ';' we need to skip to the next ',' or end of header.
398 while (pos < hdr.size() && hdr[pos] != ',')
399 ++pos;
400
401 if (pos < hdr.size() && hdr[pos] == ',')
402 ++pos;
403
404 debugs(4, 9, "STATE: lang=" << lang << ", pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
405
406 /* if we found anything we might use, try it. */
407 if (*lang != '\0' && !invalid_byte)
408 return true;
409 }
410 return false;
411 }
412
413 bool
loadFor(const HttpRequest * request)414 TemplateFile::loadFor(const HttpRequest *request)
415 {
416 String hdr;
417
418 #if USE_ERR_LOCALES
419 if (loaded()) // already loaded?
420 return true;
421
422 if (!request || !request->header.getList(Http::HdrType::ACCEPT_LANGUAGE, &hdr) )
423 return false;
424
425 char lang[256];
426 size_t pos = 0; // current parsing position in header string
427
428 debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
429
430 while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) {
431
432 /* wildcard uses the configured default language */
433 if (lang[0] == '*' && lang[1] == '\0') {
434 debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default.");
435 return false;
436 }
437
438 debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template");
439
440 if (tryLoadTemplate(lang)) {
441 /* store the language we found for the Content-Language reply header */
442 errLanguage = lang;
443 break;
444 } else if (Config.errorLogMissingLanguages) {
445 debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang);
446 }
447 }
448 #endif
449
450 return loaded();
451 }
452
453 /// \ingroup ErrorPageInternal
454 static ErrorDynamicPageInfo *
errorDynamicPageInfoCreate(int id,const char * page_name)455 errorDynamicPageInfoCreate(int id, const char *page_name)
456 {
457 ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo;
458 info->id = id;
459 info->page_name = xstrdup(page_name);
460 info->page_redirect = static_cast<Http::StatusCode>(atoi(page_name));
461
462 /* WARNING on redirection status:
463 * 2xx are permitted, but not documented officially.
464 * - might be useful for serving static files (PAC etc) in special cases
465 * 3xx require a URL suitable for Location: header.
466 * - the current design does not allow for a Location: URI as well as a local file template
467 * although this possibility is explicitly permitted in the specs.
468 * 4xx-5xx require a local file template.
469 * - sending Location: on these codes with no body is invalid by the specs.
470 * - current result is Squid crashing or XSS problems as dynamic deny_info load random disk files.
471 * - a future redesign of the file loading may result in loading remote objects sent inline as local body.
472 */
473 if (info->page_redirect == Http::scNone)
474 ; // special case okay.
475 else if (info->page_redirect < 200 || info->page_redirect > 599) {
476 // out of range
477 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
478 self_destruct();
479 } else if ( /* >= 200 && */ info->page_redirect < 300 && strchr(&(page_name[4]), ':')) {
480 // 2xx require a local template file
481 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
482 self_destruct();
483 } else if (info->page_redirect >= 300 && info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
484 // 3xx require an absolute URL
485 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a URL on '" << page_name << "'");
486 self_destruct();
487 } else if (info->page_redirect >= 400 /* && <= 599 */ && strchr(&(page_name[4]), ':')) {
488 // 4xx/5xx require a local template file
489 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
490 self_destruct();
491 }
492 // else okay.
493
494 return info;
495 }
496
497 /// \ingroup ErrorPageInternal
498 static void
errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)499 errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
500 {
501 assert(info);
502 safe_free(info->page_name);
503 delete info;
504 }
505
506 /// \ingroup ErrorPageInternal
507 static int
errorPageId(const char * page_name)508 errorPageId(const char *page_name)
509 {
510 for (int i = 0; i < ERR_MAX; ++i) {
511 if (strcmp(err_type_str[i], page_name) == 0)
512 return i;
513 }
514
515 for (size_t j = 0; j < ErrorDynamicPages.size(); ++j) {
516 if (strcmp(ErrorDynamicPages[j]->page_name, page_name) == 0)
517 return j + ERR_MAX;
518 }
519
520 return ERR_NONE;
521 }
522
523 err_type
errorReservePageId(const char * page_name)524 errorReservePageId(const char *page_name)
525 {
526 ErrorDynamicPageInfo *info;
527 int id = errorPageId(page_name);
528
529 if (id == ERR_NONE) {
530 info = errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.size(), page_name);
531 ErrorDynamicPages.push_back(info);
532 id = info->id;
533 }
534
535 return (err_type)id;
536 }
537
538 /// \ingroup ErrorPageInternal
539 const char *
errorPageName(int pageId)540 errorPageName(int pageId)
541 {
542 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
543 return err_type_str[pageId];
544
545 if (pageId >= ERR_MAX && pageId - ERR_MAX < (ssize_t)ErrorDynamicPages.size())
546 return ErrorDynamicPages[pageId - ERR_MAX]->page_name;
547
548 return "ERR_UNKNOWN"; /* should not happen */
549 }
550
551 ErrorState *
NewForwarding(err_type type,HttpRequest * request)552 ErrorState::NewForwarding(err_type type, HttpRequest *request)
553 {
554 assert(request);
555 const Http::StatusCode status = request->flags.needValidation ?
556 Http::scGatewayTimeout : Http::scServiceUnavailable;
557 return new ErrorState(type, status, request);
558 }
559
ErrorState(err_type t,Http::StatusCode status,HttpRequest * req)560 ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req) :
561 type(t),
562 page_id(t),
563 err_language(NULL),
564 httpStatus(status),
565 #if USE_AUTH
566 auth_user_request (NULL),
567 #endif
568 request(NULL),
569 url(NULL),
570 xerrno(0),
571 port(0),
572 dnsError(),
573 ttl(0),
574 src_addr(),
575 redirect_url(NULL),
576 callback(NULL),
577 callback_data(NULL),
578 request_hdrs(NULL),
579 err_msg(NULL),
580 #if USE_OPENSSL
581 detail(NULL),
582 #endif
583 detailCode(ERR_DETAIL_NONE)
584 {
585 memset(&ftp, 0, sizeof(ftp));
586
587 if (page_id >= ERR_MAX && ErrorDynamicPages[page_id - ERR_MAX]->page_redirect != Http::scNone)
588 httpStatus = ErrorDynamicPages[page_id - ERR_MAX]->page_redirect;
589
590 if (req != NULL) {
591 request = req;
592 HTTPMSGLOCK(request);
593 src_addr = req->client_addr;
594 }
595 }
596
597 void
errorAppendEntry(StoreEntry * entry,ErrorState * err)598 errorAppendEntry(StoreEntry * entry, ErrorState * err)
599 {
600 assert(entry->mem_obj != NULL);
601 assert (entry->isEmpty());
602 debugs(4, 4, "Creating an error page for entry " << entry <<
603 " with errorstate " << err <<
604 " page id " << err->page_id);
605
606 if (entry->store_status != STORE_PENDING) {
607 debugs(4, 2, "Skipping error page due to store_status: " << entry->store_status);
608 /*
609 * If the entry is not STORE_PENDING, then no clients
610 * care about it, and we don't need to generate an
611 * error message
612 */
613 assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
614 assert(entry->mem_obj->nclients == 0);
615 delete err;
616 return;
617 }
618
619 if (err->page_id == TCP_RESET) {
620 if (err->request) {
621 debugs(4, 2, "RSTing this reply");
622 err->request->flags.resetTcp = true;
623 }
624 }
625
626 entry->storeErrorResponse(err->BuildHttpReply());
627 delete err;
628 }
629
630 void
errorSend(const Comm::ConnectionPointer & conn,ErrorState * err)631 errorSend(const Comm::ConnectionPointer &conn, ErrorState * err)
632 {
633 HttpReply *rep;
634 debugs(4, 3, HERE << conn << ", err=" << err);
635 assert(Comm::IsConnOpen(conn));
636
637 rep = err->BuildHttpReply();
638
639 MemBuf *mb = rep->pack();
640 AsyncCall::Pointer call = commCbCall(78, 5, "errorSendComplete",
641 CommIoCbPtrFun(&errorSendComplete, err));
642 Comm::Write(conn, mb, call);
643 delete mb;
644
645 delete rep;
646 }
647
648 /**
649 \ingroup ErrorPageAPI
650 *
651 * Called by commHandleWrite() after data has been written
652 * to the client socket.
653 *
654 \note If there is a callback, the callback is responsible for
655 * closing the FD, otherwise we do it ourselves.
656 */
657 static void
errorSendComplete(const Comm::ConnectionPointer & conn,char *,size_t size,Comm::Flag errflag,int,void * data)658 errorSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int, void *data)
659 {
660 ErrorState *err = static_cast<ErrorState *>(data);
661 debugs(4, 3, HERE << conn << ", size=" << size);
662
663 if (errflag != Comm::ERR_CLOSING) {
664 if (err->callback) {
665 debugs(4, 3, "errorSendComplete: callback");
666 err->callback(conn->fd, err->callback_data, size);
667 } else {
668 debugs(4, 3, "errorSendComplete: comm_close");
669 conn->close();
670 }
671 }
672
673 delete err;
674 }
675
~ErrorState()676 ErrorState::~ErrorState()
677 {
678 HTTPMSGUNLOCK(request);
679 safe_free(redirect_url);
680 safe_free(url);
681 safe_free(request_hdrs);
682 wordlistDestroy(&ftp.server_msg);
683 safe_free(ftp.request);
684 safe_free(ftp.reply);
685 #if USE_AUTH
686 auth_user_request = NULL;
687 #endif
688 safe_free(err_msg);
689 #if USE_ERR_LOCALES
690 if (err_language != Config.errorDefaultLanguage)
691 #endif
692 safe_free(err_language);
693 #if USE_OPENSSL
694 delete detail;
695 #endif
696 }
697
698 int
Dump(MemBuf * mb)699 ErrorState::Dump(MemBuf * mb)
700 {
701 MemBuf str;
702 char ntoabuf[MAX_IPSTRLEN];
703
704 str.reset();
705 /* email subject line */
706 str.appendf("CacheErrorInfo - %s", errorPageName(type));
707 mb->appendf("?subject=%s", rfc1738_escape_part(str.buf));
708 str.reset();
709 /* email body */
710 str.appendf("CacheHost: %s\r\n", getMyHostname());
711 /* - Err Msgs */
712 str.appendf("ErrPage: %s\r\n", errorPageName(type));
713
714 if (xerrno) {
715 str.appendf("Err: (%d) %s\r\n", xerrno, strerror(xerrno));
716 } else {
717 str.append("Err: [none]\r\n", 13);
718 }
719 #if USE_AUTH
720 if (auth_user_request.getRaw() && auth_user_request->denyMessage())
721 str.appendf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
722 #endif
723 if (dnsError.size() > 0)
724 str.appendf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
725
726 /* - TimeStamp */
727 str.appendf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
728
729 /* - IP stuff */
730 str.appendf("ClientIP: %s\r\n", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
731
732 if (request && request->hier.host[0] != '\0') {
733 str.appendf("ServerIP: %s\r\n", request->hier.host);
734 }
735
736 str.append("\r\n", 2);
737 /* - HTTP stuff */
738 str.append("HTTP Request:\r\n", 15);
739 if (request) {
740 str.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
741 SQUIDSBUFPRINT(request->method.image()),
742 SQUIDSBUFPRINT(request->url.path()),
743 AnyP::ProtocolType_str[request->http_ver.protocol],
744 request->http_ver.major, request->http_ver.minor);
745 request->header.packInto(&str);
746 }
747
748 str.append("\r\n", 2);
749 /* - FTP stuff */
750
751 if (ftp.request) {
752 str.appendf("FTP Request: %s\r\n", ftp.request);
753 str.appendf("FTP Reply: %s\r\n", (ftp.reply? ftp.reply:"[none]"));
754 str.append("FTP Msg: ", 9);
755 wordlistCat(ftp.server_msg, &str);
756 str.append("\r\n", 2);
757 }
758
759 str.append("\r\n", 2);
760 mb->appendf("&body=%s", rfc1738_escape_part(str.buf));
761 str.clean();
762 return 0;
763 }
764
765 /// \ingroup ErrorPageInternal
766 #define CVT_BUF_SZ 512
767
768 const char *
Convert(char token,bool building_deny_info_url,bool allowRecursion)769 ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion)
770 {
771 static MemBuf mb;
772 const char *p = NULL; /* takes priority over mb if set */
773 int do_quote = 1;
774 int no_urlescape = 0; /* if true then item is NOT to be further URL-encoded */
775 char ntoabuf[MAX_IPSTRLEN];
776
777 mb.reset();
778
779 switch (token) {
780
781 case 'a':
782 #if USE_AUTH
783 if (request && request->auth_user_request != NULL)
784 p = request->auth_user_request->username();
785 if (!p)
786 #endif
787 p = "-";
788 break;
789
790 case 'b':
791 mb.appendf("%u", getMyPort());
792 break;
793
794 case 'B':
795 if (building_deny_info_url) break;
796 if (request) {
797 const SBuf &tmp = Ftp::UrlWith2f(request);
798 mb.append(tmp.rawContent(), tmp.length());
799 } else
800 p = "[no URL]";
801 break;
802
803 case 'c':
804 if (building_deny_info_url) break;
805 p = errorPageName(type);
806 break;
807
808 case 'D':
809 if (!allowRecursion)
810 p = "%D"; // if recursion is not allowed, do not convert
811 #if USE_OPENSSL
812 // currently only SSL error details implemented
813 else if (detail) {
814 detail->useRequest(request);
815 const String &errDetail = detail->toString();
816 if (errDetail.size() > 0) {
817 MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false);
818 mb.append(detail_mb->content(), detail_mb->contentSize());
819 delete detail_mb;
820 do_quote = 0;
821 }
822 }
823 #endif
824 if (!mb.contentSize())
825 mb.append("[No Error Detail]", 17);
826 break;
827
828 case 'e':
829 mb.appendf("%d", xerrno);
830 break;
831
832 case 'E':
833 if (xerrno)
834 mb.appendf("(%d) %s", xerrno, strerror(xerrno));
835 else
836 mb.append("[No Error]", 10);
837 break;
838
839 case 'f':
840 if (building_deny_info_url) break;
841 /* FTP REQUEST LINE */
842 if (ftp.request)
843 p = ftp.request;
844 else
845 p = "nothing";
846 break;
847
848 case 'F':
849 if (building_deny_info_url) break;
850 /* FTP REPLY LINE */
851 if (ftp.reply)
852 p = ftp.reply;
853 else
854 p = "nothing";
855 break;
856
857 case 'g':
858 if (building_deny_info_url) break;
859 /* FTP SERVER RESPONSE */
860 if (ftp.listing) {
861 mb.append(ftp.listing->content(), ftp.listing->contentSize());
862 do_quote = 0;
863 } else if (ftp.server_msg) {
864 wordlistCat(ftp.server_msg, &mb);
865 }
866 break;
867
868 case 'h':
869 mb.appendf("%s", getMyHostname());
870 break;
871
872 case 'H':
873 if (request) {
874 if (request->hier.host[0] != '\0') // if non-empty string.
875 p = request->hier.host;
876 else
877 p = request->url.host();
878 } else if (!building_deny_info_url)
879 p = "[unknown host]";
880 break;
881
882 case 'i':
883 mb.appendf("%s", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
884 break;
885
886 case 'I':
887 if (request && request->hier.tcpServer != NULL)
888 p = request->hier.tcpServer->remote.toStr(ntoabuf,MAX_IPSTRLEN);
889 else if (!building_deny_info_url)
890 p = "[unknown]";
891 break;
892
893 case 'l':
894 if (building_deny_info_url) break;
895 mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
896 do_quote = 0;
897 break;
898
899 case 'L':
900 if (building_deny_info_url) break;
901 if (Config.errHtmlText) {
902 mb.appendf("%s", Config.errHtmlText);
903 do_quote = 0;
904 } else
905 p = "[not available]";
906 break;
907
908 case 'm':
909 if (building_deny_info_url) break;
910 #if USE_AUTH
911 if (auth_user_request.getRaw())
912 p = auth_user_request->denyMessage("[not available]");
913 else
914 p = "[not available]";
915 #else
916 p = "-";
917 #endif
918 break;
919
920 case 'M':
921 if (request) {
922 const SBuf &m = request->method.image();
923 mb.append(m.rawContent(), m.length());
924 } else if (!building_deny_info_url)
925 p = "[unknown method]";
926 break;
927
928 case 'O':
929 if (!building_deny_info_url)
930 do_quote = 0;
931 case 'o':
932 p = request ? request->extacl_message.termedBuf() : external_acl_message;
933 if (!p && !building_deny_info_url)
934 p = "[not available]";
935 break;
936
937 case 'p':
938 if (request) {
939 mb.appendf("%u", request->url.port());
940 } else if (!building_deny_info_url) {
941 p = "[unknown port]";
942 }
943 break;
944
945 case 'P':
946 if (request) {
947 const SBuf &m = request->url.getScheme().image();
948 mb.append(m.rawContent(), m.length());
949 } else if (!building_deny_info_url) {
950 p = "[unknown protocol]";
951 }
952 break;
953
954 case 'R':
955 if (building_deny_info_url) {
956 if (request != NULL) {
957 const SBuf &tmp = request->url.path();
958 mb.append(tmp.rawContent(), tmp.length());
959 no_urlescape = 1;
960 } else
961 p = "[no request]";
962 break;
963 }
964 if (request != NULL) {
965 mb.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
966 SQUIDSBUFPRINT(request->method.image()),
967 SQUIDSBUFPRINT(request->url.path()),
968 AnyP::ProtocolType_str[request->http_ver.protocol],
969 request->http_ver.major, request->http_ver.minor);
970 request->header.packInto(&mb, true); //hide authorization data
971 } else if (request_hdrs) {
972 p = request_hdrs;
973 } else {
974 p = "[no request]";
975 }
976 break;
977
978 case 's':
979 /* for backward compat we make %s show the full URL. Drop this in some future release. */
980 if (building_deny_info_url) {
981 if (request) {
982 const SBuf &tmp = request->effectiveRequestUri();
983 mb.append(tmp.rawContent(), tmp.length());
984 } else
985 p = url;
986 debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
987 } else
988 p = visible_appname_string;
989 break;
990
991 case 'S':
992 if (building_deny_info_url) {
993 p = visible_appname_string;
994 break;
995 }
996 /* signature may contain %-escapes, recursion */
997 if (page_id != ERR_SQUID_SIGNATURE) {
998 const int saved_id = page_id;
999 page_id = ERR_SQUID_SIGNATURE;
1000 MemBuf *sign_mb = BuildContent();
1001 mb.append(sign_mb->content(), sign_mb->contentSize());
1002 sign_mb->clean();
1003 delete sign_mb;
1004 page_id = saved_id;
1005 do_quote = 0;
1006 } else {
1007 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
1008 p = "[%S]";
1009 }
1010 break;
1011
1012 case 't':
1013 mb.appendf("%s", Time::FormatHttpd(squid_curtime));
1014 break;
1015
1016 case 'T':
1017 mb.appendf("%s", mkrfc1123(squid_curtime));
1018 break;
1019
1020 case 'U':
1021 /* Using the fake-https version of absolute-URI so error pages see https:// */
1022 /* even when the url-path cannot be shown as more than '*' */
1023 if (request)
1024 p = urlCanonicalFakeHttps(request);
1025 else if (url)
1026 p = url;
1027 else if (!building_deny_info_url)
1028 p = "[no URL]";
1029 break;
1030
1031 case 'u':
1032 if (request) {
1033 const SBuf &tmp = request->effectiveRequestUri();
1034 mb.append(tmp.rawContent(), tmp.length());
1035 } else if (url)
1036 p = url;
1037 else if (!building_deny_info_url)
1038 p = "[no URL]";
1039 break;
1040
1041 case 'w':
1042 if (Config.adminEmail)
1043 mb.appendf("%s", Config.adminEmail);
1044 else if (!building_deny_info_url)
1045 p = "[unknown]";
1046 break;
1047
1048 case 'W':
1049 if (building_deny_info_url) break;
1050 if (Config.adminEmail && Config.onoff.emailErrData)
1051 Dump(&mb);
1052 no_urlescape = 1;
1053 break;
1054
1055 case 'x':
1056 #if USE_OPENSSL
1057 if (detail)
1058 mb.appendf("%s", detail->errorName());
1059 else
1060 #endif
1061 if (!building_deny_info_url)
1062 p = "[Unknown Error Code]";
1063 break;
1064
1065 case 'z':
1066 if (building_deny_info_url) break;
1067 if (dnsError.size() > 0)
1068 p = dnsError.termedBuf();
1069 else if (ftp.cwd_msg)
1070 p = ftp.cwd_msg;
1071 else
1072 p = "[unknown]";
1073 break;
1074
1075 case 'Z':
1076 if (building_deny_info_url) break;
1077 if (err_msg)
1078 p = err_msg;
1079 else
1080 p = "[unknown]";
1081 break;
1082
1083 case '%':
1084 p = "%";
1085 break;
1086
1087 default:
1088 mb.appendf("%%%c", token);
1089 do_quote = 0;
1090 break;
1091 }
1092
1093 if (!p)
1094 p = mb.buf; /* do not use mb after this assignment! */
1095
1096 assert(p);
1097
1098 debugs(4, 3, "errorConvert: %%" << token << " --> '" << p << "'" );
1099
1100 if (do_quote)
1101 p = html_quote(p);
1102
1103 if (building_deny_info_url && !no_urlescape)
1104 p = rfc1738_escape_part(p);
1105
1106 return p;
1107 }
1108
1109 void
DenyInfoLocation(const char * name,HttpRequest *,MemBuf & result)1110 ErrorState::DenyInfoLocation(const char *name, HttpRequest *, MemBuf &result)
1111 {
1112 char const *m = name;
1113 char const *p = m;
1114 char const *t;
1115
1116 if (m[0] == '3')
1117 m += 4; // skip "3xx:"
1118
1119 while ((p = strchr(m, '%'))) {
1120 result.append(m, p - m); /* copy */
1121 t = Convert(*++p, true, true); /* convert */
1122 result.appendf("%s", t); /* copy */
1123 m = p + 1; /* advance */
1124 }
1125
1126 if (*m)
1127 result.appendf("%s", m); /* copy tail */
1128
1129 assert((size_t)result.contentSize() == strlen(result.content()));
1130 }
1131
1132 HttpReply *
BuildHttpReply()1133 ErrorState::BuildHttpReply()
1134 {
1135 HttpReply *rep = new HttpReply;
1136 const char *name = errorPageName(page_id);
1137 /* no LMT for error pages; error pages expire immediately */
1138
1139 if (name[0] == '3' || (name[0] != '2' && name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
1140 /* Redirection */
1141 Http::StatusCode status = Http::scFound;
1142 // Use configured 3xx reply status if set.
1143 if (name[0] == '3')
1144 status = httpStatus;
1145 else {
1146 // Use 307 for HTTP/1.1 non-GET/HEAD requests.
1147 if (request != NULL && request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
1148 status = Http::scTemporaryRedirect;
1149 }
1150
1151 rep->setHeaders(status, NULL, "text/html;charset=utf-8", 0, 0, -1);
1152
1153 if (request) {
1154 MemBuf redirect_location;
1155 redirect_location.init();
1156 DenyInfoLocation(name, request, redirect_location);
1157 httpHeaderPutStrf(&rep->header, Http::HdrType::LOCATION, "%s", redirect_location.content() );
1158 }
1159
1160 httpHeaderPutStrf(&rep->header, Http::HdrType::X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
1161 } else {
1162 MemBuf *content = BuildContent();
1163 rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", content->contentSize(), 0, -1);
1164 /*
1165 * include some information for downstream caches. Implicit
1166 * replaceable content. This isn't quite sufficient. xerrno is not
1167 * necessarily meaningful to another system, so we really should
1168 * expand it. Additionally, we should identify ourselves. Someone
1169 * might want to know. Someone _will_ want to know OTOH, the first
1170 * X-CACHE-MISS entry should tell us who.
1171 */
1172 httpHeaderPutStrf(&rep->header, Http::HdrType::X_SQUID_ERROR, "%s %d", name, xerrno);
1173
1174 #if USE_ERR_LOCALES
1175 /*
1176 * If error page auto-negotiate is enabled in any way, send the Vary.
1177 * RFC 2616 section 13.6 and 14.44 says MAY and SHOULD do this.
1178 * We have even better reasons though:
1179 * see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
1180 */
1181 if (!Config.errorDirectory) {
1182 /* We 'negotiated' this ONLY from the Accept-Language. */
1183 rep->header.delById(Http::HdrType::VARY);
1184 rep->header.putStr(Http::HdrType::VARY, "Accept-Language");
1185 }
1186
1187 /* add the Content-Language header according to RFC section 14.12 */
1188 if (err_language) {
1189 rep->header.putStr(Http::HdrType::CONTENT_LANGUAGE, err_language);
1190 } else
1191 #endif /* USE_ERROR_LOCALES */
1192 {
1193 /* default templates are in English */
1194 /* language is known unless error_directory override used */
1195 if (!Config.errorDirectory)
1196 rep->header.putStr(Http::HdrType::CONTENT_LANGUAGE, "en");
1197 }
1198
1199 rep->body.setMb(content);
1200 /* do not memBufClean() or delete the content, it was absorbed by httpBody */
1201 }
1202
1203 // Make sure error codes get back to the client side for logging and
1204 // error tracking.
1205 if (request) {
1206 int edc = ERR_DETAIL_NONE; // error detail code
1207 #if USE_OPENSSL
1208 if (detail)
1209 edc = detail->errorNo();
1210 else
1211 #endif
1212 if (detailCode)
1213 edc = detailCode;
1214 else
1215 edc = xerrno;
1216 request->detailError(type, edc);
1217 }
1218
1219 return rep;
1220 }
1221
1222 MemBuf *
BuildContent()1223 ErrorState::BuildContent()
1224 {
1225 const char *m = NULL;
1226
1227 assert(page_id > ERR_NONE && page_id < error_page_count);
1228
1229 #if USE_ERR_LOCALES
1230 ErrorPageFile *localeTmpl = NULL;
1231
1232 /** error_directory option in squid.conf overrides translations.
1233 * Custom errors are always found either in error_directory or the templates directory.
1234 * Otherwise locate the Accept-Language header
1235 */
1236 if (!Config.errorDirectory && page_id < ERR_MAX) {
1237 if (err_language && err_language != Config.errorDefaultLanguage)
1238 safe_free(err_language);
1239
1240 localeTmpl = new ErrorPageFile(err_type_str[page_id], static_cast<err_type>(page_id));
1241 if (localeTmpl->loadFor(request)) {
1242 m = localeTmpl->text();
1243 assert(localeTmpl->language());
1244 err_language = xstrdup(localeTmpl->language());
1245 }
1246 }
1247 #endif /* USE_ERR_LOCALES */
1248
1249 /** \par
1250 * If client-specific error templates are not enabled or available.
1251 * fall back to the old style squid.conf settings.
1252 */
1253 if (!m) {
1254 m = error_text[page_id];
1255 #if USE_ERR_LOCALES
1256 if (!Config.errorDirectory)
1257 err_language = Config.errorDefaultLanguage;
1258 #endif
1259 debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
1260 }
1261
1262 MemBuf *result = ConvertText(m, true);
1263 #if USE_ERR_LOCALES
1264 if (localeTmpl)
1265 delete localeTmpl;
1266 #endif
1267 return result;
1268 }
1269
ConvertText(const char * text,bool allowRecursion)1270 MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)
1271 {
1272 MemBuf *content = new MemBuf;
1273 const char *p;
1274 const char *m = text;
1275 assert(m);
1276 content->init();
1277
1278 while ((p = strchr(m, '%'))) {
1279 content->append(m, p - m); /* copy */
1280 const char *t = Convert(*++p, false, allowRecursion); /* convert */
1281 content->appendf("%s", t); /* copy */
1282 m = p + 1; /* advance */
1283 }
1284
1285 if (*m)
1286 content->appendf("%s", m); /* copy tail */
1287
1288 content->terminate();
1289
1290 assert((size_t)content->contentSize() == strlen(content->content()));
1291
1292 return content;
1293 }
1294
1295