1 #include "first.h"
2 
3 #include "base.h"
4 #include "fdevent.h"
5 #include "h2.h"
6 #include "http_chunk.h"
7 #include "http_header.h"
8 #include "log.h"
9 
10 #include "plugin.h"
11 
12 #include <sys/types.h>
13 #include "sys-time.h"
14 
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdio.h>
19 
20 typedef struct {
21     const buffer *config_url;
22     const buffer *status_url;
23     const buffer *statistics_url;
24 
25     int sort;
26 } plugin_config;
27 
28 typedef struct {
29 	PLUGIN_DATA;
30 	plugin_config defaults;
31 	plugin_config conf;
32 
33 	double traffic_out;
34 	double requests;
35 
36 	double mod_5s_traffic_out[5];
37 	double mod_5s_requests[5];
38 	size_t mod_5s_ndx;
39 
40 	double rel_traffic_out;
41 	double rel_requests;
42 
43 	double abs_traffic_out;
44 	double abs_requests;
45 
46 	double bytes_written;
47 } plugin_data;
48 
INIT_FUNC(mod_status_init)49 INIT_FUNC(mod_status_init) {
50     return calloc(1, sizeof(plugin_data));
51 }
52 
mod_status_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)53 static void mod_status_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
54     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
55       case 0: /* status.status-url */
56         pconf->status_url = cpv->v.b;
57         break;
58       case 1: /* status.config-url */
59         pconf->config_url = cpv->v.b;
60         break;
61       case 2: /* status.statistics-url */
62         pconf->statistics_url = cpv->v.b;
63         break;
64       case 3: /* status.enable-sort */
65         pconf->sort = (int)cpv->v.u;
66         break;
67       default:/* should not happen */
68         return;
69     }
70 }
71 
mod_status_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)72 static void mod_status_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
73     do {
74         mod_status_merge_config_cpv(pconf, cpv);
75     } while ((++cpv)->k_id != -1);
76 }
77 
mod_status_patch_config(request_st * const r,plugin_data * const p)78 static void mod_status_patch_config(request_st * const r, plugin_data * const p) {
79     p->conf = p->defaults; /* copy small struct instead of memcpy() */
80     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
81     for (int i = 1, used = p->nconfig; i < used; ++i) {
82         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
83             mod_status_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
84     }
85 }
86 
SETDEFAULTS_FUNC(mod_status_set_defaults)87 SETDEFAULTS_FUNC(mod_status_set_defaults) {
88     static const config_plugin_keys_t cpk[] = {
89       { CONST_STR_LEN("status.status-url"),
90         T_CONFIG_STRING,
91         T_CONFIG_SCOPE_CONNECTION }
92      ,{ CONST_STR_LEN("status.config-url"),
93         T_CONFIG_STRING,
94         T_CONFIG_SCOPE_CONNECTION }
95      ,{ CONST_STR_LEN("status.statistics-url"),
96         T_CONFIG_STRING,
97         T_CONFIG_SCOPE_CONNECTION }
98      ,{ CONST_STR_LEN("status.enable-sort"),
99         T_CONFIG_BOOL,
100         T_CONFIG_SCOPE_CONNECTION }
101      ,{ NULL, 0,
102         T_CONFIG_UNSET,
103         T_CONFIG_SCOPE_UNSET }
104     };
105 
106     plugin_data * const p = p_d;
107     if (!config_plugin_values_init(srv, p, cpk, "mod_status"))
108         return HANDLER_ERROR;
109 
110     /* process and validate config directives
111      * (init i to 0 if global context; to 1 to skip empty global context) */
112     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
113         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
114         for (; -1 != cpv->k_id; ++cpv) {
115             switch (cpv->k_id) {
116               case 0: /* status.status-url */
117               case 1: /* status.config-url */
118               case 2: /* status.statistics-url */
119                 if (buffer_is_blank(cpv->v.b))
120                     cpv->v.b = NULL;
121                 break;
122               case 3: /* status.enable-sort */
123                 break;
124               default:/* should not happen */
125                 break;
126             }
127         }
128     }
129 
130     p->defaults.sort = 1;
131 
132     /* initialize p->defaults from global config context */
133     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
134         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
135         if (-1 != cpv->k_id)
136             mod_status_merge_config(&p->defaults, cpv);
137     }
138 
139     return HANDLER_GO_ON;
140 }
141 
142 
143 
144 static void
mod_status_append_state(buffer * const b,request_state_t state)145 mod_status_append_state (buffer * const b, request_state_t state)
146 {
147     const char *s;
148     size_t n;
149     switch (state) {
150       case CON_STATE_CONNECT:
151         s = "connect";    n = sizeof("connect")-1;    break;
152       case CON_STATE_READ:
153         s = "read";       n = sizeof("read")-1;       break;
154       case CON_STATE_READ_POST:
155         s = "readpost";   n = sizeof("readpost")-1;   break;
156       case CON_STATE_WRITE:
157         s = "write";      n = sizeof("write")-1;      break;
158       case CON_STATE_CLOSE:
159         s = "close";      n = sizeof("close")-1;      break;
160       case CON_STATE_ERROR:
161         s = "error";      n = sizeof("error")-1;      break;
162       case CON_STATE_HANDLE_REQUEST:
163         s = "handle-req"; n = sizeof("handle-req")-1; break;
164       case CON_STATE_REQUEST_START:
165         s = "req-start";  n = sizeof("req-start")-1;  break;
166       case CON_STATE_REQUEST_END:
167         s = "req-end";    n = sizeof("req-end")-1;    break;
168       case CON_STATE_RESPONSE_START:
169         s = "resp-start"; n = sizeof("resp-start")-1; break;
170       case CON_STATE_RESPONSE_END:
171         s = "resp-end";   n = sizeof("resp-end")-1;   break;
172       default:
173         s = "(unknown)";  n = sizeof("(unknown)")-1;  break;
174     }
175     buffer_append_string_len(b, s, n);
176 }
177 
178 
179 static const char *
mod_status_get_short_state(request_state_t state)180 mod_status_get_short_state (request_state_t state)
181 {
182     switch (state) {
183       case CON_STATE_CONNECT:        return ".";
184       case CON_STATE_READ:           return "r";
185       case CON_STATE_READ_POST:      return "R";
186       case CON_STATE_WRITE:          return "W";
187       case CON_STATE_CLOSE:          return "C";
188       case CON_STATE_ERROR:          return "E";
189       case CON_STATE_HANDLE_REQUEST: return "h";
190       case CON_STATE_REQUEST_START:  return "q";
191       case CON_STATE_REQUEST_END:    return "Q";
192       case CON_STATE_RESPONSE_START: return "s";
193       case CON_STATE_RESPONSE_END:   return "S";
194       default:                       return "x";
195     }
196 }
197 
198 
mod_status_header_append_sort(buffer * b,plugin_data * p,const char * k,size_t klen)199 static void mod_status_header_append_sort(buffer *b, plugin_data *p, const char* k, size_t klen)
200 {
201     p->conf.sort
202       ? buffer_append_str3(b,
203           CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"),
204           k, klen,
205           CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"))
206       : buffer_append_str3(b,
207           CONST_STR_LEN("<th class=\"status\">"),
208           k, klen,
209           CONST_STR_LEN("</th>\n"));
210 }
211 
mod_status_get_multiplier(buffer * b,double avg,int size)212 static void mod_status_get_multiplier(buffer *b, double avg, int size) {
213     char unit[] = "  ";
214 
215     if (avg > size) { avg /= size; unit[1] = 'k'; }
216     if (avg > size) { avg /= size; unit[1] = 'M'; }
217     if (avg > size) { avg /= size; unit[1] = 'G'; }
218     if (avg > size) { avg /= size; unit[1] = 'T'; }
219     if (avg > size) { avg /= size; unit[1] = 'P'; }
220     if (avg > size) { avg /= size; unit[1] = 'E'; }
221     if (avg > size) { avg /= size; unit[1] = 'Z'; }
222     if (avg > size) { avg /= size; unit[1] = 'Y'; }
223 
224     if (size == 1000) {
225         buffer_append_int(b, (intmax_t)avg);
226     }
227     else { /* (size == 1024) */
228         char buf[32+1];
229         buffer_append_string_len(b, buf, (size_t)
230                                  snprintf(buf, sizeof(buf), "%.2f", avg));
231     }
232     buffer_append_string_len(b, unit, 2);
233 }
234 
mod_status_html_rtable_r(buffer * const b,const request_st * const r,const connection * const con,const unix_time64_t cur_ts)235 static void mod_status_html_rtable_r (buffer * const b, const request_st * const r, const connection * const con, const unix_time64_t cur_ts) {
236     buffer_append_str3(b, CONST_STR_LEN("<tr><td class=\"string\">"),
237                           BUF_PTR_LEN(&con->dst_addr_buf),
238                           CONST_STR_LEN("</td><td class=\"int\">"));
239 
240     if (r->reqbody_length) {
241         buffer_append_int(b, r->reqbody_queue.bytes_in);
242         buffer_append_string_len(b, CONST_STR_LEN("/"));
243         buffer_append_int(b, r->reqbody_length);
244     }
245     else
246         buffer_append_string_len(b, CONST_STR_LEN("0/0"));
247 
248     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
249 
250     buffer_append_int(b, r->write_queue.bytes_out);
251     buffer_append_string_len(b, CONST_STR_LEN("/"));
252     buffer_append_int(b, r->write_queue.bytes_out + chunkqueue_length(&r->write_queue));
253 
254     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
255 
256     if (CON_STATE_READ == r->state && !buffer_is_blank(&r->target_orig)) {
257         buffer_append_string_len(b, CONST_STR_LEN("keep-alive"));
258     }
259     else
260         mod_status_append_state(b, r->state);
261 
262     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
263 
264     buffer_append_int(b, cur_ts - r->start_hp.tv_sec);
265 
266     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
267 
268     if (buffer_is_blank(r->server_name))
269         buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.authority), ENCODING_HTML);
270     else
271         buffer_append_string_encoded(b, BUF_PTR_LEN(r->server_name), ENCODING_HTML);
272 
273     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
274 
275     if (!buffer_is_blank(&r->uri.path))
276         buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.path), ENCODING_HTML);
277 
278     if (!buffer_is_blank(&r->uri.query)) {
279         buffer_append_string_len(b, CONST_STR_LEN("?"));
280         buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.query), ENCODING_HTML);
281     }
282 
283     if (!buffer_is_blank(&r->target_orig)) {
284         buffer_append_string_len(b, CONST_STR_LEN(" ("));
285         buffer_append_string_encoded(b, BUF_PTR_LEN(&r->target_orig), ENCODING_HTML);
286         buffer_append_string_len(b, CONST_STR_LEN(")"));
287     }
288     buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
289 
290     buffer_append_string_encoded(b, BUF_PTR_LEN(&r->physical.path), ENCODING_HTML);
291 
292     buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
293 }
294 
mod_status_html_rtable(request_st * const rq,const server * const srv,const unix_time64_t cur_ts)295 static void mod_status_html_rtable (request_st * const rq, const server * const srv, const unix_time64_t cur_ts) {
296     /* connection table and URLs might be large, so double-buffer to aggregate
297      * before sending to chunkqueue, which might be temporary file
298      * (avoid write() per connection) */
299     buffer * const b = rq->tmp_buf;
300     buffer_clear(b);
301     for (const connection *con = srv->conns; con; con = con->next) {
302         const request_st * const r = &con->request;
303         if (r->http_status <= HTTP_VERSION_1_1) {
304             if (buffer_string_space(b) < 4096) {
305                 http_chunk_append_mem(rq, BUF_PTR_LEN(b));
306                 buffer_clear(b);
307             }
308             mod_status_html_rtable_r(b, r, con, cur_ts);
309         }
310         else {
311             h2con * const h2c = con->h2;
312             for (uint32_t j = 0, rused = h2c->rused; j < rused; ++j) {
313                 if (buffer_string_space(b) < 4096) {
314                     http_chunk_append_mem(rq, BUF_PTR_LEN(b));
315                     buffer_clear(b);
316                 }
317                 mod_status_html_rtable_r(b, h2c->r[j], con, cur_ts);
318             }
319         }
320     }
321     http_chunk_append_mem(rq, BUF_PTR_LEN(b));
322 }
323 
mod_status_handle_server_status_html(server * srv,request_st * const r,plugin_data * p)324 static handler_t mod_status_handle_server_status_html(server *srv, request_st * const r, plugin_data *p) {
325 	buffer * const b = chunkqueue_append_buffer_open(&r->write_queue);
326 	buffer_string_prepare_append(b, 8192-1);/*(status page base HTML is ~5.2k)*/
327 	double avg;
328 	uint32_t j;
329 	unix_time64_t ts;
330 	const unix_time64_t cur_ts = log_epoch_secs;
331 
332 	int days, hours, mins, seconds;
333 
334 	/*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
335 	int cstates[CON_STATE_CLOSE+3];
336 	memset(cstates, 0, sizeof(cstates));
337 
338 	buffer_copy_string_len(b, CONST_STR_LEN(
339 				 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
340 				 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
341 				 "         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
342 				 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
343 				 " <head>\n"
344 				 "  <title>Status</title>\n"
345 
346 				   "  <style type=\"text/css\">\n"
347 				   "    table.status { border: black solid thin; }\n"
348 				   "    td { white-space: nowrap; }\n"
349 				   "    td.int { background-color: #f0f0f0; text-align: right }\n"
350 				   "    td.string { background-color: #f0f0f0; text-align: left }\n"
351 				   "    th.status { background-color: black; color: white; font-weight: bold; }\n"
352 				   "    a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
353 				   "    span.sortarrow { color: white; text-decoration: none; }\n"
354 				   "  </style>\n"));
355 
356 	if (!buffer_is_blank(&r->uri.query) && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("refresh="))) {
357 		/* Note: Refresh is an historical, but non-standard HTTP header
358 		 * References (meta http-equiv="refresh" use is deprecated):
359 		 *   https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
360 		 *   https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
361 		 *   https://www.w3.org/QA/Tips/reback
362 		 */
363 		const long refresh = strtol(r->uri.query.ptr+sizeof("refresh=")-1, NULL, 10);
364 		if (refresh > 0) {
365 			buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
366 			buffer_append_int(b, refresh < 604800 ? refresh : 604800);
367 			buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
368 		}
369 	}
370 
371 	if (p->conf.sort) {
372 		buffer_append_string_len(b, CONST_STR_LEN(
373 					   "<script type=\"text/javascript\">\n"
374 					   "// <!--\n"
375 					   "var sort_column;\n"
376 					   "var prev_span = null;\n"
377 
378 					   "function get_inner_text(el) {\n"
379 					   " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
380 					   "  return el;\n"
381 					   " if(el.innerText)\n"
382 					   "  return el.innerText;\n"
383 					   " else {\n"
384 					   "  var str = \"\";\n"
385 					   "  var cs = el.childNodes;\n"
386 					   "  var l = cs.length;\n"
387 					   "  for (i=0;i<l;i++) {\n"
388 					   "   if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
389 					   "   else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
390 					   "  }\n"
391 					   " }\n"
392 					   " return str;\n"
393 					   "}\n"
394 
395 					   "function sortfn(a,b) {\n"
396 					   " var at = get_inner_text(a.cells[sort_column]);\n"
397 					   " var bt = get_inner_text(b.cells[sort_column]);\n"
398 					   " if (a.cells[sort_column].className == 'int') {\n"
399 					   "  return parseInt(at)-parseInt(bt);\n"
400 					   " } else {\n"
401 					   "  aa = at.toLowerCase();\n"
402 					   "  bb = bt.toLowerCase();\n"
403 					   "  if (aa==bb) return 0;\n"
404 					   "  else if (aa<bb) return -1;\n"
405 					   "  else return 1;\n"
406 					   " }\n"
407 					   "}\n"
408 
409 					   "function resort(lnk) {\n"
410 					   " var span = lnk.childNodes[1];\n"
411 					   " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
412 					   " var rows = new Array();\n"
413 					   " for (j=1;j<table.rows.length;j++)\n"
414 					   "  rows[j-1] = table.rows[j];\n"
415 					   " sort_column = lnk.parentNode.cellIndex;\n"
416 					   " rows.sort(sortfn);\n"
417 
418 					   " if (prev_span != null) prev_span.innerHTML = '';\n"
419 					   " if (span.getAttribute('sortdir')=='down') {\n"
420 					   "  span.innerHTML = '&uarr;';\n"
421 					   "  span.setAttribute('sortdir','up');\n"
422 					   "  rows.reverse();\n"
423 					   " } else {\n"
424 					   "  span.innerHTML = '&darr;';\n"
425 					   "  span.setAttribute('sortdir','down');\n"
426 					   " }\n"
427 					   " for (i=0;i<rows.length;i++)\n"
428 					   "  table.tBodies[0].appendChild(rows[i]);\n"
429 					   " prev_span = span;\n"
430 					   "}\n"
431 					   "// -->\n"
432 					   "</script>\n"));
433 	}
434 
435 	buffer_append_string_len(b, CONST_STR_LEN(
436 				 " </head>\n"
437 				 "<body>\n"));
438 
439 
440 
441 	/* connection listing */
442 	buffer_append_string_len(b,
443 	                      CONST_STR_LEN("<h1>Server-Status"));
444 	if (r->conf.server_tag)
445 		buffer_append_str3(b, CONST_STR_LEN(
446 			      " ("),
447 	                      BUF_PTR_LEN(r->conf.server_tag),
448 	                      CONST_STR_LEN(
449 			      ")"));
450 	buffer_append_string_len(b,
451 	                      CONST_STR_LEN("</h1>"
452 	                                    "<table summary=\"status\" class=\"status\">"
453 	                                    "<tr><td>Hostname</td><td class=\"string\">"));
454 	buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.authority), ENCODING_HTML);
455 	if (!buffer_is_blank(r->server_name) && r->server_name != &r->uri.authority) {
456 		buffer_append_string_len(b, CONST_STR_LEN(" ("));
457 		buffer_append_string_encoded(b, BUF_PTR_LEN(r->server_name), ENCODING_HTML);
458 		buffer_append_string_len(b, CONST_STR_LEN(")"));
459 	}
460 	buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"
461 	                                          "<tr><td>Uptime</td><td class=\"string\">"));
462 
463 	ts = cur_ts - srv->startup_ts;
464 
465 	days = ts / (60 * 60 * 24);
466 	ts %= (60 * 60 * 24);
467 
468 	hours = ts / (60 * 60);
469 	ts %= (60 * 60);
470 
471 	mins = ts / (60);
472 	ts %= (60);
473 
474 	seconds = ts;
475 
476 	if (days) {
477 		buffer_append_int(b, days);
478 		buffer_append_string_len(b, CONST_STR_LEN(" days "));
479 	}
480 
481 	if (hours) {
482 		buffer_append_int(b, hours);
483 		buffer_append_string_len(b, CONST_STR_LEN(" hours "));
484 	}
485 
486 	if (mins) {
487 		buffer_append_int(b, mins);
488 		buffer_append_string_len(b, CONST_STR_LEN(" min "));
489 	}
490 
491 	buffer_append_int(b, seconds);
492 	buffer_append_string_len(b, CONST_STR_LEN(" s"
493 	                                          "</td></tr>\n"
494 	                                          "<tr><td>Started at</td><td class=\"string\">"));
495 
496 	ts = srv->startup_ts;
497 
498 	struct tm tm;
499 	buffer_append_strftime(b, "%F %T", localtime64_r(&ts, &tm));
500 	buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"
501 	                                          "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"
502 	                                          "<tr><td>Requests</td><td class=\"string\">"));
503 	avg = p->abs_requests;
504 	mod_status_get_multiplier(b, avg, 1000);
505 	buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n"
506 	                                          "<tr><td>Traffic</td><td class=\"string\">"));
507 	avg = p->abs_traffic_out;
508 	mod_status_get_multiplier(b, avg, 1024);
509 	buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n"
510 	                                          "<tr><th colspan=\"2\">average (since start)</th></tr>\n"
511 	                                          "<tr><td>Requests</td><td class=\"string\">"));
512 	avg = p->abs_requests / (cur_ts - srv->startup_ts);
513 	mod_status_get_multiplier(b, avg, 1000);
514 	buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"
515 	                                          "<tr><td>Traffic</td><td class=\"string\">"));
516 	avg = p->abs_traffic_out / (cur_ts - srv->startup_ts);
517 	mod_status_get_multiplier(b, avg, 1024);
518 	buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"
519 	                                          "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"));
520 	for (j = 0, avg = 0; j < 5; j++) {
521 		avg += p->mod_5s_requests[j];
522 	}
523 
524 	avg /= 5;
525 
526 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
527 
528 	mod_status_get_multiplier(b, avg, 1000);
529 	buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
530 
531 	for (j = 0, avg = 0; j < 5; j++) {
532 		avg += p->mod_5s_traffic_out[j];
533 	}
534 
535 	avg /= 5;
536 
537 	buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
538 
539 	mod_status_get_multiplier(b, avg, 1024);
540 	buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"
541 	                                          "</table>\n"
542 	                                          "<hr />\n<pre>\n"
543 	                                          "<b>"));
544 	buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
545 	buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n"));
546 
547 	int per_line = 50;
548 	for (const connection *c = srv->conns; c; c = c->next) {
549 		const request_st * const cr = &c->request;
550 		const char *state;
551 
552 		if ((c->h2 && 0 == c->h2->rused)
553 		    || (CON_STATE_READ == cr->state && !buffer_is_blank(&cr->target_orig))) {
554 			state = "k";
555 			++cstates[CON_STATE_CLOSE+2];
556 		} else {
557 			state = mod_status_get_short_state(cr->state);
558 			++cstates[(cr->state <= CON_STATE_CLOSE ? cr->state : CON_STATE_CLOSE+1)];
559 		}
560 
561 		buffer_append_string_len(b, state, 1);
562 
563 		if (0 == --per_line) {
564 			per_line = 50;
565 			buffer_append_string_len(b, CONST_STR_LEN("\n"));
566 		}
567 	}
568 	buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n"
569 	                                          "<tr><td style=\"text-align:right\">"));
570 	buffer_append_int(b, cstates[CON_STATE_CLOSE+2]);
571 	buffer_append_string_len(b, CONST_STR_LEN("<td>&nbsp;&nbsp;k = keep-alive</td></tr>\n"));
572 	for (j = 0; j < CON_STATE_CLOSE+2; ++j) {
573 		/*(skip "unknown" state if there are none; there should not be any unknown)*/
574 		if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue;
575 		buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
576 		buffer_append_int(b, cstates[j]);
577 		buffer_append_str3(b, CONST_STR_LEN("</td><td>&nbsp;&nbsp;"),
578 		                      mod_status_get_short_state(j), 1,
579 		                      CONST_STR_LEN(" = "));
580 		mod_status_append_state(b, j);
581 		buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
582 	}
583 	buffer_append_string_len(b, CONST_STR_LEN(
584 	  "</table>\n"
585 	  "</pre><hr />\n<h2>Connections</h2>\n"
586 	  "<table summary=\"status\" class=\"status\">\n"
587 	  "<tr>"));
588 	mod_status_header_append_sort(b, p, CONST_STR_LEN("Client IP"));
589 	mod_status_header_append_sort(b, p, CONST_STR_LEN("Read"));
590 	mod_status_header_append_sort(b, p, CONST_STR_LEN("Written"));
591 	mod_status_header_append_sort(b, p, CONST_STR_LEN("State"));
592 	mod_status_header_append_sort(b, p, CONST_STR_LEN("Time"));
593 	mod_status_header_append_sort(b, p, CONST_STR_LEN("Host"));
594 	mod_status_header_append_sort(b, p, CONST_STR_LEN("URI"));
595 	mod_status_header_append_sort(b, p, CONST_STR_LEN("File"));
596 	buffer_append_string_len(b, CONST_STR_LEN("</tr>\n"));
597 
598 	chunkqueue_append_buffer_commit(&r->write_queue);
599 	/* connection table might be large, so buffer separately */
600 
601 	mod_status_html_rtable(r, srv, cur_ts);
602 
603 	http_chunk_append_mem(r, CONST_STR_LEN(
604 		      "</table>\n"
605 		      "</body>\n"
606 		      "</html>\n"
607 		      ));
608 
609 	http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
610 
611 	return 0;
612 }
613 
614 
mod_status_handle_server_status_text(server * srv,request_st * const r,plugin_data * p)615 static handler_t mod_status_handle_server_status_text(server *srv, request_st * const r, plugin_data *p) {
616 	buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
617 
618 	/* output total number of requests */
619 	buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: "));
620 	buffer_append_int(b, (intmax_t)p->abs_requests);
621 
622 	buffer_append_string_len(b, CONST_STR_LEN("\nTotal kBytes: "));
623 	buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024));
624 
625 	buffer_append_string_len(b, CONST_STR_LEN("\nUptime: "));
626 	buffer_append_int(b, log_epoch_secs - srv->startup_ts);
627 
628 	buffer_append_string_len(b, CONST_STR_LEN("\nBusyServers: "));
629 	buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
630 
631 	buffer_append_string_len(b, CONST_STR_LEN("\nIdleServers: "));
632 	buffer_append_int(b, srv->lim_conns); /*(could omit)*/
633 
634 	buffer_append_string_len(b, CONST_STR_LEN("\nScoreboard: "));
635 	for (const connection *c = srv->conns; c; c = c->next) {
636 		const request_st * const cr = &c->request;
637 		const char *state =
638 		  ((c->h2 && 0 == c->h2->rused)
639 		   || (CON_STATE_READ == cr->state && !buffer_is_blank(&cr->target_orig)))
640 		    ? "k"
641 		    : mod_status_get_short_state(cr->state);
642 		buffer_append_string_len(b, state, 1);
643 	}
644 	for (uint32_t i = 0; i < srv->lim_conns; ++i) { /*(could omit)*/
645 		buffer_append_string_len(b, CONST_STR_LEN("_"));
646 	}
647 	buffer_append_string_len(b, CONST_STR_LEN("\n"));
648 
649 	chunkqueue_append_buffer_commit(&r->write_queue);
650 
651 	/* set text/plain output */
652 	http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
653 
654 	return 0;
655 }
656 
657 
mod_status_handle_server_status_json(server * srv,request_st * const r,plugin_data * p)658 static handler_t mod_status_handle_server_status_json(server *srv, request_st * const r, plugin_data *p) {
659 	buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
660 	double avg;
661 	uint32_t j;
662 	unsigned int jsonp = 0;
663 
664 	if (buffer_clen(&r->uri.query) >= sizeof("jsonp=")-1
665 	    && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("jsonp="))) {
666 		/* not a full parse of query string for multiple parameters,
667 		* not URL-decoding param and not XML-encoding (XSS protection),
668 		* so simply ensure that json function name isalnum() or '_' */
669 		const char *f = r->uri.query.ptr + sizeof("jsonp=")-1;
670 		int len = 0;
671 		while (light_isalnum(f[len]) || f[len] == '_') ++len;
672 		if (0 != len && light_isalpha(f[0]) && f[len] == '\0') {
673 			buffer_append_str2(b, f, len, CONST_STR_LEN("("));
674 			jsonp = 1;
675 		}
676 	}
677 
678 	buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": "));
679 	buffer_append_int(b, (intmax_t)p->abs_requests);
680 
681 	buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"TrafficTotal\": "));
682 	buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024));
683 
684 	buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"Uptime\": "));
685 	buffer_append_int(b, log_epoch_secs - srv->startup_ts);
686 
687 	buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"BusyServers\": "));
688 	buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
689 
690 	buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"IdleServers\": "));
691 	buffer_append_int(b, srv->lim_conns); /*(could omit)*/
692 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
693 
694 	for (j = 0, avg = 0; j < 5; j++) {
695 		avg += p->mod_5s_requests[j];
696 	}
697 
698 	avg /= 5;
699 
700 	buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":"));
701 	buffer_append_int(b, avg);
702 	buffer_append_string_len(b, CONST_STR_LEN(",\n"));
703 
704 	for (j = 0, avg = 0; j < 5; j++) {
705 		avg += p->mod_5s_traffic_out[j];
706 	}
707 
708 	avg /= 5;
709 
710 	buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":"));
711 	buffer_append_int(b, avg / 1024); /* kbps */
712 	buffer_append_string_len(b, CONST_STR_LEN("\n}"));
713 
714 	if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");"));
715 
716 	chunkqueue_append_buffer_commit(&r->write_queue);
717 
718 	/* set text/plain output */
719 	http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript"));
720 
721 	return 0;
722 }
723 
724 
mod_status_handle_server_statistics(request_st * const r)725 static handler_t mod_status_handle_server_statistics(request_st * const r) {
726 	buffer *b;
727 	size_t i;
728 	array *st = &plugin_stats;
729 
730 	if (0 == st->used) {
731 		/* we have nothing to send */
732 		r->http_status = 204;
733 		r->resp_body_finished = 1;
734 
735 		return HANDLER_FINISHED;
736 	}
737 
738 	b = chunkqueue_append_buffer_open(&r->write_queue);
739 	for (i = 0; i < st->used; i++) {
740 		buffer_append_str2(b, BUF_PTR_LEN(&st->sorted[i]->key),
741 		                      CONST_STR_LEN(": "));
742 		buffer_append_int(b, ((data_integer *)st->sorted[i])->value);
743 		buffer_append_string_len(b, CONST_STR_LEN("\n"));
744 	}
745 	chunkqueue_append_buffer_commit(&r->write_queue);
746 
747 	http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
748 
749 	r->http_status = 200;
750 	r->resp_body_finished = 1;
751 
752 	return HANDLER_FINISHED;
753 }
754 
755 
mod_status_handle_server_status(request_st * const r,plugin_data * const p)756 static handler_t mod_status_handle_server_status(request_st * const r, plugin_data * const p) {
757 	server * const srv = r->con->srv;
758 	if (buffer_is_equal_string(&r->uri.query, CONST_STR_LEN("auto"))) {
759 		mod_status_handle_server_status_text(srv, r, p);
760 	} else if (buffer_clen(&r->uri.query) >= sizeof("json")-1
761 		   && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("json"))) {
762 		mod_status_handle_server_status_json(srv, r, p);
763 	} else {
764 		mod_status_handle_server_status_html(srv, r, p);
765 	}
766 
767 	r->http_status = 200;
768 	r->resp_body_finished = 1;
769 
770 	return HANDLER_FINISHED;
771 }
772 
773 
mod_status_row_append(buffer * b,const char * k,size_t klen,const char * v,size_t vlen)774 static void mod_status_row_append(buffer *b, const char *k, size_t klen, const char *v, size_t vlen)
775 {
776     struct const_iovec iov[] = {
777       { CONST_STR_LEN("   <tr>\n"
778                       "    <td><b>") }
779      ,{ k, klen }
780      ,{ CONST_STR_LEN("</b></td>\n"
781                       "    <td>") }
782      ,{ v, vlen }
783      ,{ CONST_STR_LEN("</td>\n"
784                       "   </tr>\n") }
785     };
786     buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
787 }
788 
mod_status_header_append(buffer * b,const char * k,size_t klen)789 static void mod_status_header_append(buffer *b, const char *k, size_t klen)
790 {
791     buffer_append_str3(b,
792       CONST_STR_LEN("   <tr>\n"
793 	            "    <th colspan=\"2\">"),
794       k, klen,
795       CONST_STR_LEN("</th>\n"
796 	            "   </tr>\n"));
797 }
798 
mod_status_handle_server_config(request_st * const r)799 static handler_t mod_status_handle_server_config(request_st * const r) {
800 	server * const srv = r->con->srv;
801 	buffer * const tb = r->tmp_buf;
802 	buffer_clear(tb);
803 	for (uint32_t i = 0; i < srv->plugins.used; ++i) {
804 		const char *name = ((plugin **)srv->plugins.ptr)[i]->name;
805 		if (i != 0) {
806 			buffer_append_string_len(tb, CONST_STR_LEN("<br />"));
807 		}
808 		buffer_append_string_len(tb, name, strlen(name));
809 	}
810 
811 	buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
812 
813 	/*(could expand the following into a single buffer_append_iovec(),
814 	 * but this routine is not expected to be under high load)*/
815 
816 	buffer_append_string_len(b, CONST_STR_LEN(
817 			   "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
818 			   "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
819 			   "         \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
820 			   "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
821 			   " <head>\n"
822 			   "  <title>Status</title>\n"
823 			   " </head>\n"
824 			   " <body>\n"));
825 
826 	if (r->conf.server_tag)
827 		buffer_append_str3(b, CONST_STR_LEN(
828 			   "  <h1>"),
829 	                   BUF_PTR_LEN(r->conf.server_tag),
830 	                   CONST_STR_LEN(
831 			   "  </h1>\n"));
832 
833 	buffer_append_string_len(b, CONST_STR_LEN(
834 			   "  <table summary=\"status\" border=\"1\">\n"));
835 
836 	mod_status_header_append(b, CONST_STR_LEN("Server-Features"));
837 #ifdef HAVE_PCRE
838 	mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("enabled"));
839 #else
840 	mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("disabled - pcre missing"));
841 #endif
842 	mod_status_header_append(b, CONST_STR_LEN("Network Engine"));
843 
844 	mod_status_row_append(b, CONST_STR_LEN("fd-Event-Handler"),
845                                  srv->srvconf.event_handler,
846                                  strlen(srv->srvconf.event_handler));
847 
848 	mod_status_header_append(b, CONST_STR_LEN("Config-File-Settings"));
849 
850 	mod_status_row_append(b, CONST_STR_LEN("Loaded Modules"), BUF_PTR_LEN(tb));
851 
852 	buffer_append_string_len(b, CONST_STR_LEN(
853 		      "  </table>\n"
854 		      " </body>\n"
855 		      "</html>\n"
856 		      ));
857 
858 	chunkqueue_append_buffer_commit(&r->write_queue);
859 
860 	http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
861 
862 	r->http_status = 200;
863 	r->resp_body_finished = 1;
864 
865 	return HANDLER_FINISHED;
866 }
867 
mod_status_handler(request_st * const r,void * p_d)868 static handler_t mod_status_handler(request_st * const r, void *p_d) {
869 	plugin_data *p = p_d;
870 
871 	if (NULL != r->handler_module) return HANDLER_GO_ON;
872 
873 	mod_status_patch_config(r, p);
874 
875 	if (p->conf.status_url &&
876 	    buffer_is_equal(p->conf.status_url, &r->uri.path)) {
877 		return mod_status_handle_server_status(r, p);
878 	} else if (p->conf.config_url &&
879 	    buffer_is_equal(p->conf.config_url, &r->uri.path)) {
880 		return mod_status_handle_server_config(r);
881 	} else if (p->conf.statistics_url &&
882 	    buffer_is_equal(p->conf.statistics_url, &r->uri.path)) {
883 		return mod_status_handle_server_statistics(r);
884 	}
885 
886 	return HANDLER_GO_ON;
887 }
888 
TRIGGER_FUNC(mod_status_trigger)889 TRIGGER_FUNC(mod_status_trigger) {
890 	plugin_data *p = p_d;
891 
892 	/* check all connections */
893 	for (const connection *c = srv->conns; c; c = c->next)
894 		p->bytes_written += c->bytes_written_cur_second;
895 
896 	/* a sliding average */
897 	p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
898 	p->mod_5s_requests   [p->mod_5s_ndx] = p->requests;
899 
900 	p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
901 
902 	p->abs_traffic_out += p->bytes_written;
903 	p->rel_traffic_out += p->bytes_written;
904 
905 	p->bytes_written = 0;
906 
907 	/* reset storage - second */
908 	p->traffic_out = 0;
909 	p->requests    = 0;
910 
911 	return HANDLER_GO_ON;
912 }
913 
REQUESTDONE_FUNC(mod_status_account)914 REQUESTDONE_FUNC(mod_status_account) {
915 	plugin_data *p = p_d;
916 
917 	p->requests++;
918 	p->rel_requests++;
919 	p->abs_requests++;
920 
921 	p->bytes_written += r->con->bytes_written_cur_second;
922 
923 	return HANDLER_GO_ON;
924 }
925 
926 
927 int mod_status_plugin_init(plugin *p);
mod_status_plugin_init(plugin * p)928 int mod_status_plugin_init(plugin *p) {
929 	p->version     = LIGHTTPD_VERSION_ID;
930 	p->name        = "status";
931 
932 	p->init        = mod_status_init;
933 	p->set_defaults= mod_status_set_defaults;
934 
935 	p->handle_uri_clean    = mod_status_handler;
936 	p->handle_trigger      = mod_status_trigger;
937 	p->handle_request_done = mod_status_account;
938 
939 	return 0;
940 }
941