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