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