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