]> git.ipfire.org Git - thirdparty/squid.git/blame - src/errorpage.cc
typo
[thirdparty/squid.git] / src / errorpage.cc
CommitLineData
4a9a952e 1
30a4f2a8 2/*
1cfdbcf0 3 * $Id: errorpage.cc,v 1.145 1999/01/11 22:54:19 wessels Exp $
30a4f2a8 4 *
5 * DEBUG: section 4 Error Generation
6 * AUTHOR: Duane Wessels
7 *
42c04c16 8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
e25c139f 9 * ----------------------------------------------------------
30a4f2a8 10 *
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
e25c139f 13 * National Laboratory for Applied Network Research and funded by the
14 * National Science Foundation. Squid is Copyrighted (C) 1998 by
15 * Duane Wessels and the University of California San Diego. Please
16 * see the COPYRIGHT file for full details. Squid incorporates
17 * software developed and/or copyrighted by other sources. Please see
18 * the CREDITS file for full details.
30a4f2a8 19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
cbdec147 32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 33 *
30a4f2a8 34 */
1e74c110 35
fe40a877 36/*
4e3f29eb 37 * Abstract: These routines are used to generate error messages to be
38 * sent to clients. The error type is used to select between
39 * the various message formats. (formats are stored in the
40 * Config.errorDirectory)
fe40a877 41 */
42
44a47c6e 43#include "squid.h"
1e74c110 44
02922e76 45
46/* local types */
47
48typedef struct {
49 int id;
50 char *page_name;
51} ErrorDynamicPageInfo;
52
53/* local constant and vars */
54
1cfdbcf0 55static const char *const proxy_auth_challenge_fmt = "Basic realm=\"%s\"";
56
1d803566 57/*
58 * note: hard coded error messages are not appended with %S automagically
59 * to give you more control on the format
60 */
2ac76861 61static const struct {
1afe05c5 62 int type; /* and page_id */
2ac76861 63 const char *text;
64} error_hard_text[] = {
1afe05c5 65
2ac76861 66 {
67 ERR_SQUID_SIGNATURE,
68 "\n<br clear=\"all\">\n"
69 "<hr noshade size=1>\n"
5d85cb62 70 "Generated %T by %h (<a href=\"http://squid.nlanr.net/Squid/\">%s</a>)\n"
e32b11fc 71 "</BODY></HTML>\n"
1d803566 72 }
73};
02922e76 74
75static Stack ErrorDynamicPages;
76
77/* local prototypes */
78
2ac76861 79static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
02922e76 80static char **error_text = NULL;
81static int error_page_count = 0;
9b312a19 82
02922e76 83static char *errorTryLoadText(const char *page_name, const char *dir);
84static char *errorLoadText(const char *page_name);
1d803566 85static const char *errorFindHardText(err_type type);
c68e9c6b 86static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
87static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
1d803566 88static MemBuf errorBuildContent(ErrorState * err);
fe40a877 89static const char *errorConvert(char token, ErrorState * err);
9b312a19 90static CWCB errorSendComplete;
1e74c110 91
fe40a877 92/*
93 * Function: errorInitialize
94 *
1d803566 95 * Abstract: This function finds the error messages formats, and stores
fe40a877 96 * them in error_text[];
97 *
98 * Global effects:
99 * error_text[] - is modified
100 */
b8d8561b 101void
0673c0ba 102errorInitialize(void)
fa966b74 103{
728da2ee 104 err_type i;
1d803566 105 const char *text;
02922e76 106 error_page_count = ERR_MAX + ErrorDynamicPages.count;
1afe05c5 107 error_text = xcalloc(error_page_count, sizeof(char *));
728da2ee 108 for (i = ERR_NONE, i++; i < error_page_count; i++) {
1d803566 109 safe_free(error_text[i]);
110 /* hard-coded ? */
111 if ((text = errorFindHardText(i)))
112 error_text[i] = xstrdup(text);
c68e9c6b 113 else if (i < ERR_MAX) {
1afe05c5 114 /* precompiled ? */
02922e76 115 error_text[i] = errorLoadText(err_type_str[i]);
c68e9c6b 116 } else {
117 /* dynamic */
1afe05c5 118 ErrorDynamicPageInfo *info = ErrorDynamicPages.items[i - ERR_MAX];
02922e76 119 assert(info && info->id == i && info->page_name);
120 error_text[i] = errorLoadText(info->page_name);
121 }
1d803566 122 assert(error_text[i]);
123 }
124}
125
c68e9c6b 126void
127errorClean(void)
128{
129 if (error_text) {
130 int i;
131 for (i = ERR_NONE + 1; i < error_page_count; i++)
132 safe_free(error_text[i]);
133 safe_free(error_text);
134 }
135 while (ErrorDynamicPages.count)
136 errorDynamicPageInfoDestroy(stackPop(&ErrorDynamicPages));
137 error_page_count = 0;
138}
139
1d803566 140static const char *
141errorFindHardText(err_type type)
142{
143 int i;
144 for (i = 0; i < error_hard_text_count; i++)
145 if (error_hard_text[i].type == type)
146 return error_hard_text[i].text;
147 return NULL;
148}
149
150
151static char *
02922e76 152errorLoadText(const char *page_name)
1d803566 153{
154 /* test configured location */
02922e76 155 char *text = errorTryLoadText(page_name, Config.errorDirectory);
1d803566 156 /* test default location if failed */
157 if (!text && strcmp(Config.errorDirectory, DEFAULT_SQUID_ERROR_DIR))
02922e76 158 text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR);
1d803566 159 /* giving up if failed */
160 if (!text)
161 fatal("failed to find or read error text file.");
162 return text;
163}
164
165static char *
02922e76 166errorTryLoadText(const char *page_name, const char *dir)
1d803566 167{
9b312a19 168 int fd;
169 char path[MAXPATHLEN];
170 struct stat sb;
1d803566 171 char *text;
172
137ee196 173 snprintf(path, sizeof(path), "%s/%s", dir, page_name);
1d803566 174 fd = file_open(path, O_RDONLY, NULL, NULL, NULL);
175 if (fd < 0 || fstat(fd, &sb) < 0) {
176 debug(4, 0) ("errorTryLoadText: '%s': %s\n", path, xstrerror());
177 if (fd >= 0)
178 file_close(fd);
179 return NULL;
180 }
d5e36424 181 text = xcalloc(sb.st_size + 2 + 1, 1); /* 2 == space for %S */
1d803566 182 if (read(fd, text, sb.st_size) != sb.st_size) {
183 debug(4, 0) ("errorTryLoadText: failed to fully read: '%s': %s\n",
184 path, xstrerror());
185 xfree(text);
186 text = NULL;
1e74c110 187 }
1d803566 188 file_close(fd);
c68e9c6b 189 if (strstr(text, "%s") == NULL)
190 strcat(text, "%S"); /* add signature */
1d803566 191 return text;
6eb42cae 192}
193
02922e76 194static ErrorDynamicPageInfo *
195errorDynamicPageInfoCreate(int id, const char *page_name)
196{
197 ErrorDynamicPageInfo *info = xcalloc(1, sizeof(ErrorDynamicPageInfo));
198 info->id = id;
199 info->page_name = xstrdup(page_name);
200 return info;
201}
202
203static void
1afe05c5 204errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
02922e76 205{
206 assert(info);
207 xfree(info->page_name);
208 xfree(info);
209}
210
211int
212errorReservePageId(const char *page_name)
213{
1afe05c5 214 ErrorDynamicPageInfo *info =
215 errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.count, page_name);
02922e76 216 stackPush(&ErrorDynamicPages, info);
217 return info->id;
218}
219
c68e9c6b 220static const char *
221errorPageName(int pageId)
53ad48e6 222{
c68e9c6b 223 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
224 return err_type_str[pageId];
225 if (pageId >= ERR_MAX && pageId - ERR_MAX < ErrorDynamicPages.count)
226 return ((ErrorDynamicPageInfo *) ErrorDynamicPages.
227 items[pageId - ERR_MAX])->page_name;
228 return "ERR_UNKNOWN"; /* should not happen */
53ad48e6 229}
230
fe40a877 231/*
232 * Function: errorCon
233 *
234 * Abstract: This function creates a ErrorState object.
235 */
236ErrorState *
728da2ee 237errorCon(err_type type, http_status status)
fe40a877 238{
239 ErrorState *err = xcalloc(1, sizeof(ErrorState));
1afe05c5 240 err->page_id = type; /* has to be reset manually if needed */
fe40a877 241 err->type = type;
242 err->http_status = status;
243 return err;
244}
245
246/*
247 * Function: errorAppendEntry
248 *
249 * Arguments: err - This object is destroyed after use in this function.
250 *
251 * Abstract: This function generates a error page from the info contained
4e3f29eb 252 * by 'err' and then stores the text in the specified store
253 * entry. This function should only be called by ``server
254 * side routines'' which need to communicate errors to the
79e0dc20 255 * client side. It should also be called from client_side.c
256 * because we now support persistent connections, and
257 * cannot assume that we can immediately write to the socket
258 * for an error.
fe40a877 259 */
260void
261errorAppendEntry(StoreEntry * entry, ErrorState * err)
262{
cb69b4c7 263 HttpReply *rep;
cb69b4c7 264 MemObject *mem = entry->mem_obj;
901e234d 265 /*
266 * Kostas sez PUT "success" replies might not be STORE_PENDING?
267 */
ef849d44 268 /* assert(entry->store_status == STORE_PENDING); */
b50179a6 269 assert(mem != NULL);
4e3f29eb 270 assert(mem->inmem_hi == 0);
429cf70b 271 storeBuffer(entry);
cb69b4c7 272 rep = errorBuildReply(err);
1cfdbcf0 273 /* Add authentication header */
274 switch (err->http_status) {
275 case HTTP_PROXY_AUTHENTICATION_REQUIRED:
276 /* Proxy authorisation needed */
277 httpHeaderPutStrf(&rep->header, HDR_PROXY_AUTHENTICATE,
278 proxy_auth_challenge_fmt, Config.proxyAuthRealm);
279 break;
280 case HTTP_UNAUTHORIZED:
281 /* WWW Authorisation needed */
282 httpHeaderPutStrf(&rep->header, HDR_WWW_AUTHENTICATE,
283 proxy_auth_challenge_fmt, Config.proxyAuthRealm);
284 break;
285 default:
286 /* Keep GCC happy */
287 break;
288 }
cb69b4c7 289 httpReplySwapOut(rep, entry);
290 httpReplyDestroy(rep);
cb69b4c7 291 mem->reply->sline.status = err->http_status;
2fcac026 292 mem->reply->content_length = -1;
429cf70b 293 storeBufferFlush(entry);
00691420 294 storeComplete(entry);
b50179a6 295 storeNegativeCache(entry);
296 storeReleaseRequest(entry);
fe40a877 297 errorStateFree(err);
fe40a877 298}
299
300/*
301 * Function: errorSend
302 *
303 * Arguments: err - This object is destroyed after use in this function.
304 *
305 * Abstract: This function generates a error page from the info contained
306 * by 'err' and then sends it to the client.
4e3f29eb 307 * The callback function errorSendComplete() is called after
308 * the page has been written to the client socket (fd).
309 * errorSendComplete() deallocates 'err'. We need to add
310 * 'err' to the cbdata because comm_write() requires it
311 * for all callback data pointers.
79e0dc20 312 *
313 * Note, normally errorSend() should only be called from
314 * routines in ssl.c and pass.c, where we don't have any
315 * StoreEntry's. In client_side.c we must allocate a StoreEntry
316 * for errors and use errorAppendEntry() to account for
317 * persistent/pipeline connections.
fe40a877 318 */
319void
320errorSend(int fd, ErrorState * err)
321{
cb69b4c7 322 HttpReply *rep;
fe40a877 323 debug(4, 3) ("errorSend: FD %d, err=%p\n", fd, err);
324 assert(fd >= 0);
88aad2e5 325 /*
326 * ugh, this is how we make sure error codes get back to
327 * the client side for logging and error tracking.
328 */
329 if (err->request)
330 err->request->err_type = err->type;
cb69b4c7 331 /* moved in front of errorBuildBuf @?@ */
b515fc11 332 err->flags.flag_cbdata = 1;
db1cd23c 333 cbdataAdd(err, cbdataXfree, 0);
cb69b4c7 334 rep = errorBuildReply(err);
335 comm_write_mbuf(fd, httpReplyPack(rep), errorSendComplete, err);
336 httpReplyDestroy(rep);
fe40a877 337}
338
339/*
340 * Function: errorSendComplete
341 *
4e3f29eb 342 * Abstract: Called by commHandleWrite() after data has been written
343 * to the client socket.
fe40a877 344 *
345 * Note: If there is a callback, the callback is responsible for
346 * closeing the FD, otherwise we do it ourseves.
347 */
348static void
79a15e0a 349errorSendComplete(int fd, char *bufnotused, size_t size, int errflag, void *data)
fe40a877 350{
351 ErrorState *err = data;
352 debug(4, 3) ("errorSendComplete: FD %d, size=%d\n", fd, size);
353 if (errflag != COMM_ERR_CLOSING) {
354 if (err->callback)
355 err->callback(fd, err->callback_data, size);
356 else
357 comm_close(fd);
358 }
359 errorStateFree(err);
fe40a877 360}
361
cb69b4c7 362void
9b312a19 363errorStateFree(ErrorState * err)
6eb42cae 364{
9b312a19 365 requestUnlink(err->request);
366 safe_free(err->redirect_url);
367 safe_free(err->url);
76a5501f 368 safe_free(err->host);
5f3c4e9a 369 safe_free(err->dnsserver_msg);
b5af8569 370 safe_free(err->request_hdrs);
b515fc11 371 if (err->flags.flag_cbdata)
38d04788 372 cbdataFree(err);
bb0929d8 373 else
374 safe_free(err);
1e74c110 375}
8213067d 376
2658f489 377#define CVT_BUF_SZ 512
fe40a877 378
379/*
1d803566 380 * B - URL with FTP %2f hack x
381 * c - Squid error code x
382 * d - seconds elapsed since request received x
fe40a877 383 * e - errno x
384 * E - strerror() x
fe40a877 385 * f - FTP request line x
969c39b9 386 * F - FTP reply line x
7131112f 387 * g - FTP server message x
fe40a877 388 * h - cache hostname x
969c39b9 389 * H - server host name x
fe40a877 390 * i - client IP address x
391 * I - server IP address x
392 * L - HREF link for more info/contact x
393 * M - Request Method x
394 * p - URL port # x
395 * P - Protocol x
b5af8569 396 * R - Full HTTP Request x
1d803566 397 * S - squid signature from ERR_SIGNATURE x
398 * s - caching proxy software with version x
fe40a877 399 * t - local time x
400 * T - UTC x
969c39b9 401 * U - URL without password x
402 * u - URL without password, %2f added to path x
fe40a877 403 * w - cachemgr email address x
404 * z - dns server error message x
405 */
406
407static const char *
9b312a19 408errorConvert(char token, ErrorState * err)
8213067d 409{
2658f489 410 request_t *r = err->request;
137ee196 411 static MemBuf mb = MemBufNULL;
eeb423fb 412 const char *p = NULL; /* takes priority over mb if set */
413
137ee196 414 memBufReset(&mb);
9b312a19 415 switch (token) {
8f872bb6 416 case 'B':
417 p = r ? ftpUrlWith2f(r) : "[no URL]";
418 break;
042461c3 419 case 'e':
137ee196 420 memBufPrintf(&mb, "%d", err->xerrno);
042461c3 421 break;
422 case 'E':
23d92c64 423 if (err->xerrno)
137ee196 424 memBufPrintf(&mb, "(%d) %s", err->xerrno, strerror(err->xerrno));
23d92c64 425 else
137ee196 426 memBufPrintf(&mb, "[No Error]");
03d7b07f 427 break;
fe40a877 428 case 'f':
429 /* FTP REQUEST LINE */
430 if (err->ftp.request)
431 p = err->ftp.request;
432 else
a8af3a35 433 p = "nothing";
fe40a877 434 break;
435 case 'F':
436 /* FTP REPLY LINE */
437 if (err->ftp.request)
438 p = err->ftp.reply;
439 else
a8af3a35 440 p = "nothing";
03d7b07f 441 break;
7131112f 442 case 'g':
443 /* FTP SERVER MESSAGE */
137ee196 444 wordlistCat(err->ftp_server_msg, &mb);
7131112f 445 break;
03d7b07f 446 case 'h':
137ee196 447 memBufPrintf(&mb, "%s", getMyHostname());
f787fb1e 448 break;
fe40a877 449 case 'H':
450 p = r ? r->host : "[unknown host]";
f787fb1e 451 break;
452 case 'i':
137ee196 453 memBufPrintf(&mb, "%s", inet_ntoa(err->src_addr));
f6aa1a5c 454 break;
f787fb1e 455 case 'I':
456 if (err->host) {
137ee196 457 memBufPrintf(&mb, "%s", err->host);
f787fb1e 458 } else
fe40a877 459 p = "[unknown]";
460 break;
461 case 'L':
462 if (Config.errHtmlText) {
137ee196 463 memBufPrintf(&mb, "%s", Config.errHtmlText);
fe40a877 464 } else
465 p = "[not available]";
466 break;
467 case 'M':
468 p = r ? RequestMethodStr[r->method] : "[unkown method]";
469 break;
470 case 'p':
471 if (r) {
137ee196 472 memBufPrintf(&mb, "%d", (int) r->port);
fe40a877 473 } else {
474 p = "[unknown port]";
475 }
476 break;
477 case 'P':
478 p = r ? ProtocolStr[r->protocol] : "[unkown protocol]";
479 break;
b5af8569 480 case 'R':
865094d7 481 if (NULL != r) {
865094d7 482 Packer p;
865094d7 483 memBufPrintf(&mb, "%s %s HTTP/%3.1f\n",
484 RequestMethodStr[r->method],
485 strLen(r->urlpath) ? strBuf(r->urlpath) : "/",
486 (double) r->http_ver);
487 packerToMemInit(&p, &mb);
488 httpHeaderPackInto(&r->header, &p);
489 packerClean(&p);
865094d7 490 } else if (err->request_hdrs) {
491 p = err->request_hdrs;
492 } else {
493 p = "[no request]";
494 }
b5af8569 495 break;
1d803566 496 case 's':
497 p = full_appname_string;
498 break;
499 case 'S':
500 /* signature may contain %-escapes, recursion */
02922e76 501 if (err->page_id != ERR_SQUID_SIGNATURE) {
502 const int saved_id = err->page_id;
137ee196 503 MemBuf sign_mb;
02922e76 504 err->page_id = ERR_SQUID_SIGNATURE;
137ee196 505 sign_mb = errorBuildContent(err);
506 memBufPrintf(&mb, "%s", sign_mb.buf);
507 memBufClean(&sign_mb);
02922e76 508 err->page_id = saved_id;
1d803566 509 } else {
510 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
511 p = "[%S]";
512 }
513 break;
fe40a877 514 case 't':
137ee196 515 memBufPrintf(&mb, "%s", mkhttpdlogtime(&squid_curtime));
f787fb1e 516 break;
f8291f8f 517 case 'T':
137ee196 518 memBufPrintf(&mb, "%s", mkrfc1123(squid_curtime));
f8291f8f 519 break;
fe40a877 520 case 'U':
b5af8569 521 p = r ? urlCanonicalClean(r) : err->url ? err->url : "[no URL]";
b9916917 522 break;
fe40a877 523 case 'w':
137ee196 524 if (Config.adminEmail)
525 memBufPrintf(&mb, "%s", Config.adminEmail);
526 else
fe40a877 527 p = "[unknown]";
528 break;
529 case 'z':
530 if (err->dnsserver_msg)
531 p = err->dnsserver_msg;
b9916917 532 else
fe40a877 533 p = "[unknown]";
b9916917 534 break;
e347f8e5 535 case '%':
536 p = "%";
537 break;
9b312a19 538 default:
539 p = "%UNKNOWN%";
540 break;
541 }
137ee196 542 if (!p)
eeb423fb 543 p = mb.buf; /* do not use mb after this assignment! */
137ee196 544 assert(p);
e102ebda 545 debug(4, 3) ("errorConvert: %%%c --> '%s'\n", token, p);
9b312a19 546 return p;
8213067d 547}
e381a13d 548
cb69b4c7 549/* allocates and initializes an error response */
550HttpReply *
2ac76861 551errorBuildReply(ErrorState * err)
cb69b4c7 552{
cb69b4c7 553 HttpReply *rep = httpReplyCreate();
1d803566 554 MemBuf content = errorBuildContent(err);
cb69b4c7 555 /* no LMT for error pages; error pages expire immediately */
1d803566 556 httpReplySetHeaders(rep, 1.0, err->http_status, NULL, "text/html", content.size, 0, squid_curtime);
fcc62502 557 /*
558 * include some information for downstream caches. Implicit
2246b732 559 * replaceable content. This isn't quite sufficient. xerrno is not
fcc62502 560 * necessarily meaningful to another system, so we really should
561 * expand it. Additionally, we should identify ourselves. Someone
562 * might want to know. Someone _will_ want to know OTOH, the first
563 * X-CACHE-MISS entry should tell us who.
564 */
2246b732 565 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d",
c68e9c6b 566 errorPageName(err->page_id), err->xerrno);
1d21d91d 567 httpBodySet(&rep->body, &content);
568 /* do not memBufClean() the content, it was absorbed by httpBody */
cb69b4c7 569 return rep;
570}
571
1d803566 572static MemBuf
573errorBuildContent(ErrorState * err)
cb69b4c7 574{
1d803566 575 MemBuf content;
1d803566 576 const char *m;
577 const char *p;
cb69b4c7 578 const char *t;
579 assert(err != NULL);
02922e76 580 assert(err->page_id > ERR_NONE && err->page_id < error_page_count);
1d803566 581 memBufDefInit(&content);
02922e76 582 m = error_text[err->page_id];
1d803566 583 assert(m);
cb69b4c7 584 while ((p = strchr(m, '%'))) {
2ac76861 585 memBufAppend(&content, m, p - m); /* copy */
586 t = errorConvert(*++p, err); /* convert */
587 memBufPrintf(&content, "%s", t); /* copy */
588 m = p + 1; /* advance */
cb69b4c7 589 }
1d803566 590 if (*m)
2ac76861 591 memBufPrintf(&content, "%s", m); /* copy tail */
1d803566 592 assert(content.size == strlen(content.buf));
cb69b4c7 593 return content;
594}