blob: 23948f70fe8f8c32f5dcfc6f1574eabe6104fd09 [file] [log] [blame]
William Lallemand74c24fb2016-11-21 17:18:36 +01001/*
2 * Functions dedicated to statistics output and the stats socket
3 *
4 * Copyright 2000-2012 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
14#include <ctype.h>
15#include <errno.h>
16#include <fcntl.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <pwd.h>
21#include <grp.h>
22
23#include <sys/socket.h>
24#include <sys/stat.h>
25#include <sys/types.h>
26
27#include <common/cfgparse.h>
28#include <common/compat.h>
29#include <common/config.h>
30#include <common/debug.h>
31#include <common/memory.h>
32#include <common/mini-clist.h>
33#include <common/standard.h>
34#include <common/ticks.h>
35#include <common/time.h>
36#include <common/uri_auth.h>
37#include <common/version.h>
38#include <common/base64.h>
39
40#include <types/applet.h>
William Lallemand9ed62032016-11-21 17:49:11 +010041#include <types/cli.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010042#include <types/global.h>
43#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010044#include <types/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010045
46#include <proto/backend.h>
47#include <proto/channel.h>
48#include <proto/checks.h>
49#include <proto/compression.h>
William Lallemand9ed62032016-11-21 17:49:11 +010050#include <proto/stats.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010051#include <proto/fd.h>
52#include <proto/freq_ctr.h>
53#include <proto/frontend.h>
54#include <proto/log.h>
55#include <proto/pattern.h>
56#include <proto/pipe.h>
57#include <proto/listener.h>
58#include <proto/map.h>
William Lallemand74c24fb2016-11-21 17:18:36 +010059#include <proto/proto_uxst.h>
60#include <proto/proxy.h>
61#include <proto/sample.h>
62#include <proto/session.h>
63#include <proto/stream.h>
64#include <proto/server.h>
65#include <proto/raw_sock.h>
66#include <proto/stream_interface.h>
67#include <proto/task.h>
68
William Lallemand74c24fb2016-11-21 17:18:36 +010069static struct applet cli_applet;
70
71static const char stats_sock_usage_msg[] =
72 "Unknown command. Please enter one of the following commands only :\n"
73 " clear counters : clear max statistics counters (add 'all' for all counters)\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010074 " help : this message\n"
75 " prompt : toggle interactive mode with prompt\n"
76 " quit : disconnect\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010077 " set timeout : change a timeout setting\n"
78 " set maxconn : change a maxconn setting\n"
79 " set rate-limit : change a rate limiting value\n"
80 " disable : put a server or frontend in maintenance mode\n"
81 " enable : re-enable a server or frontend which is in maintenance mode\n"
82 " shutdown : kill a session or a frontend (eg:to release listening ports)\n"
William Lallemand74c24fb2016-11-21 17:18:36 +010083 "";
84
85static const char stats_permission_denied_msg[] =
86 "Permission denied\n"
87 "";
88
89
90static char *dynamic_usage_msg = NULL;
91
92/* List head of cli keywords */
93static struct cli_kw_list cli_keywords = {
94 .list = LIST_HEAD_INIT(cli_keywords.list)
95};
96
97extern const char *stat_status_codes[];
98
99char *cli_gen_usage_msg()
100{
101 struct cli_kw_list *kw_list;
102 struct cli_kw *kw;
103 struct chunk *tmp = get_trash_chunk();
104 struct chunk out;
105
106 free(dynamic_usage_msg);
107 dynamic_usage_msg = NULL;
108
109 if (LIST_ISEMPTY(&cli_keywords.list))
110 return NULL;
111
112 chunk_reset(tmp);
113 chunk_strcat(tmp, stats_sock_usage_msg);
114 list_for_each_entry(kw_list, &cli_keywords.list, list) {
115 kw = &kw_list->kw[0];
116 while (kw->usage) {
117 chunk_appendf(tmp, " %s\n", kw->usage);
118 kw++;
119 }
120 }
121 chunk_init(&out, NULL, 0);
122 chunk_dup(&out, tmp);
123 dynamic_usage_msg = out.str;
124 return dynamic_usage_msg;
125}
126
127struct cli_kw* cli_find_kw(char **args)
128{
129 struct cli_kw_list *kw_list;
130 struct cli_kw *kw;/* current cli_kw */
131 char **tmp_args;
132 const char **tmp_str_kw;
133 int found = 0;
134
135 if (LIST_ISEMPTY(&cli_keywords.list))
136 return NULL;
137
138 list_for_each_entry(kw_list, &cli_keywords.list, list) {
139 kw = &kw_list->kw[0];
140 while (*kw->str_kw) {
141 tmp_args = args;
142 tmp_str_kw = kw->str_kw;
143 while (*tmp_str_kw) {
144 if (strcmp(*tmp_str_kw, *tmp_args) == 0) {
145 found = 1;
146 } else {
147 found = 0;
148 break;
149 }
150 tmp_args++;
151 tmp_str_kw++;
152 }
153 if (found)
154 return (kw);
155 kw++;
156 }
157 }
158 return NULL;
159}
160
161void cli_register_kw(struct cli_kw_list *kw_list)
162{
163 LIST_ADDQ(&cli_keywords.list, &kw_list->list);
164}
165
166
167/* allocate a new stats frontend named <name>, and return it
168 * (or NULL in case of lack of memory).
169 */
170static struct proxy *alloc_stats_fe(const char *name, const char *file, int line)
171{
172 struct proxy *fe;
173
174 fe = calloc(1, sizeof(*fe));
175 if (!fe)
176 return NULL;
177
178 init_new_proxy(fe);
179 fe->next = proxy;
180 proxy = fe;
181 fe->last_change = now.tv_sec;
182 fe->id = strdup("GLOBAL");
183 fe->cap = PR_CAP_FE;
184 fe->maxconn = 10; /* default to 10 concurrent connections */
185 fe->timeout.client = MS_TO_TICKS(10000); /* default timeout of 10 seconds */
186 fe->conf.file = strdup(file);
187 fe->conf.line = line;
188 fe->accept = frontend_accept;
189 fe->default_target = &cli_applet.obj_type;
190
191 /* the stats frontend is the only one able to assign ID #0 */
192 fe->conf.id.key = fe->uuid = 0;
193 eb32_insert(&used_proxy_id, &fe->conf.id);
194 return fe;
195}
196
197/* This function parses a "stats" statement in the "global" section. It returns
198 * -1 if there is any error, otherwise zero. If it returns -1, it will write an
199 * error message into the <err> buffer which will be preallocated. The trailing
200 * '\n' must not be written. The function must be called with <args> pointing to
201 * the first word after "stats".
202 */
203static int stats_parse_global(char **args, int section_type, struct proxy *curpx,
204 struct proxy *defpx, const char *file, int line,
205 char **err)
206{
207 struct bind_conf *bind_conf;
208 struct listener *l;
209
210 if (!strcmp(args[1], "socket")) {
211 int cur_arg;
212
213 if (*args[2] == 0) {
214 memprintf(err, "'%s %s' in global section expects an address or a path to a UNIX socket", args[0], args[1]);
215 return -1;
216 }
217
218 if (!global.stats_fe) {
219 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
220 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
221 return -1;
222 }
223 }
224
225 bind_conf = bind_conf_alloc(&global.stats_fe->conf.bind, file, line, args[2]);
226 bind_conf->level = ACCESS_LVL_OPER; /* default access level */
227
228 if (!str2listener(args[2], global.stats_fe, bind_conf, file, line, err)) {
229 memprintf(err, "parsing [%s:%d] : '%s %s' : %s\n",
230 file, line, args[0], args[1], err && *err ? *err : "error");
231 return -1;
232 }
233
234 cur_arg = 3;
235 while (*args[cur_arg]) {
236 static int bind_dumped;
237 struct bind_kw *kw;
238
239 kw = bind_find_kw(args[cur_arg]);
240 if (kw) {
241 if (!kw->parse) {
242 memprintf(err, "'%s %s' : '%s' option is not implemented in this version (check build options).",
243 args[0], args[1], args[cur_arg]);
244 return -1;
245 }
246
247 if (kw->parse(args, cur_arg, global.stats_fe, bind_conf, err) != 0) {
248 if (err && *err)
249 memprintf(err, "'%s %s' : '%s'", args[0], args[1], *err);
250 else
251 memprintf(err, "'%s %s' : error encountered while processing '%s'",
252 args[0], args[1], args[cur_arg]);
253 return -1;
254 }
255
256 cur_arg += 1 + kw->skip;
257 continue;
258 }
259
260 if (!bind_dumped) {
261 bind_dump_kws(err);
262 indent_msg(err, 4);
263 bind_dumped = 1;
264 }
265
266 memprintf(err, "'%s %s' : unknown keyword '%s'.%s%s",
267 args[0], args[1], args[cur_arg],
268 err && *err ? " Registered keywords :" : "", err && *err ? *err : "");
269 return -1;
270 }
271
272 list_for_each_entry(l, &bind_conf->listeners, by_bind) {
273 l->maxconn = global.stats_fe->maxconn;
274 l->backlog = global.stats_fe->backlog;
275 l->accept = session_accept_fd;
276 l->handler = process_stream;
277 l->default_target = global.stats_fe->default_target;
278 l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
279 l->nice = -64; /* we want to boost priority for local stats */
280 global.maxsock += l->maxconn;
281 }
282 }
283 else if (!strcmp(args[1], "timeout")) {
284 unsigned timeout;
285 const char *res = parse_time_err(args[2], &timeout, TIME_UNIT_MS);
286
287 if (res) {
288 memprintf(err, "'%s %s' : unexpected character '%c'", args[0], args[1], *res);
289 return -1;
290 }
291
292 if (!timeout) {
293 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
294 return -1;
295 }
296 if (!global.stats_fe) {
297 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
298 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
299 return -1;
300 }
301 }
302 global.stats_fe->timeout.client = MS_TO_TICKS(timeout);
303 }
304 else if (!strcmp(args[1], "maxconn")) {
305 int maxconn = atol(args[2]);
306
307 if (maxconn <= 0) {
308 memprintf(err, "'%s %s' expects a positive value", args[0], args[1]);
309 return -1;
310 }
311
312 if (!global.stats_fe) {
313 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
314 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
315 return -1;
316 }
317 }
318 global.stats_fe->maxconn = maxconn;
319 }
320 else if (!strcmp(args[1], "bind-process")) { /* enable the socket only on some processes */
321 int cur_arg = 2;
322 unsigned long set = 0;
323
324 if (!global.stats_fe) {
325 if ((global.stats_fe = alloc_stats_fe("GLOBAL", file, line)) == NULL) {
326 memprintf(err, "'%s %s' : out of memory trying to allocate a frontend", args[0], args[1]);
327 return -1;
328 }
329 }
330
331 while (*args[cur_arg]) {
332 unsigned int low, high;
333
334 if (strcmp(args[cur_arg], "all") == 0) {
335 set = 0;
336 break;
337 }
338 else if (strcmp(args[cur_arg], "odd") == 0) {
339 set |= ~0UL/3UL; /* 0x555....555 */
340 }
341 else if (strcmp(args[cur_arg], "even") == 0) {
342 set |= (~0UL/3UL) << 1; /* 0xAAA...AAA */
343 }
344 else if (isdigit((int)*args[cur_arg])) {
345 char *dash = strchr(args[cur_arg], '-');
346
347 low = high = str2uic(args[cur_arg]);
348 if (dash)
349 high = str2uic(dash + 1);
350
351 if (high < low) {
352 unsigned int swap = low;
353 low = high;
354 high = swap;
355 }
356
357 if (low < 1 || high > LONGBITS) {
358 memprintf(err, "'%s %s' supports process numbers from 1 to %d.\n",
359 args[0], args[1], LONGBITS);
360 return -1;
361 }
362 while (low <= high)
363 set |= 1UL << (low++ - 1);
364 }
365 else {
366 memprintf(err,
367 "'%s %s' expects 'all', 'odd', 'even', or a list of process ranges with numbers from 1 to %d.\n",
368 args[0], args[1], LONGBITS);
369 return -1;
370 }
371 cur_arg++;
372 }
373 global.stats_fe->bind_proc = set;
374 }
375 else {
376 memprintf(err, "'%s' only supports 'socket', 'maxconn', 'bind-process' and 'timeout' (got '%s')", args[0], args[1]);
377 return -1;
378 }
379 return 0;
380}
381
Willy Tarreaude57a572016-11-23 17:01:39 +0100382/* Verifies that the CLI at least has a level at least as high as <level>
383 * (typically ACCESS_LVL_ADMIN). Returns 1 if OK, otherwise 0. In case of
384 * failure, an error message is prepared and the appctx's state is adjusted
385 * to print it so that a return 1 is enough to abort any processing.
386 */
387int cli_has_level(struct appctx *appctx, int level)
388{
389 struct stream_interface *si = appctx->owner;
390 struct stream *s = si_strm(si);
391
392 if (strm_li(s)->bind_conf->level < level) {
393 appctx->ctx.cli.msg = stats_permission_denied_msg;
394 appctx->st0 = STAT_CLI_PRINT;
395 return 0;
396 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100397 return 1;
398}
399
William Lallemand74c24fb2016-11-21 17:18:36 +0100400
401/* Expects to find a frontend named <arg> and returns it, otherwise displays various
402 * adequate error messages and returns NULL. This function also expects the stream
403 * level to be admin.
404 */
405static struct proxy *expect_frontend_admin(struct stream *s, struct stream_interface *si, const char *arg)
406{
407 struct appctx *appctx = __objt_appctx(si->end);
408 struct proxy *px;
409
410 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
411 appctx->ctx.cli.msg = stats_permission_denied_msg;
412 appctx->st0 = STAT_CLI_PRINT;
413 return NULL;
414 }
415
416 if (!*arg) {
417 appctx->ctx.cli.msg = "A frontend name is expected.\n";
418 appctx->st0 = STAT_CLI_PRINT;
419 return NULL;
420 }
421
422 px = proxy_fe_by_name(arg);
423 if (!px) {
424 appctx->ctx.cli.msg = "No such frontend.\n";
425 appctx->st0 = STAT_CLI_PRINT;
426 return NULL;
427 }
428 return px;
429}
430
431/* Expects to find a backend and a server in <arg> under the form <backend>/<server>,
432 * and returns the pointer to the server. Otherwise, display adequate error messages
433 * and returns NULL. This function also expects the stream level to be admin. Note:
434 * the <arg> is modified to remove the '/'.
435 */
William Lallemand222baf22016-11-19 02:00:33 +0100436struct server *expect_server_admin(struct stream *s, struct stream_interface *si, char *arg)
William Lallemand74c24fb2016-11-21 17:18:36 +0100437{
438 struct appctx *appctx = __objt_appctx(si->end);
439 struct proxy *px;
440 struct server *sv;
441 char *line;
442
443 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
444 appctx->ctx.cli.msg = stats_permission_denied_msg;
445 appctx->st0 = STAT_CLI_PRINT;
446 return NULL;
447 }
448
449 /* split "backend/server" and make <line> point to server */
450 for (line = arg; *line; line++)
451 if (*line == '/') {
452 *line++ = '\0';
453 break;
454 }
455
456 if (!*line || !*arg) {
457 appctx->ctx.cli.msg = "Require 'backend/server'.\n";
458 appctx->st0 = STAT_CLI_PRINT;
459 return NULL;
460 }
461
462 if (!get_backend_server(arg, line, &px, &sv)) {
463 appctx->ctx.cli.msg = px ? "No such server.\n" : "No such backend.\n";
464 appctx->st0 = STAT_CLI_PRINT;
465 return NULL;
466 }
467
468 if (px->state == PR_STSTOPPED) {
469 appctx->ctx.cli.msg = "Proxy is disabled.\n";
470 appctx->st0 = STAT_CLI_PRINT;
471 return NULL;
472 }
473
474 return sv;
475}
476
William Lallemand74c24fb2016-11-21 17:18:36 +0100477/* Processes the stats interpreter on the statistics socket. This function is
478 * called from an applet running in a stream interface. The function returns 1
479 * if the request was understood, otherwise zero. It sets appctx->st0 to a value
480 * designating the function which will have to process the request, which can
481 * also be the print function to display the return message set into cli.msg.
482 */
483static int stats_sock_parse_request(struct stream_interface *si, char *line)
484{
485 struct stream *s = si_strm(si);
486 struct appctx *appctx = __objt_appctx(si->end);
487 char *args[MAX_STATS_ARGS + 1];
488 struct cli_kw *kw;
489 int arg;
490 int i, j;
491
492 while (isspace((unsigned char)*line))
493 line++;
494
495 arg = 0;
496 args[arg] = line;
497
498 while (*line && arg < MAX_STATS_ARGS) {
499 if (*line == '\\') {
500 line++;
501 if (*line == '\0')
502 break;
503 }
504 else if (isspace((unsigned char)*line)) {
505 *line++ = '\0';
506
507 while (isspace((unsigned char)*line))
508 line++;
509
510 args[++arg] = line;
511 continue;
512 }
513
514 line++;
515 }
516
517 while (++arg <= MAX_STATS_ARGS)
518 args[arg] = line;
519
520 /* remove \ */
521 arg = 0;
522 while (*args[arg] != '\0') {
523 j = 0;
524 for (i=0; args[arg][i] != '\0'; i++) {
525 if (args[arg][i] == '\\')
526 continue;
527 args[arg][j] = args[arg][i];
528 j++;
529 }
530 args[arg][j] = '\0';
531 arg++;
532 }
533
534 appctx->ctx.stats.scope_str = 0;
535 appctx->ctx.stats.scope_len = 0;
536 appctx->ctx.stats.flags = 0;
537 if ((kw = cli_find_kw(args))) {
538 if (kw->parse) {
539 if (kw->parse(args, appctx, kw->private) == 0 && kw->io_handler) {
540 appctx->st0 = STAT_CLI_O_CUSTOM;
541 appctx->io_handler = kw->io_handler;
542 appctx->io_release = kw->io_release;
543 }
544 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100545 }
546 else if (strcmp(args[0], "clear") == 0) {
547 if (strcmp(args[1], "counters") == 0) {
548 struct proxy *px;
549 struct server *sv;
550 struct listener *li;
551 int clrall = 0;
552
553 if (strcmp(args[2], "all") == 0)
554 clrall = 1;
555
556 /* check permissions */
557 if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER ||
558 (clrall && strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN)) {
559 appctx->ctx.cli.msg = stats_permission_denied_msg;
560 appctx->st0 = STAT_CLI_PRINT;
561 return 1;
562 }
563
564 for (px = proxy; px; px = px->next) {
565 if (clrall) {
566 memset(&px->be_counters, 0, sizeof(px->be_counters));
567 memset(&px->fe_counters, 0, sizeof(px->fe_counters));
568 }
569 else {
570 px->be_counters.conn_max = 0;
571 px->be_counters.p.http.rps_max = 0;
572 px->be_counters.sps_max = 0;
573 px->be_counters.cps_max = 0;
574 px->be_counters.nbpend_max = 0;
575
576 px->fe_counters.conn_max = 0;
577 px->fe_counters.p.http.rps_max = 0;
578 px->fe_counters.sps_max = 0;
579 px->fe_counters.cps_max = 0;
580 px->fe_counters.nbpend_max = 0;
581 }
582
583 for (sv = px->srv; sv; sv = sv->next)
584 if (clrall)
585 memset(&sv->counters, 0, sizeof(sv->counters));
586 else {
587 sv->counters.cur_sess_max = 0;
588 sv->counters.nbpend_max = 0;
589 sv->counters.sps_max = 0;
590 }
591
592 list_for_each_entry(li, &px->conf.listeners, by_fe)
593 if (li->counters) {
594 if (clrall)
595 memset(li->counters, 0, sizeof(*li->counters));
596 else
597 li->counters->conn_max = 0;
598 }
599 }
600
601 global.cps_max = 0;
602 global.sps_max = 0;
603 return 1;
604 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100605 else {
606 /* unknown "clear" argument */
607 return 0;
608 }
609 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100610 else if (strcmp(args[0], "set") == 0) {
William Lallemand6b160942016-11-22 12:34:35 +0100611 if (strcmp(args[1], "timeout") == 0) {
William Lallemand74c24fb2016-11-21 17:18:36 +0100612 if (strcmp(args[2], "cli") == 0) {
613 unsigned timeout;
614 const char *res;
615
616 if (!*args[3]) {
617 appctx->ctx.cli.msg = "Expects an integer value.\n";
618 appctx->st0 = STAT_CLI_PRINT;
619 return 1;
620 }
621
622 res = parse_time_err(args[3], &timeout, TIME_UNIT_S);
623 if (res || timeout < 1) {
624 appctx->ctx.cli.msg = "Invalid timeout value.\n";
625 appctx->st0 = STAT_CLI_PRINT;
626 return 1;
627 }
628
629 s->req.rto = s->res.wto = 1 + MS_TO_TICKS(timeout*1000);
630 task_wakeup(s->task, TASK_WOKEN_MSG); // recompute timeouts
631 return 1;
632 }
633 else {
634 appctx->ctx.cli.msg = "'set timeout' only supports 'cli'.\n";
635 appctx->st0 = STAT_CLI_PRINT;
636 return 1;
637 }
638 }
639 else if (strcmp(args[1], "maxconn") == 0) {
640 if (strcmp(args[2], "frontend") == 0) {
641 struct proxy *px;
642 struct listener *l;
643 int v;
644
645 px = expect_frontend_admin(s, si, args[3]);
646 if (!px)
647 return 1;
648
649 if (!*args[4]) {
650 appctx->ctx.cli.msg = "Integer value expected.\n";
651 appctx->st0 = STAT_CLI_PRINT;
652 return 1;
653 }
654
655 v = atoi(args[4]);
656 if (v < 0) {
657 appctx->ctx.cli.msg = "Value out of range.\n";
658 appctx->st0 = STAT_CLI_PRINT;
659 return 1;
660 }
661
662 /* OK, the value is fine, so we assign it to the proxy and to all of
663 * its listeners. The blocked ones will be dequeued.
664 */
665 px->maxconn = v;
666 list_for_each_entry(l, &px->conf.listeners, by_fe) {
667 l->maxconn = v;
668 if (l->state == LI_FULL)
669 resume_listener(l);
670 }
671
672 if (px->maxconn > px->feconn && !LIST_ISEMPTY(&px->listener_queue))
673 dequeue_all_listeners(&px->listener_queue);
674
675 return 1;
676 }
677 else if (strcmp(args[2], "server") == 0) {
678 struct server *sv;
679 const char *warning;
680
681 sv = expect_server_admin(s, si, args[3]);
682 if (!sv)
683 return 1;
684
685 warning = server_parse_maxconn_change_request(sv, args[4]);
686 if (warning) {
687 appctx->ctx.cli.msg = warning;
688 appctx->st0 = STAT_CLI_PRINT;
689 }
690
691 return 1;
692 }
693 else if (strcmp(args[2], "global") == 0) {
694 int v;
695
696 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
697 appctx->ctx.cli.msg = stats_permission_denied_msg;
698 appctx->st0 = STAT_CLI_PRINT;
699 return 1;
700 }
701
702 if (!*args[3]) {
703 appctx->ctx.cli.msg = "Expects an integer value.\n";
704 appctx->st0 = STAT_CLI_PRINT;
705 return 1;
706 }
707
708 v = atoi(args[3]);
709 if (v > global.hardmaxconn) {
710 appctx->ctx.cli.msg = "Value out of range.\n";
711 appctx->st0 = STAT_CLI_PRINT;
712 return 1;
713 }
714
715 /* check for unlimited values */
716 if (v <= 0)
717 v = global.hardmaxconn;
718
719 global.maxconn = v;
720
721 /* Dequeues all of the listeners waiting for a resource */
722 if (!LIST_ISEMPTY(&global_listener_queue))
723 dequeue_all_listeners(&global_listener_queue);
724
725 return 1;
726 }
727 else {
728 appctx->ctx.cli.msg = "'set maxconn' only supports 'frontend', 'server', and 'global'.\n";
729 appctx->st0 = STAT_CLI_PRINT;
730 return 1;
731 }
732 }
733 else if (strcmp(args[1], "rate-limit") == 0) {
734 if (strcmp(args[2], "connections") == 0) {
735 if (strcmp(args[3], "global") == 0) {
736 int v;
737
738 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
739 appctx->ctx.cli.msg = stats_permission_denied_msg;
740 appctx->st0 = STAT_CLI_PRINT;
741 return 1;
742 }
743
744 if (!*args[4]) {
745 appctx->ctx.cli.msg = "Expects an integer value.\n";
746 appctx->st0 = STAT_CLI_PRINT;
747 return 1;
748 }
749
750 v = atoi(args[4]);
751 if (v < 0) {
752 appctx->ctx.cli.msg = "Value out of range.\n";
753 appctx->st0 = STAT_CLI_PRINT;
754 return 1;
755 }
756
757 global.cps_lim = v;
758
759 /* Dequeues all of the listeners waiting for a resource */
760 if (!LIST_ISEMPTY(&global_listener_queue))
761 dequeue_all_listeners(&global_listener_queue);
762
763 return 1;
764 }
765 else {
766 appctx->ctx.cli.msg = "'set rate-limit connections' only supports 'global'.\n";
767 appctx->st0 = STAT_CLI_PRINT;
768 return 1;
769 }
770 }
771 else if (strcmp(args[2], "sessions") == 0) {
772 if (strcmp(args[3], "global") == 0) {
773 int v;
774
775 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
776 appctx->ctx.cli.msg = stats_permission_denied_msg;
777 appctx->st0 = STAT_CLI_PRINT;
778 return 1;
779 }
780
781 if (!*args[4]) {
782 appctx->ctx.cli.msg = "Expects an integer value.\n";
783 appctx->st0 = STAT_CLI_PRINT;
784 return 1;
785 }
786
787 v = atoi(args[4]);
788 if (v < 0) {
789 appctx->ctx.cli.msg = "Value out of range.\n";
790 appctx->st0 = STAT_CLI_PRINT;
791 return 1;
792 }
793
794 global.sps_lim = v;
795
796 /* Dequeues all of the listeners waiting for a resource */
797 if (!LIST_ISEMPTY(&global_listener_queue))
798 dequeue_all_listeners(&global_listener_queue);
799
800 return 1;
801 }
802 else {
803 appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n";
804 appctx->st0 = STAT_CLI_PRINT;
805 return 1;
806 }
807 }
808#ifdef USE_OPENSSL
809 else if (strcmp(args[2], "ssl-sessions") == 0) {
810 if (strcmp(args[3], "global") == 0) {
811 int v;
812
813 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
814 appctx->ctx.cli.msg = stats_permission_denied_msg;
815 appctx->st0 = STAT_CLI_PRINT;
816 return 1;
817 }
818
819 if (!*args[4]) {
820 appctx->ctx.cli.msg = "Expects an integer value.\n";
821 appctx->st0 = STAT_CLI_PRINT;
822 return 1;
823 }
824
825 v = atoi(args[4]);
826 if (v < 0) {
827 appctx->ctx.cli.msg = "Value out of range.\n";
828 appctx->st0 = STAT_CLI_PRINT;
829 return 1;
830 }
831
832 global.ssl_lim = v;
833
834 /* Dequeues all of the listeners waiting for a resource */
835 if (!LIST_ISEMPTY(&global_listener_queue))
836 dequeue_all_listeners(&global_listener_queue);
837
838 return 1;
839 }
840 else {
841 appctx->ctx.cli.msg = "'set rate-limit ssl-sessions' only supports 'global'.\n";
842 appctx->st0 = STAT_CLI_PRINT;
843 return 1;
844 }
845 }
846#endif
847 else if (strcmp(args[2], "http-compression") == 0) {
848 if (strcmp(args[3], "global") == 0) {
849 int v;
850
851 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
852 appctx->ctx.cli.msg = stats_permission_denied_msg;
853 appctx->st0 = STAT_CLI_PRINT;
854 return 1;
855 }
856
857 if (!*args[4]) {
858 appctx->ctx.cli.msg = "Expects a maximum input byte rate in kB/s.\n";
859 appctx->st0 = STAT_CLI_PRINT;
860 return 1;
861 }
862
863 v = atoi(args[4]);
864 global.comp_rate_lim = v * 1024; /* Kilo to bytes. */
865 }
866 else {
867 appctx->ctx.cli.msg = "'set rate-limit http-compression' only supports 'global'.\n";
868 appctx->st0 = STAT_CLI_PRINT;
869 return 1;
870 }
871 }
872 else {
873 appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n";
874 appctx->st0 = STAT_CLI_PRINT;
875 return 1;
876 }
William Lallemand32af2032016-10-29 18:09:35 +0200877 } else { /* unknown "set" parameter */
William Lallemand74c24fb2016-11-21 17:18:36 +0100878 return 0;
879 }
880 }
881 else if (strcmp(args[0], "enable") == 0) {
882 if (strcmp(args[1], "agent") == 0) {
883 struct server *sv;
884
885 sv = expect_server_admin(s, si, args[2]);
886 if (!sv)
887 return 1;
888
889 if (!(sv->agent.state & CHK_ST_CONFIGURED)) {
890 appctx->ctx.cli.msg = "Agent was not configured on this server, cannot enable.\n";
891 appctx->st0 = STAT_CLI_PRINT;
892 return 1;
893 }
894
895 sv->agent.state |= CHK_ST_ENABLED;
896 return 1;
897 }
898 else if (strcmp(args[1], "health") == 0) {
899 struct server *sv;
900
901 sv = expect_server_admin(s, si, args[2]);
902 if (!sv)
903 return 1;
904
905 if (!(sv->check.state & CHK_ST_CONFIGURED)) {
906 appctx->ctx.cli.msg = "Health checks are not configured on this server, cannot enable.\n";
907 appctx->st0 = STAT_CLI_PRINT;
908 return 1;
909 }
910
911 sv->check.state |= CHK_ST_ENABLED;
912 return 1;
913 }
914 else if (strcmp(args[1], "server") == 0) {
915 struct server *sv;
916
917 sv = expect_server_admin(s, si, args[2]);
918 if (!sv)
919 return 1;
920
921 srv_adm_set_ready(sv);
922 return 1;
923 }
924 else if (strcmp(args[1], "frontend") == 0) {
925 struct proxy *px;
926
927 px = expect_frontend_admin(s, si, args[2]);
928 if (!px)
929 return 1;
930
931 if (px->state == PR_STSTOPPED) {
932 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n";
933 appctx->st0 = STAT_CLI_PRINT;
934 return 1;
935 }
936
937 if (px->state != PR_STPAUSED) {
938 appctx->ctx.cli.msg = "Frontend is already enabled.\n";
939 appctx->st0 = STAT_CLI_PRINT;
940 return 1;
941 }
942
943 if (!resume_proxy(px)) {
944 appctx->ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n";
945 appctx->st0 = STAT_CLI_PRINT;
946 return 1;
947 }
948 return 1;
949 }
950 else { /* unknown "enable" parameter */
951 appctx->ctx.cli.msg = "'enable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
952 appctx->st0 = STAT_CLI_PRINT;
953 return 1;
954 }
955 }
956 else if (strcmp(args[0], "disable") == 0) {
957 if (strcmp(args[1], "agent") == 0) {
958 struct server *sv;
959
960 sv = expect_server_admin(s, si, args[2]);
961 if (!sv)
962 return 1;
963
964 sv->agent.state &= ~CHK_ST_ENABLED;
965 return 1;
966 }
967 else if (strcmp(args[1], "health") == 0) {
968 struct server *sv;
969
970 sv = expect_server_admin(s, si, args[2]);
971 if (!sv)
972 return 1;
973
974 sv->check.state &= ~CHK_ST_ENABLED;
975 return 1;
976 }
977 else if (strcmp(args[1], "server") == 0) {
978 struct server *sv;
979
980 sv = expect_server_admin(s, si, args[2]);
981 if (!sv)
982 return 1;
983
984 srv_adm_set_maint(sv);
985 return 1;
986 }
987 else if (strcmp(args[1], "frontend") == 0) {
988 struct proxy *px;
989
990 px = expect_frontend_admin(s, si, args[2]);
991 if (!px)
992 return 1;
993
994 if (px->state == PR_STSTOPPED) {
995 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n";
996 appctx->st0 = STAT_CLI_PRINT;
997 return 1;
998 }
999
1000 if (px->state == PR_STPAUSED) {
1001 appctx->ctx.cli.msg = "Frontend is already disabled.\n";
1002 appctx->st0 = STAT_CLI_PRINT;
1003 return 1;
1004 }
1005
1006 if (!pause_proxy(px)) {
1007 appctx->ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n";
1008 appctx->st0 = STAT_CLI_PRINT;
1009 return 1;
1010 }
1011 return 1;
1012 }
1013 else { /* unknown "disable" parameter */
1014 appctx->ctx.cli.msg = "'disable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
1015 appctx->st0 = STAT_CLI_PRINT;
1016 return 1;
1017 }
1018 }
1019 else if (strcmp(args[0], "shutdown") == 0) {
1020 if (strcmp(args[1], "frontend") == 0) {
1021 struct proxy *px;
1022
1023 px = expect_frontend_admin(s, si, args[2]);
1024 if (!px)
1025 return 1;
1026
1027 if (px->state == PR_STSTOPPED) {
1028 appctx->ctx.cli.msg = "Frontend was already shut down.\n";
1029 appctx->st0 = STAT_CLI_PRINT;
1030 return 1;
1031 }
1032
1033 Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1034 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1035 send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
1036 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
1037 stop_proxy(px);
1038 return 1;
1039 }
1040 else if (strcmp(args[1], "session") == 0) {
1041 struct stream *sess, *ptr;
1042
1043 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
1044 appctx->ctx.cli.msg = stats_permission_denied_msg;
1045 appctx->st0 = STAT_CLI_PRINT;
1046 return 1;
1047 }
1048
1049 if (!*args[2]) {
1050 appctx->ctx.cli.msg = "Session pointer expected (use 'show sess').\n";
1051 appctx->st0 = STAT_CLI_PRINT;
1052 return 1;
1053 }
1054
1055 ptr = (void *)strtoul(args[2], NULL, 0);
1056
1057 /* first, look for the requested stream in the stream table */
1058 list_for_each_entry(sess, &streams, list) {
1059 if (sess == ptr)
1060 break;
1061 }
1062
1063 /* do we have the stream ? */
1064 if (sess != ptr) {
1065 appctx->ctx.cli.msg = "No such session (use 'show sess').\n";
1066 appctx->st0 = STAT_CLI_PRINT;
1067 return 1;
1068 }
1069
1070 stream_shutdown(sess, SF_ERR_KILLED);
1071 return 1;
1072 }
1073 else if (strcmp(args[1], "sessions") == 0) {
1074 if (strcmp(args[2], "server") == 0) {
1075 struct server *sv;
1076 struct stream *sess, *sess_bck;
1077
1078 sv = expect_server_admin(s, si, args[3]);
1079 if (!sv)
1080 return 1;
1081
1082 /* kill all the stream that are on this server */
1083 list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
1084 if (sess->srv_conn == sv)
1085 stream_shutdown(sess, SF_ERR_KILLED);
1086
1087 return 1;
1088 }
1089 else {
1090 appctx->ctx.cli.msg = "'shutdown sessions' only supports 'server'.\n";
1091 appctx->st0 = STAT_CLI_PRINT;
1092 return 1;
1093 }
1094 }
1095 else { /* unknown "disable" parameter */
1096 appctx->ctx.cli.msg = "'shutdown' only supports 'frontend', 'session' and 'sessions'.\n";
1097 appctx->st0 = STAT_CLI_PRINT;
1098 return 1;
1099 }
1100 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001101 else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */
1102 return 0;
1103 }
1104 return 1;
1105}
1106
1107/* This I/O handler runs as an applet embedded in a stream interface. It is
1108 * used to processes I/O from/to the stats unix socket. The system relies on a
1109 * state machine handling requests and various responses. We read a request,
1110 * then we process it and send the response, and we possibly display a prompt.
1111 * Then we can read again. The state is stored in appctx->st0 and is one of the
1112 * STAT_CLI_* constants. appctx->st1 is used to indicate whether prompt is enabled
1113 * or not.
1114 */
1115static void cli_io_handler(struct appctx *appctx)
1116{
1117 struct stream_interface *si = appctx->owner;
1118 struct channel *req = si_oc(si);
1119 struct channel *res = si_ic(si);
1120 int reql;
1121 int len;
1122
1123 if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
1124 goto out;
1125
1126 while (1) {
1127 if (appctx->st0 == STAT_CLI_INIT) {
1128 /* Stats output not initialized yet */
1129 memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats));
1130 appctx->st0 = STAT_CLI_GETREQ;
1131 }
1132 else if (appctx->st0 == STAT_CLI_END) {
1133 /* Let's close for real now. We just close the request
1134 * side, the conditions below will complete if needed.
1135 */
1136 si_shutw(si);
1137 break;
1138 }
1139 else if (appctx->st0 == STAT_CLI_GETREQ) {
1140 /* ensure we have some output room left in the event we
1141 * would want to return some info right after parsing.
1142 */
1143 if (buffer_almost_full(si_ib(si))) {
1144 si_applet_cant_put(si);
1145 break;
1146 }
1147
1148 reql = bo_getline(si_oc(si), trash.str, trash.size);
1149 if (reql <= 0) { /* closed or EOL not found */
1150 if (reql == 0)
1151 break;
1152 appctx->st0 = STAT_CLI_END;
1153 continue;
1154 }
1155
1156 /* seek for a possible unescaped semi-colon. If we find
1157 * one, we replace it with an LF and skip only this part.
1158 */
1159 for (len = 0; len < reql; len++) {
1160 if (trash.str[len] == '\\') {
1161 len++;
1162 continue;
1163 }
1164 if (trash.str[len] == ';') {
1165 trash.str[len] = '\n';
1166 reql = len + 1;
1167 break;
1168 }
1169 }
1170
1171 /* now it is time to check that we have a full line,
1172 * remove the trailing \n and possibly \r, then cut the
1173 * line.
1174 */
1175 len = reql - 1;
1176 if (trash.str[len] != '\n') {
1177 appctx->st0 = STAT_CLI_END;
1178 continue;
1179 }
1180
1181 if (len && trash.str[len-1] == '\r')
1182 len--;
1183
1184 trash.str[len] = '\0';
1185
1186 appctx->st0 = STAT_CLI_PROMPT;
1187 if (len) {
1188 if (strcmp(trash.str, "quit") == 0) {
1189 appctx->st0 = STAT_CLI_END;
1190 continue;
1191 }
1192 else if (strcmp(trash.str, "prompt") == 0)
1193 appctx->st1 = !appctx->st1;
1194 else if (strcmp(trash.str, "help") == 0 ||
1195 !stats_sock_parse_request(si, trash.str)) {
1196 cli_gen_usage_msg();
1197 if (dynamic_usage_msg)
1198 appctx->ctx.cli.msg = dynamic_usage_msg;
1199 else
1200 appctx->ctx.cli.msg = stats_sock_usage_msg;
1201 appctx->st0 = STAT_CLI_PRINT;
1202 }
1203 /* NB: stats_sock_parse_request() may have put
1204 * another STAT_CLI_O_* into appctx->st0.
1205 */
1206 }
1207 else if (!appctx->st1) {
1208 /* if prompt is disabled, print help on empty lines,
1209 * so that the user at least knows how to enable
1210 * prompt and find help.
1211 */
1212 cli_gen_usage_msg();
1213 if (dynamic_usage_msg)
1214 appctx->ctx.cli.msg = dynamic_usage_msg;
1215 else
1216 appctx->ctx.cli.msg = stats_sock_usage_msg;
1217 appctx->st0 = STAT_CLI_PRINT;
1218 }
1219
1220 /* re-adjust req buffer */
1221 bo_skip(si_oc(si), reql);
1222 req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */
1223 }
1224 else { /* output functions */
1225 switch (appctx->st0) {
1226 case STAT_CLI_PROMPT:
1227 break;
1228 case STAT_CLI_PRINT:
1229 if (bi_putstr(si_ic(si), appctx->ctx.cli.msg) != -1)
1230 appctx->st0 = STAT_CLI_PROMPT;
1231 else
1232 si_applet_cant_put(si);
1233 break;
1234 case STAT_CLI_PRINT_FREE:
1235 if (bi_putstr(si_ic(si), appctx->ctx.cli.err) != -1) {
1236 free(appctx->ctx.cli.err);
1237 appctx->st0 = STAT_CLI_PROMPT;
1238 }
1239 else
1240 si_applet_cant_put(si);
1241 break;
William Lallemand74c24fb2016-11-21 17:18:36 +01001242 case STAT_CLI_O_CUSTOM: /* use custom pointer */
1243 if (appctx->io_handler)
1244 if (appctx->io_handler(appctx)) {
1245 appctx->st0 = STAT_CLI_PROMPT;
1246 if (appctx->io_release) {
1247 appctx->io_release(appctx);
1248 appctx->io_release = NULL;
1249 }
1250 }
1251 break;
1252 default: /* abnormal state */
1253 si->flags |= SI_FL_ERR;
1254 break;
1255 }
1256
1257 /* The post-command prompt is either LF alone or LF + '> ' in interactive mode */
1258 if (appctx->st0 == STAT_CLI_PROMPT) {
1259 if (bi_putstr(si_ic(si), appctx->st1 ? "\n> " : "\n") != -1)
1260 appctx->st0 = STAT_CLI_GETREQ;
1261 else
1262 si_applet_cant_put(si);
1263 }
1264
1265 /* If the output functions are still there, it means they require more room. */
1266 if (appctx->st0 >= STAT_CLI_OUTPUT)
1267 break;
1268
1269 /* Now we close the output if one of the writers did so,
1270 * or if we're not in interactive mode and the request
1271 * buffer is empty. This still allows pipelined requests
1272 * to be sent in non-interactive mode.
1273 */
1274 if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) {
1275 appctx->st0 = STAT_CLI_END;
1276 continue;
1277 }
1278
1279 /* switch state back to GETREQ to read next requests */
1280 appctx->st0 = STAT_CLI_GETREQ;
1281 }
1282 }
1283
1284 if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST)) {
1285 DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
1286 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1287 /* Other side has closed, let's abort if we have no more processing to do
1288 * and nothing more to consume. This is comparable to a broken pipe, so
1289 * we forward the close to the request side so that it flows upstream to
1290 * the client.
1291 */
1292 si_shutw(si);
1293 }
1294
1295 if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && (appctx->st0 < STAT_CLI_OUTPUT)) {
1296 DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n",
1297 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1298 /* We have no more processing to do, and nothing more to send, and
1299 * the client side has closed. So we'll forward this state downstream
1300 * on the response buffer.
1301 */
1302 si_shutr(si);
1303 res->flags |= CF_READ_NULL;
1304 }
1305
1306 out:
1307 DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rqh=%d, rqs=%d, rh=%d, rs=%d\n",
1308 __FUNCTION__, __LINE__,
1309 si->state, req->flags, res->flags, req->buf->i, req->buf->o, res->buf->i, res->buf->o);
1310}
1311
William Lallemand74c24fb2016-11-21 17:18:36 +01001312/* This is called when the stream interface is closed. For instance, upon an
1313 * external abort, we won't call the i/o handler anymore so we may need to
1314 * remove back references to the stream currently being dumped.
1315 */
1316static void cli_release_handler(struct appctx *appctx)
1317{
1318 if (appctx->io_release) {
1319 appctx->io_release(appctx);
1320 appctx->io_release = NULL;
1321 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001322 else if (appctx->st0 == STAT_CLI_PRINT_FREE) {
1323 free(appctx->ctx.cli.err);
1324 appctx->ctx.cli.err = NULL;
1325 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001326}
1327
1328/* This function dumps all environmnent variables to the buffer. It returns 0
1329 * if the output buffer is full and it needs to be called again, otherwise
1330 * non-zero. Dumps only one entry if st2 == STAT_ST_END.
1331 */
Willy Tarreau0a739292016-11-22 20:21:23 +01001332static int cli_io_handler_show_env(struct appctx *appctx)
William Lallemand74c24fb2016-11-21 17:18:36 +01001333{
Willy Tarreau0a739292016-11-22 20:21:23 +01001334 struct stream_interface *si = appctx->owner;
William Lallemand74c24fb2016-11-21 17:18:36 +01001335
1336 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1337 return 1;
1338
1339 chunk_reset(&trash);
1340
1341 /* we have two inner loops here, one for the proxy, the other one for
1342 * the buffer.
1343 */
1344 while (*appctx->ctx.env.var) {
1345 chunk_printf(&trash, "%s\n", *appctx->ctx.env.var);
1346
1347 if (bi_putchk(si_ic(si), &trash) == -1) {
1348 si_applet_cant_put(si);
1349 return 0;
1350 }
1351 if (appctx->st2 == STAT_ST_END)
1352 break;
1353 appctx->ctx.env.var++;
1354 }
1355
1356 /* dump complete */
1357 return 1;
1358}
1359
Willy Tarreau0a739292016-11-22 20:21:23 +01001360/* parse a "show env" CLI request. Returns 0 if it needs to continue, 1 if it
1361 * wants to stop here.
1362 */
1363static int cli_parse_show_env(char **args, struct appctx *appctx, void *private)
1364{
1365 extern char **environ;
1366
1367 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1368 return 1;
1369
1370 appctx->ctx.env.var = environ;
1371 appctx->st2 = STAT_ST_INIT;
1372
1373 if (*args[2]) {
1374 int len = strlen(args[2]);
1375
1376 for (; *appctx->ctx.env.var; appctx->ctx.env.var++) {
1377 if (strncmp(*appctx->ctx.env.var, args[2], len) == 0 &&
1378 (*appctx->ctx.env.var)[len] == '=')
1379 break;
1380 }
1381 if (!*appctx->ctx.env.var) {
1382 appctx->ctx.cli.msg = "Variable not found\n";
1383 appctx->st0 = STAT_CLI_PRINT;
1384 return 1;
1385 }
1386 appctx->st2 = STAT_ST_END;
1387 }
1388 return 0;
1389}
1390
William Lallemand74c24fb2016-11-21 17:18:36 +01001391/* parse the "level" argument on the bind lines */
1392static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
1393{
1394 if (!*args[cur_arg + 1]) {
1395 memprintf(err, "'%s' : missing level", args[cur_arg]);
1396 return ERR_ALERT | ERR_FATAL;
1397 }
1398
1399 if (!strcmp(args[cur_arg+1], "user"))
1400 conf->level = ACCESS_LVL_USER;
1401 else if (!strcmp(args[cur_arg+1], "operator"))
1402 conf->level = ACCESS_LVL_OPER;
1403 else if (!strcmp(args[cur_arg+1], "admin"))
1404 conf->level = ACCESS_LVL_ADMIN;
1405 else {
1406 memprintf(err, "'%s' only supports 'user', 'operator', and 'admin' (got '%s')",
1407 args[cur_arg], args[cur_arg+1]);
1408 return ERR_ALERT | ERR_FATAL;
1409 }
1410
1411 return 0;
1412}
1413
1414static struct applet cli_applet = {
1415 .obj_type = OBJ_TYPE_APPLET,
1416 .name = "<CLI>", /* used for logging */
1417 .fct = cli_io_handler,
1418 .release = cli_release_handler,
1419};
1420
Willy Tarreau0a739292016-11-22 20:21:23 +01001421/* register cli keywords */
1422static struct cli_kw_list cli_kws = {{ },{
1423 { { "show", "env", NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL },
1424 {{},}
1425}};
1426
William Lallemand74c24fb2016-11-21 17:18:36 +01001427static struct cfg_kw_list cfg_kws = {ILH, {
1428 { CFG_GLOBAL, "stats", stats_parse_global },
1429 { 0, NULL, NULL },
1430}};
1431
1432static struct bind_kw_list bind_kws = { "STAT", { }, {
1433 { "level", bind_parse_level, 1 }, /* set the unix socket admin level */
1434 { NULL, NULL, 0 },
1435}};
1436
1437__attribute__((constructor))
1438static void __dumpstats_module_init(void)
1439{
1440 cfg_register_keywords(&cfg_kws);
Willy Tarreau0a739292016-11-22 20:21:23 +01001441 cli_register_kw(&cli_kws);
William Lallemand74c24fb2016-11-21 17:18:36 +01001442 bind_register_keywords(&bind_kws);
1443}
1444
1445/*
1446 * Local variables:
1447 * c-indent-level: 8
1448 * c-basic-offset: 8
1449 * End:
1450 */