]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/errorpage.cc
3 * $Id: errorpage.cc,v 1.177 2002/09/15 06:40:57 robertc Exp $
5 * DEBUG: section 4 Error Generation
6 * AUTHOR: Duane Wessels
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
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.
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.
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
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
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)
51 } ErrorDynamicPageInfo
;
53 /* local constant and vars */
56 * note: hard coded error messages are not appended with %S automagically
57 * to give you more control on the format
60 int type
; /* and page_id */
62 } error_hard_text
[] = {
66 "\n<BR clear=\"all\">\n"
67 "<HR noshade size=\"1px\">\n"
69 "Generated %T by %h (%s)\n"
79 static Stack ErrorDynamicPages
;
81 /* local prototypes */
83 static const int error_hard_text_count
= sizeof(error_hard_text
) / sizeof(*error_hard_text
);
84 static char **error_text
= NULL
;
85 static int error_page_count
= 0;
87 static char *errorTryLoadText(const char *page_name
, const char *dir
);
88 static char *errorLoadText(const char *page_name
);
89 static const char *errorFindHardText(err_type type
);
90 static ErrorDynamicPageInfo
*errorDynamicPageInfoCreate(int id
, const char *page_name
);
91 static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo
* info
);
92 static MemBuf
errorBuildContent(ErrorState
* err
);
93 static int errorDump(ErrorState
* err
, MemBuf
* mb
);
94 static const char *errorConvert(char token
, ErrorState
* err
);
95 static CWCB errorSendComplete
;
98 * Function: errorInitialize
100 * Abstract: This function finds the error messages formats, and stores
101 * them in error_text[];
104 * error_text[] - is modified
107 errorInitialize(void)
111 error_page_count
= ERR_MAX
+ ErrorDynamicPages
.count
;
112 error_text
= xcalloc(error_page_count
, sizeof(char *));
113 for (i
= ERR_NONE
, i
++; i
< error_page_count
; i
++) {
114 safe_free(error_text
[i
]);
116 if ((text
= errorFindHardText(i
)))
117 error_text
[i
] = xstrdup(text
);
118 else if (i
< ERR_MAX
) {
120 error_text
[i
] = errorLoadText(err_type_str
[i
]);
123 ErrorDynamicPageInfo
*info
= ErrorDynamicPages
.items
[i
- ERR_MAX
];
124 assert(info
&& info
->id
== i
&& info
->page_name
);
125 if (strchr(info
->page_name
, ':') == NULL
) {
126 /* Not on redirected errors... */
127 error_text
[i
] = errorLoadText(info
->page_name
);
138 for (i
= ERR_NONE
+ 1; i
< error_page_count
; i
++)
139 safe_free(error_text
[i
]);
140 safe_free(error_text
);
142 while (ErrorDynamicPages
.count
)
143 errorDynamicPageInfoDestroy(stackPop(&ErrorDynamicPages
));
144 error_page_count
= 0;
148 errorFindHardText(err_type type
)
151 for (i
= 0; i
< error_hard_text_count
; i
++)
152 if (error_hard_text
[i
].type
== type
)
153 return error_hard_text
[i
].text
;
159 errorLoadText(const char *page_name
)
161 /* test configured location */
162 char *text
= errorTryLoadText(page_name
, Config
.errorDirectory
);
163 /* test default location if failed */
164 if (!text
&& strcmp(Config
.errorDirectory
, DEFAULT_SQUID_ERROR_DIR
))
165 text
= errorTryLoadText(page_name
, DEFAULT_SQUID_ERROR_DIR
);
166 /* giving up if failed */
168 fatal("failed to find or read error text file.");
173 errorTryLoadText(const char *page_name
, const char *dir
)
176 char path
[MAXPATHLEN
];
180 snprintf(path
, sizeof(path
), "%s/%s", dir
, page_name
);
181 fd
= file_open(path
, O_RDONLY
| O_TEXT
);
182 if (fd
< 0 || fstat(fd
, &sb
) < 0) {
183 debug(4, 0) ("errorTryLoadText: '%s': %s\n", path
, xstrerror());
188 text
= xcalloc(sb
.st_size
+ 2 + 1, 1); /* 2 == space for %S */
189 if (FD_READ_METHOD(fd
, text
, sb
.st_size
) != sb
.st_size
) {
190 debug(4, 0) ("errorTryLoadText: failed to fully read: '%s': %s\n",
196 if (strstr(text
, "%s") == NULL
)
197 strcat(text
, "%S"); /* add signature */
201 static ErrorDynamicPageInfo
*
202 errorDynamicPageInfoCreate(int id
, const char *page_name
)
204 ErrorDynamicPageInfo
*info
= xcalloc(1, sizeof(ErrorDynamicPageInfo
));
206 info
->page_name
= xstrdup(page_name
);
211 errorDynamicPageInfoDestroy(ErrorDynamicPageInfo
* info
)
214 xfree(info
->page_name
);
219 errorPageId(const char *page_name
)
222 for (i
= 0; i
< ERR_MAX
; i
++) {
223 if (strcmp(err_type_str
[i
], page_name
) == 0)
226 for (i
= 0; i
< ErrorDynamicPages
.count
; i
++) {
227 if (strcmp(((ErrorDynamicPageInfo
*) ErrorDynamicPages
.items
[i
])->page_name
, page_name
) == 0)
234 errorReservePageId(const char *page_name
)
236 ErrorDynamicPageInfo
*info
;
237 int id
= errorPageId(page_name
);
238 if (id
== ERR_NONE
) {
239 info
= errorDynamicPageInfoCreate(ERR_MAX
+ ErrorDynamicPages
.count
, page_name
);
240 stackPush(&ErrorDynamicPages
, info
);
247 errorPageName(int pageId
)
249 if (pageId
>= ERR_NONE
&& pageId
< ERR_MAX
) /* common case */
250 return err_type_str
[pageId
];
251 if (pageId
>= ERR_MAX
&& pageId
- ERR_MAX
< ErrorDynamicPages
.count
)
252 return ((ErrorDynamicPageInfo
*) ErrorDynamicPages
.
253 items
[pageId
- ERR_MAX
])->page_name
;
254 return "ERR_UNKNOWN"; /* should not happen */
260 * Abstract: This function creates a ErrorState object.
263 errorCon(err_type type
, http_status status
)
266 err
= cbdataAlloc(ErrorState
);
267 err
->page_id
= type
; /* has to be reset manually if needed */
269 err
->httpStatus
= status
;
274 * Function: errorAppendEntry
276 * Arguments: err - This object is destroyed after use in this function.
278 * Abstract: This function generates a error page from the info contained
279 * by 'err' and then stores the text in the specified store
280 * entry. This function should only be called by ``server
281 * side routines'' which need to communicate errors to the
282 * client side. It should also be called from client_side.c
283 * because we now support persistent connections, and
284 * cannot assume that we can immediately write to the socket
288 errorAppendEntry(StoreEntry
* entry
, ErrorState
* err
)
291 MemObject
*mem
= entry
->mem_obj
;
293 assert(mem
->inmem_hi
== 0);
294 if (entry
->store_status
!= STORE_PENDING
) {
296 * If the entry is not STORE_PENDING, then no clients
297 * care about it, and we don't need to generate an
300 assert(EBIT_TEST(entry
->flags
, ENTRY_ABORTED
));
301 assert(mem
->nclients
== 0);
305 if (err
->page_id
== TCP_RESET
) {
307 debug(4, 2) ("RSTing this reply\n");
308 err
->request
->flags
.reset_tcp
= 1;
311 storeLockObject(entry
);
313 rep
= errorBuildReply(err
);
314 /* Add authentication header */
315 /* TODO: alter errorstate to be accel on|off aware. The 0 on the next line
316 * depends on authenticate behaviour: all schemes to date send no extra data
317 * on 407/401 responses, and do not check the accel state on 401/407 responses
319 authenticateFixHeader(rep
, err
->auth_user_request
, err
->request
, 0, 1);
320 httpReplySwapOut(rep
, entry
);
321 httpReplyAbsorb(mem
->reply
, rep
);
322 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
323 storeBufferFlush(entry
);
324 storeComplete(entry
);
325 storeNegativeCache(entry
);
326 storeReleaseRequest(entry
);
327 storeUnlockObject(entry
);
332 * Function: errorSend
334 * Arguments: err - This object is destroyed after use in this function.
336 * Abstract: This function generates a error page from the info contained
337 * by 'err' and then sends it to the client.
338 * The callback function errorSendComplete() is called after
339 * the page has been written to the client socket (fd).
340 * errorSendComplete() deallocates 'err'. We need to add
341 * 'err' to the cbdata because comm_write() requires it
342 * for all callback data pointers.
344 * Note, normally errorSend() should only be called from
345 * routines in ssl.c and pass.c, where we don't have any
346 * StoreEntry's. In client_side.c we must allocate a StoreEntry
347 * for errors and use errorAppendEntry() to account for
348 * persistent/pipeline connections.
351 errorSend(int fd
, ErrorState
* err
)
354 debug(4, 3) ("errorSend: FD %d, err=%p\n", fd
, err
);
357 * ugh, this is how we make sure error codes get back to
358 * the client side for logging and error tracking.
361 err
->request
->errType
= err
->type
;
362 /* moved in front of errorBuildBuf @?@ */
363 err
->flags
.flag_cbdata
= 1;
364 rep
= errorBuildReply(err
);
365 comm_write_mbuf(fd
, httpReplyPack(rep
), errorSendComplete
, err
);
366 httpReplyDestroy(rep
);
370 * Function: errorSendComplete
372 * Abstract: Called by commHandleWrite() after data has been written
373 * to the client socket.
375 * Note: If there is a callback, the callback is responsible for
376 * closeing the FD, otherwise we do it ourseves.
379 errorSendComplete(int fd
, char *bufnotused
, size_t size
, comm_err_t errflag
, void *data
)
381 ErrorState
*err
= data
;
382 debug(4, 3) ("errorSendComplete: FD %d, size=%ld\n", fd
, (long int) size
);
383 if (errflag
!= COMM_ERR_CLOSING
) {
385 debug(4, 3) ("errorSendComplete: callback\n");
386 err
->callback(fd
, err
->callback_data
, size
);
389 debug(4, 3) ("errorSendComplete: comm_close\n");
396 errorStateFree(ErrorState
* err
)
398 requestUnlink(err
->request
);
399 safe_free(err
->redirect_url
);
401 safe_free(err
->host
);
402 safe_free(err
->dnsserver_msg
);
403 safe_free(err
->request_hdrs
);
404 wordlistDestroy(&err
->ftp
.server_msg
);
405 safe_free(err
->ftp
.request
);
406 safe_free(err
->ftp
.reply
);
407 if (err
->auth_user_request
)
408 authenticateAuthUserRequestUnlock(err
->auth_user_request
);
409 err
->auth_user_request
= NULL
;
414 errorDump(ErrorState
* err
, MemBuf
* mb
)
416 request_t
*r
= err
->request
;
417 MemBuf str
= MemBufNULL
;
418 const char *p
= NULL
; /* takes priority over mb if set */
420 /* email subject line */
421 memBufPrintf(&str
, "CacheErrorInfo - %s", errorPageName(err
->type
));
422 memBufPrintf(mb
, "?subject=%s", rfc1738_escape_part(str
.buf
));
425 memBufPrintf(&str
, "CacheHost: %s\r\n", getMyHostname());
427 memBufPrintf(&str
, "ErrPage: %s\r\n", errorPageName(err
->type
));
429 memBufPrintf(&str
, "Err: (%d) %s\r\n", err
->xerrno
, strerror(err
->xerrno
));
431 memBufPrintf(&str
, "Err: [none]\r\n");
433 if (authenticateAuthUserRequestMessage(err
->auth_user_request
)) {
434 memBufPrintf(&str
, "extAuth ErrMsg: %s\r\n", authenticateAuthUserRequestMessage(err
->auth_user_request
));
436 if (err
->dnsserver_msg
) {
437 memBufPrintf(&str
, "DNS Server ErrMsg: %s\r\n", err
->dnsserver_msg
);
440 memBufPrintf(&str
, "TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime
));
442 memBufPrintf(&str
, "ClientIP: %s\r\n", inet_ntoa(err
->src_addr
));
444 memBufPrintf(&str
, "ServerIP: %s\r\n", err
->host
);
446 memBufPrintf(&str
, "\r\n");
448 memBufPrintf(&str
, "HTTP Request:\r\n");
451 memBufPrintf(&str
, "%s %s HTTP/%d.%d\n",
452 RequestMethodStr
[r
->method
],
453 strLen(r
->urlpath
) ? strBuf(r
->urlpath
) : "/",
454 r
->http_ver
.major
, r
->http_ver
.minor
);
455 packerToMemInit(&p
, &str
);
456 httpHeaderPackInto(&r
->header
, &p
);
458 } else if (err
->request_hdrs
) {
459 p
= err
->request_hdrs
;
463 memBufPrintf(&str
, "\r\n");
465 if (err
->ftp
.request
) {
466 memBufPrintf(&str
, "FTP Request: %s\r\n", err
->ftp
.request
);
467 memBufPrintf(&str
, "FTP Reply: %s\r\n", err
->ftp
.reply
);
468 memBufPrintf(&str
, "FTP Msg: ");
469 wordlistCat(err
->ftp
.server_msg
, &str
);
470 memBufPrintf(&str
, "\r\n");
472 memBufPrintf(&str
, "\r\n");
473 memBufPrintf(mb
, "&body=%s", rfc1738_escape_part(str
.buf
));
478 #define CVT_BUF_SZ 512
481 * B - URL with FTP %2f hack x
482 * c - Squid error code x
483 * d - seconds elapsed since request received x
486 * f - FTP request line x
487 * F - FTP reply line x
488 * g - FTP server message x
489 * h - cache hostname x
490 * H - server host name x
491 * i - client IP address x
492 * I - server IP address x
493 * L - HREF link for more info/contact x
494 * M - Request Method x
495 * m - Error message returned by external Auth. x
498 * R - Full HTTP Request x
499 * S - squid signature from ERR_SIGNATURE x
500 * s - caching proxy software with version x
503 * U - URL without password x
504 * u - URL with password x
505 * w - cachemgr email address x
506 * W - error data (to be included in the mailto links)
507 * z - dns server error message x
511 errorConvert(char token
, ErrorState
* err
)
513 request_t
*r
= err
->request
;
514 static MemBuf mb
= MemBufNULL
;
515 const char *p
= NULL
; /* takes priority over mb if set */
521 p
= r
? ftpUrlWith2f(r
) : "[no URL]";
524 p
= errorPageName(err
->type
);
527 memBufPrintf(&mb
, "%d", err
->xerrno
);
531 memBufPrintf(&mb
, "(%d) %s", err
->xerrno
, strerror(err
->xerrno
));
533 memBufPrintf(&mb
, "[No Error]");
536 /* FTP REQUEST LINE */
537 if (err
->ftp
.request
)
538 p
= err
->ftp
.request
;
544 if (err
->ftp
.request
)
550 /* FTP SERVER MESSAGE */
551 wordlistCat(err
->ftp
.server_msg
, &mb
);
554 memBufPrintf(&mb
, "%s", getMyHostname());
557 p
= r
? r
->host
: "[unknown host]";
560 memBufPrintf(&mb
, "%s", inet_ntoa(err
->src_addr
));
564 memBufPrintf(&mb
, "%s", err
->host
);
569 if (Config
.errHtmlText
) {
570 memBufPrintf(&mb
, "%s", Config
.errHtmlText
);
573 p
= "[not available]";
576 p
= authenticateAuthUserRequestMessage(err
->auth_user_request
) ? authenticateAuthUserRequestMessage(err
->auth_user_request
) : "[not available]";
579 p
= r
? RequestMethodStr
[r
->method
] : "[unkown method]";
583 memBufPrintf(&mb
, "%d", (int) r
->port
);
585 p
= "[unknown port]";
589 p
= r
? ProtocolStr
[r
->protocol
] : "[unkown protocol]";
594 memBufPrintf(&mb
, "%s %s HTTP/%d.%d\n",
595 RequestMethodStr
[r
->method
],
596 strLen(r
->urlpath
) ? strBuf(r
->urlpath
) : "/",
597 r
->http_ver
.major
, r
->http_ver
.minor
);
598 packerToMemInit(&p
, &mb
);
599 httpHeaderPackInto(&r
->header
, &p
);
601 } else if (err
->request_hdrs
) {
602 p
= err
->request_hdrs
;
608 p
= full_appname_string
;
611 /* signature may contain %-escapes, recursion */
612 if (err
->page_id
!= ERR_SQUID_SIGNATURE
) {
613 const int saved_id
= err
->page_id
;
615 err
->page_id
= ERR_SQUID_SIGNATURE
;
616 sign_mb
= errorBuildContent(err
);
617 memBufPrintf(&mb
, "%s", sign_mb
.buf
);
618 memBufClean(&sign_mb
);
619 err
->page_id
= saved_id
;
622 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
627 memBufPrintf(&mb
, "%s", mkhttpdlogtime(&squid_curtime
));
630 memBufPrintf(&mb
, "%s", mkrfc1123(squid_curtime
));
633 p
= r
? urlCanonicalClean(r
) : err
->url
? err
->url
: "[no URL]";
636 p
= r
? urlCanonical(r
) : err
->url
? err
->url
: "[no URL]";
639 if (Config
.adminEmail
)
640 memBufPrintf(&mb
, "%s", Config
.adminEmail
);
645 if (Config
.adminEmail
&& Config
.onoff
.emailErrData
)
649 if (err
->dnsserver_msg
)
650 p
= err
->dnsserver_msg
;
658 memBufPrintf(&mb
, "%%%c", token
);
662 p
= mb
.buf
; /* do not use mb after this assignment! */
664 debug(4, 3) ("errorConvert: %%%c --> '%s'\n", token
, p
);
670 /* allocates and initializes an error response */
672 errorBuildReply(ErrorState
* err
)
674 HttpReply
*rep
= httpReplyCreate();
675 const char *name
= errorPageName(err
->page_id
);
676 http_version_t version
;
677 /* no LMT for error pages; error pages expire immediately */
678 httpBuildVersion(&version
, 1, 0);
679 if (strchr(name
, ':')) {
681 char *quoted_url
= rfc1738_escape_part(errorConvert('u', err
));
682 httpReplySetHeaders(rep
, version
, HTTP_MOVED_TEMPORARILY
, NULL
, "text/html", 0, 0, squid_curtime
);
683 httpHeaderPutStrf(&rep
->header
, HDR_LOCATION
, name
, quoted_url
);
684 httpHeaderPutStrf(&rep
->header
, HDR_X_SQUID_ERROR
, "%d %s\n", err
->httpStatus
, "Access Denied");
686 MemBuf content
= errorBuildContent(err
);
687 httpReplySetHeaders(rep
, version
, err
->httpStatus
, NULL
, "text/html", content
.size
, 0, squid_curtime
);
689 * include some information for downstream caches. Implicit
690 * replaceable content. This isn't quite sufficient. xerrno is not
691 * necessarily meaningful to another system, so we really should
692 * expand it. Additionally, we should identify ourselves. Someone
693 * might want to know. Someone _will_ want to know OTOH, the first
694 * X-CACHE-MISS entry should tell us who.
696 httpHeaderPutStrf(&rep
->header
, HDR_X_SQUID_ERROR
, "%s %d",
698 httpBodySet(&rep
->body
, &content
);
699 /* do not memBufClean() the content, it was absorbed by httpBody */
705 errorBuildContent(ErrorState
* err
)
712 assert(err
->page_id
> ERR_NONE
&& err
->page_id
< error_page_count
);
713 memBufDefInit(&content
);
714 m
= error_text
[err
->page_id
];
716 while ((p
= strchr(m
, '%'))) {
717 memBufAppend(&content
, m
, p
- m
); /* copy */
718 t
= errorConvert(*++p
, err
); /* convert */
719 memBufPrintf(&content
, "%s", t
); /* copy */
720 m
= p
+ 1; /* advance */
723 memBufPrintf(&content
, "%s", m
); /* copy tail */
724 assert(content
.size
== strlen(content
.buf
));