blob: fad2217f58c6d7733ccb441debbee16a5ce740a5 [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 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100599 else {
600 appctx->ctx.cli.msg = "'set maxconn' only supports 'frontend', 'server', and 'global'.\n";
601 appctx->st0 = STAT_CLI_PRINT;
602 return 1;
603 }
604 }
605 else if (strcmp(args[1], "rate-limit") == 0) {
606 if (strcmp(args[2], "connections") == 0) {
607 if (strcmp(args[3], "global") == 0) {
608 int v;
609
610 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
611 appctx->ctx.cli.msg = stats_permission_denied_msg;
612 appctx->st0 = STAT_CLI_PRINT;
613 return 1;
614 }
615
616 if (!*args[4]) {
617 appctx->ctx.cli.msg = "Expects an integer value.\n";
618 appctx->st0 = STAT_CLI_PRINT;
619 return 1;
620 }
621
622 v = atoi(args[4]);
623 if (v < 0) {
624 appctx->ctx.cli.msg = "Value out of range.\n";
625 appctx->st0 = STAT_CLI_PRINT;
626 return 1;
627 }
628
629 global.cps_lim = v;
630
631 /* Dequeues all of the listeners waiting for a resource */
632 if (!LIST_ISEMPTY(&global_listener_queue))
633 dequeue_all_listeners(&global_listener_queue);
634
635 return 1;
636 }
637 else {
638 appctx->ctx.cli.msg = "'set rate-limit connections' only supports 'global'.\n";
639 appctx->st0 = STAT_CLI_PRINT;
640 return 1;
641 }
642 }
643 else if (strcmp(args[2], "sessions") == 0) {
644 if (strcmp(args[3], "global") == 0) {
645 int v;
646
647 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
648 appctx->ctx.cli.msg = stats_permission_denied_msg;
649 appctx->st0 = STAT_CLI_PRINT;
650 return 1;
651 }
652
653 if (!*args[4]) {
654 appctx->ctx.cli.msg = "Expects an integer value.\n";
655 appctx->st0 = STAT_CLI_PRINT;
656 return 1;
657 }
658
659 v = atoi(args[4]);
660 if (v < 0) {
661 appctx->ctx.cli.msg = "Value out of range.\n";
662 appctx->st0 = STAT_CLI_PRINT;
663 return 1;
664 }
665
666 global.sps_lim = v;
667
668 /* Dequeues all of the listeners waiting for a resource */
669 if (!LIST_ISEMPTY(&global_listener_queue))
670 dequeue_all_listeners(&global_listener_queue);
671
672 return 1;
673 }
674 else {
675 appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n";
676 appctx->st0 = STAT_CLI_PRINT;
677 return 1;
678 }
679 }
680#ifdef USE_OPENSSL
681 else if (strcmp(args[2], "ssl-sessions") == 0) {
682 if (strcmp(args[3], "global") == 0) {
683 int v;
684
685 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
686 appctx->ctx.cli.msg = stats_permission_denied_msg;
687 appctx->st0 = STAT_CLI_PRINT;
688 return 1;
689 }
690
691 if (!*args[4]) {
692 appctx->ctx.cli.msg = "Expects an integer value.\n";
693 appctx->st0 = STAT_CLI_PRINT;
694 return 1;
695 }
696
697 v = atoi(args[4]);
698 if (v < 0) {
699 appctx->ctx.cli.msg = "Value out of range.\n";
700 appctx->st0 = STAT_CLI_PRINT;
701 return 1;
702 }
703
704 global.ssl_lim = v;
705
706 /* Dequeues all of the listeners waiting for a resource */
707 if (!LIST_ISEMPTY(&global_listener_queue))
708 dequeue_all_listeners(&global_listener_queue);
709
710 return 1;
711 }
712 else {
713 appctx->ctx.cli.msg = "'set rate-limit ssl-sessions' only supports 'global'.\n";
714 appctx->st0 = STAT_CLI_PRINT;
715 return 1;
716 }
717 }
718#endif
719 else if (strcmp(args[2], "http-compression") == 0) {
720 if (strcmp(args[3], "global") == 0) {
721 int v;
722
723 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
724 appctx->ctx.cli.msg = stats_permission_denied_msg;
725 appctx->st0 = STAT_CLI_PRINT;
726 return 1;
727 }
728
729 if (!*args[4]) {
730 appctx->ctx.cli.msg = "Expects a maximum input byte rate in kB/s.\n";
731 appctx->st0 = STAT_CLI_PRINT;
732 return 1;
733 }
734
735 v = atoi(args[4]);
736 global.comp_rate_lim = v * 1024; /* Kilo to bytes. */
737 }
738 else {
739 appctx->ctx.cli.msg = "'set rate-limit http-compression' only supports 'global'.\n";
740 appctx->st0 = STAT_CLI_PRINT;
741 return 1;
742 }
743 }
744 else {
745 appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', 'ssl-sessions', and 'http-compression'.\n";
746 appctx->st0 = STAT_CLI_PRINT;
747 return 1;
748 }
William Lallemand32af2032016-10-29 18:09:35 +0200749 } else { /* unknown "set" parameter */
William Lallemand74c24fb2016-11-21 17:18:36 +0100750 return 0;
751 }
752 }
753 else if (strcmp(args[0], "enable") == 0) {
754 if (strcmp(args[1], "agent") == 0) {
755 struct server *sv;
756
757 sv = expect_server_admin(s, si, args[2]);
758 if (!sv)
759 return 1;
760
761 if (!(sv->agent.state & CHK_ST_CONFIGURED)) {
762 appctx->ctx.cli.msg = "Agent was not configured on this server, cannot enable.\n";
763 appctx->st0 = STAT_CLI_PRINT;
764 return 1;
765 }
766
767 sv->agent.state |= CHK_ST_ENABLED;
768 return 1;
769 }
770 else if (strcmp(args[1], "health") == 0) {
771 struct server *sv;
772
773 sv = expect_server_admin(s, si, args[2]);
774 if (!sv)
775 return 1;
776
777 if (!(sv->check.state & CHK_ST_CONFIGURED)) {
778 appctx->ctx.cli.msg = "Health checks are not configured on this server, cannot enable.\n";
779 appctx->st0 = STAT_CLI_PRINT;
780 return 1;
781 }
782
783 sv->check.state |= CHK_ST_ENABLED;
784 return 1;
785 }
786 else if (strcmp(args[1], "server") == 0) {
787 struct server *sv;
788
789 sv = expect_server_admin(s, si, args[2]);
790 if (!sv)
791 return 1;
792
793 srv_adm_set_ready(sv);
794 return 1;
795 }
796 else if (strcmp(args[1], "frontend") == 0) {
797 struct proxy *px;
798
799 px = expect_frontend_admin(s, si, args[2]);
800 if (!px)
801 return 1;
802
803 if (px->state == PR_STSTOPPED) {
804 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot enable.\n";
805 appctx->st0 = STAT_CLI_PRINT;
806 return 1;
807 }
808
809 if (px->state != PR_STPAUSED) {
810 appctx->ctx.cli.msg = "Frontend is already enabled.\n";
811 appctx->st0 = STAT_CLI_PRINT;
812 return 1;
813 }
814
815 if (!resume_proxy(px)) {
816 appctx->ctx.cli.msg = "Failed to resume frontend, check logs for precise cause (port conflict?).\n";
817 appctx->st0 = STAT_CLI_PRINT;
818 return 1;
819 }
820 return 1;
821 }
822 else { /* unknown "enable" parameter */
823 appctx->ctx.cli.msg = "'enable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
824 appctx->st0 = STAT_CLI_PRINT;
825 return 1;
826 }
827 }
828 else if (strcmp(args[0], "disable") == 0) {
829 if (strcmp(args[1], "agent") == 0) {
830 struct server *sv;
831
832 sv = expect_server_admin(s, si, args[2]);
833 if (!sv)
834 return 1;
835
836 sv->agent.state &= ~CHK_ST_ENABLED;
837 return 1;
838 }
839 else if (strcmp(args[1], "health") == 0) {
840 struct server *sv;
841
842 sv = expect_server_admin(s, si, args[2]);
843 if (!sv)
844 return 1;
845
846 sv->check.state &= ~CHK_ST_ENABLED;
847 return 1;
848 }
849 else if (strcmp(args[1], "server") == 0) {
850 struct server *sv;
851
852 sv = expect_server_admin(s, si, args[2]);
853 if (!sv)
854 return 1;
855
856 srv_adm_set_maint(sv);
857 return 1;
858 }
859 else if (strcmp(args[1], "frontend") == 0) {
860 struct proxy *px;
861
862 px = expect_frontend_admin(s, si, args[2]);
863 if (!px)
864 return 1;
865
866 if (px->state == PR_STSTOPPED) {
867 appctx->ctx.cli.msg = "Frontend was previously shut down, cannot disable.\n";
868 appctx->st0 = STAT_CLI_PRINT;
869 return 1;
870 }
871
872 if (px->state == PR_STPAUSED) {
873 appctx->ctx.cli.msg = "Frontend is already disabled.\n";
874 appctx->st0 = STAT_CLI_PRINT;
875 return 1;
876 }
877
878 if (!pause_proxy(px)) {
879 appctx->ctx.cli.msg = "Failed to pause frontend, check logs for precise cause.\n";
880 appctx->st0 = STAT_CLI_PRINT;
881 return 1;
882 }
883 return 1;
884 }
885 else { /* unknown "disable" parameter */
886 appctx->ctx.cli.msg = "'disable' only supports 'agent', 'frontend', 'health', and 'server'.\n";
887 appctx->st0 = STAT_CLI_PRINT;
888 return 1;
889 }
890 }
891 else if (strcmp(args[0], "shutdown") == 0) {
892 if (strcmp(args[1], "frontend") == 0) {
893 struct proxy *px;
894
895 px = expect_frontend_admin(s, si, args[2]);
896 if (!px)
897 return 1;
898
899 if (px->state == PR_STSTOPPED) {
900 appctx->ctx.cli.msg = "Frontend was already shut down.\n";
901 appctx->st0 = STAT_CLI_PRINT;
902 return 1;
903 }
904
905 Warning("Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
906 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
907 send_log(px, LOG_WARNING, "Proxy %s stopped (FE: %lld conns, BE: %lld conns).\n",
908 px->id, px->fe_counters.cum_conn, px->be_counters.cum_conn);
909 stop_proxy(px);
910 return 1;
911 }
912 else if (strcmp(args[1], "session") == 0) {
913 struct stream *sess, *ptr;
914
915 if (strm_li(s)->bind_conf->level < ACCESS_LVL_ADMIN) {
916 appctx->ctx.cli.msg = stats_permission_denied_msg;
917 appctx->st0 = STAT_CLI_PRINT;
918 return 1;
919 }
920
921 if (!*args[2]) {
922 appctx->ctx.cli.msg = "Session pointer expected (use 'show sess').\n";
923 appctx->st0 = STAT_CLI_PRINT;
924 return 1;
925 }
926
927 ptr = (void *)strtoul(args[2], NULL, 0);
928
929 /* first, look for the requested stream in the stream table */
930 list_for_each_entry(sess, &streams, list) {
931 if (sess == ptr)
932 break;
933 }
934
935 /* do we have the stream ? */
936 if (sess != ptr) {
937 appctx->ctx.cli.msg = "No such session (use 'show sess').\n";
938 appctx->st0 = STAT_CLI_PRINT;
939 return 1;
940 }
941
942 stream_shutdown(sess, SF_ERR_KILLED);
943 return 1;
944 }
945 else if (strcmp(args[1], "sessions") == 0) {
946 if (strcmp(args[2], "server") == 0) {
947 struct server *sv;
948 struct stream *sess, *sess_bck;
949
950 sv = expect_server_admin(s, si, args[3]);
951 if (!sv)
952 return 1;
953
954 /* kill all the stream that are on this server */
955 list_for_each_entry_safe(sess, sess_bck, &sv->actconns, by_srv)
956 if (sess->srv_conn == sv)
957 stream_shutdown(sess, SF_ERR_KILLED);
958
959 return 1;
960 }
961 else {
962 appctx->ctx.cli.msg = "'shutdown sessions' only supports 'server'.\n";
963 appctx->st0 = STAT_CLI_PRINT;
964 return 1;
965 }
966 }
967 else { /* unknown "disable" parameter */
968 appctx->ctx.cli.msg = "'shutdown' only supports 'frontend', 'session' and 'sessions'.\n";
969 appctx->st0 = STAT_CLI_PRINT;
970 return 1;
971 }
972 }
William Lallemand74c24fb2016-11-21 17:18:36 +0100973 else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */
974 return 0;
975 }
976 return 1;
977}
978
979/* This I/O handler runs as an applet embedded in a stream interface. It is
980 * used to processes I/O from/to the stats unix socket. The system relies on a
981 * state machine handling requests and various responses. We read a request,
982 * then we process it and send the response, and we possibly display a prompt.
983 * Then we can read again. The state is stored in appctx->st0 and is one of the
984 * STAT_CLI_* constants. appctx->st1 is used to indicate whether prompt is enabled
985 * or not.
986 */
987static void cli_io_handler(struct appctx *appctx)
988{
989 struct stream_interface *si = appctx->owner;
990 struct channel *req = si_oc(si);
991 struct channel *res = si_ic(si);
992 int reql;
993 int len;
994
995 if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
996 goto out;
997
998 while (1) {
999 if (appctx->st0 == STAT_CLI_INIT) {
1000 /* Stats output not initialized yet */
1001 memset(&appctx->ctx.stats, 0, sizeof(appctx->ctx.stats));
1002 appctx->st0 = STAT_CLI_GETREQ;
1003 }
1004 else if (appctx->st0 == STAT_CLI_END) {
1005 /* Let's close for real now. We just close the request
1006 * side, the conditions below will complete if needed.
1007 */
1008 si_shutw(si);
1009 break;
1010 }
1011 else if (appctx->st0 == STAT_CLI_GETREQ) {
1012 /* ensure we have some output room left in the event we
1013 * would want to return some info right after parsing.
1014 */
1015 if (buffer_almost_full(si_ib(si))) {
1016 si_applet_cant_put(si);
1017 break;
1018 }
1019
1020 reql = bo_getline(si_oc(si), trash.str, trash.size);
1021 if (reql <= 0) { /* closed or EOL not found */
1022 if (reql == 0)
1023 break;
1024 appctx->st0 = STAT_CLI_END;
1025 continue;
1026 }
1027
1028 /* seek for a possible unescaped semi-colon. If we find
1029 * one, we replace it with an LF and skip only this part.
1030 */
1031 for (len = 0; len < reql; len++) {
1032 if (trash.str[len] == '\\') {
1033 len++;
1034 continue;
1035 }
1036 if (trash.str[len] == ';') {
1037 trash.str[len] = '\n';
1038 reql = len + 1;
1039 break;
1040 }
1041 }
1042
1043 /* now it is time to check that we have a full line,
1044 * remove the trailing \n and possibly \r, then cut the
1045 * line.
1046 */
1047 len = reql - 1;
1048 if (trash.str[len] != '\n') {
1049 appctx->st0 = STAT_CLI_END;
1050 continue;
1051 }
1052
1053 if (len && trash.str[len-1] == '\r')
1054 len--;
1055
1056 trash.str[len] = '\0';
1057
1058 appctx->st0 = STAT_CLI_PROMPT;
1059 if (len) {
1060 if (strcmp(trash.str, "quit") == 0) {
1061 appctx->st0 = STAT_CLI_END;
1062 continue;
1063 }
1064 else if (strcmp(trash.str, "prompt") == 0)
1065 appctx->st1 = !appctx->st1;
1066 else if (strcmp(trash.str, "help") == 0 ||
1067 !stats_sock_parse_request(si, trash.str)) {
1068 cli_gen_usage_msg();
1069 if (dynamic_usage_msg)
1070 appctx->ctx.cli.msg = dynamic_usage_msg;
1071 else
1072 appctx->ctx.cli.msg = stats_sock_usage_msg;
1073 appctx->st0 = STAT_CLI_PRINT;
1074 }
1075 /* NB: stats_sock_parse_request() may have put
1076 * another STAT_CLI_O_* into appctx->st0.
1077 */
1078 }
1079 else if (!appctx->st1) {
1080 /* if prompt is disabled, print help on empty lines,
1081 * so that the user at least knows how to enable
1082 * prompt and find help.
1083 */
1084 cli_gen_usage_msg();
1085 if (dynamic_usage_msg)
1086 appctx->ctx.cli.msg = dynamic_usage_msg;
1087 else
1088 appctx->ctx.cli.msg = stats_sock_usage_msg;
1089 appctx->st0 = STAT_CLI_PRINT;
1090 }
1091
1092 /* re-adjust req buffer */
1093 bo_skip(si_oc(si), reql);
1094 req->flags |= CF_READ_DONTWAIT; /* we plan to read small requests */
1095 }
1096 else { /* output functions */
1097 switch (appctx->st0) {
1098 case STAT_CLI_PROMPT:
1099 break;
1100 case STAT_CLI_PRINT:
1101 if (bi_putstr(si_ic(si), appctx->ctx.cli.msg) != -1)
1102 appctx->st0 = STAT_CLI_PROMPT;
1103 else
1104 si_applet_cant_put(si);
1105 break;
1106 case STAT_CLI_PRINT_FREE:
1107 if (bi_putstr(si_ic(si), appctx->ctx.cli.err) != -1) {
1108 free(appctx->ctx.cli.err);
1109 appctx->st0 = STAT_CLI_PROMPT;
1110 }
1111 else
1112 si_applet_cant_put(si);
1113 break;
William Lallemand74c24fb2016-11-21 17:18:36 +01001114 case STAT_CLI_O_CUSTOM: /* use custom pointer */
1115 if (appctx->io_handler)
1116 if (appctx->io_handler(appctx)) {
1117 appctx->st0 = STAT_CLI_PROMPT;
1118 if (appctx->io_release) {
1119 appctx->io_release(appctx);
1120 appctx->io_release = NULL;
1121 }
1122 }
1123 break;
1124 default: /* abnormal state */
1125 si->flags |= SI_FL_ERR;
1126 break;
1127 }
1128
1129 /* The post-command prompt is either LF alone or LF + '> ' in interactive mode */
1130 if (appctx->st0 == STAT_CLI_PROMPT) {
1131 if (bi_putstr(si_ic(si), appctx->st1 ? "\n> " : "\n") != -1)
1132 appctx->st0 = STAT_CLI_GETREQ;
1133 else
1134 si_applet_cant_put(si);
1135 }
1136
1137 /* If the output functions are still there, it means they require more room. */
1138 if (appctx->st0 >= STAT_CLI_OUTPUT)
1139 break;
1140
1141 /* Now we close the output if one of the writers did so,
1142 * or if we're not in interactive mode and the request
1143 * buffer is empty. This still allows pipelined requests
1144 * to be sent in non-interactive mode.
1145 */
1146 if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) {
1147 appctx->st0 = STAT_CLI_END;
1148 continue;
1149 }
1150
1151 /* switch state back to GETREQ to read next requests */
1152 appctx->st0 = STAT_CLI_GETREQ;
1153 }
1154 }
1155
1156 if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST)) {
1157 DPRINTF(stderr, "%s@%d: si to buf closed. req=%08x, res=%08x, st=%d\n",
1158 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1159 /* Other side has closed, let's abort if we have no more processing to do
1160 * and nothing more to consume. This is comparable to a broken pipe, so
1161 * we forward the close to the request side so that it flows upstream to
1162 * the client.
1163 */
1164 si_shutw(si);
1165 }
1166
1167 if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST) && (appctx->st0 < STAT_CLI_OUTPUT)) {
1168 DPRINTF(stderr, "%s@%d: buf to si closed. req=%08x, res=%08x, st=%d\n",
1169 __FUNCTION__, __LINE__, req->flags, res->flags, si->state);
1170 /* We have no more processing to do, and nothing more to send, and
1171 * the client side has closed. So we'll forward this state downstream
1172 * on the response buffer.
1173 */
1174 si_shutr(si);
1175 res->flags |= CF_READ_NULL;
1176 }
1177
1178 out:
1179 DPRINTF(stderr, "%s@%d: st=%d, rqf=%x, rpf=%x, rqh=%d, rqs=%d, rh=%d, rs=%d\n",
1180 __FUNCTION__, __LINE__,
1181 si->state, req->flags, res->flags, req->buf->i, req->buf->o, res->buf->i, res->buf->o);
1182}
1183
William Lallemand74c24fb2016-11-21 17:18:36 +01001184/* This is called when the stream interface is closed. For instance, upon an
1185 * external abort, we won't call the i/o handler anymore so we may need to
1186 * remove back references to the stream currently being dumped.
1187 */
1188static void cli_release_handler(struct appctx *appctx)
1189{
1190 if (appctx->io_release) {
1191 appctx->io_release(appctx);
1192 appctx->io_release = NULL;
1193 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001194 else if (appctx->st0 == STAT_CLI_PRINT_FREE) {
1195 free(appctx->ctx.cli.err);
1196 appctx->ctx.cli.err = NULL;
1197 }
William Lallemand74c24fb2016-11-21 17:18:36 +01001198}
1199
1200/* This function dumps all environmnent variables to the buffer. It returns 0
1201 * if the output buffer is full and it needs to be called again, otherwise
1202 * non-zero. Dumps only one entry if st2 == STAT_ST_END.
1203 */
Willy Tarreau0a739292016-11-22 20:21:23 +01001204static int cli_io_handler_show_env(struct appctx *appctx)
William Lallemand74c24fb2016-11-21 17:18:36 +01001205{
Willy Tarreau0a739292016-11-22 20:21:23 +01001206 struct stream_interface *si = appctx->owner;
William Lallemand74c24fb2016-11-21 17:18:36 +01001207
1208 if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
1209 return 1;
1210
1211 chunk_reset(&trash);
1212
1213 /* we have two inner loops here, one for the proxy, the other one for
1214 * the buffer.
1215 */
1216 while (*appctx->ctx.env.var) {
1217 chunk_printf(&trash, "%s\n", *appctx->ctx.env.var);
1218
1219 if (bi_putchk(si_ic(si), &trash) == -1) {
1220 si_applet_cant_put(si);
1221 return 0;
1222 }
1223 if (appctx->st2 == STAT_ST_END)
1224 break;
1225 appctx->ctx.env.var++;
1226 }
1227
1228 /* dump complete */
1229 return 1;
1230}
1231
Willy Tarreau0a739292016-11-22 20:21:23 +01001232/* parse a "show env" CLI request. Returns 0 if it needs to continue, 1 if it
1233 * wants to stop here.
1234 */
1235static int cli_parse_show_env(char **args, struct appctx *appctx, void *private)
1236{
1237 extern char **environ;
1238
1239 if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1240 return 1;
1241
1242 appctx->ctx.env.var = environ;
1243 appctx->st2 = STAT_ST_INIT;
1244
1245 if (*args[2]) {
1246 int len = strlen(args[2]);
1247
1248 for (; *appctx->ctx.env.var; appctx->ctx.env.var++) {
1249 if (strncmp(*appctx->ctx.env.var, args[2], len) == 0 &&
1250 (*appctx->ctx.env.var)[len] == '=')
1251 break;
1252 }
1253 if (!*appctx->ctx.env.var) {
1254 appctx->ctx.cli.msg = "Variable not found\n";
1255 appctx->st0 = STAT_CLI_PRINT;
1256 return 1;
1257 }
1258 appctx->st2 = STAT_ST_END;
1259 }
1260 return 0;
1261}
1262
Willy Tarreau599852e2016-11-22 20:33:32 +01001263/* parse a "set timeout" CLI request. It always returns 1. */
1264static int cli_parse_set_timeout(char **args, struct appctx *appctx, void *private)
1265{
1266 struct stream_interface *si = appctx->owner;
1267 struct stream *s = si_strm(si);
1268
1269 if (strcmp(args[2], "cli") == 0) {
1270 unsigned timeout;
1271 const char *res;
1272
1273 if (!*args[3]) {
1274 appctx->ctx.cli.msg = "Expects an integer value.\n";
1275 appctx->st0 = STAT_CLI_PRINT;
1276 return 1;
1277 }
1278
1279 res = parse_time_err(args[3], &timeout, TIME_UNIT_S);
1280 if (res || timeout < 1) {
1281 appctx->ctx.cli.msg = "Invalid timeout value.\n";
1282 appctx->st0 = STAT_CLI_PRINT;
1283 return 1;
1284 }
1285
1286 s->req.rto = s->res.wto = 1 + MS_TO_TICKS(timeout*1000);
1287 task_wakeup(s->task, TASK_WOKEN_MSG); // recompute timeouts
1288 return 1;
1289 }
1290 else {
1291 appctx->ctx.cli.msg = "'set timeout' only supports 'cli'.\n";
1292 appctx->st0 = STAT_CLI_PRINT;
1293 return 1;
1294 }
1295}
1296
Willy Tarreau2af99412016-11-23 11:10:59 +01001297/* parse a "set maxconn global" command. It always returns 1. */
1298static int cli_parse_set_maxconn_global(char **args, struct appctx *appctx, void *private)
1299{
1300 int v;
1301
1302 if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1303 return 1;
1304
1305 if (!*args[3]) {
1306 appctx->ctx.cli.msg = "Expects an integer value.\n";
1307 appctx->st0 = STAT_CLI_PRINT;
1308 return 1;
1309 }
1310
1311 v = atoi(args[3]);
1312 if (v > global.hardmaxconn) {
1313 appctx->ctx.cli.msg = "Value out of range.\n";
1314 appctx->st0 = STAT_CLI_PRINT;
1315 return 1;
1316 }
1317
1318 /* check for unlimited values */
1319 if (v <= 0)
1320 v = global.hardmaxconn;
1321
1322 global.maxconn = v;
1323
1324 /* Dequeues all of the listeners waiting for a resource */
1325 if (!LIST_ISEMPTY(&global_listener_queue))
1326 dequeue_all_listeners(&global_listener_queue);
1327
1328 return 1;
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 Tarreau2af99412016-11-23 11:10:59 +01001363 { { "set", "maxconn", "global", NULL }, "set maxconn global : change the per-process maxconn setting", cli_parse_set_maxconn_global, NULL },
Willy Tarreau599852e2016-11-22 20:33:32 +01001364 { { "set", "timeout", NULL }, "set timeout : change a timeout setting", cli_parse_set_timeout, NULL, NULL },
Willy Tarreau0a739292016-11-22 20:21:23 +01001365 { { "show", "env", NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL },
1366 {{},}
1367}};
1368
William Lallemand74c24fb2016-11-21 17:18:36 +01001369static struct cfg_kw_list cfg_kws = {ILH, {
1370 { CFG_GLOBAL, "stats", stats_parse_global },
1371 { 0, NULL, NULL },
1372}};
1373
1374static struct bind_kw_list bind_kws = { "STAT", { }, {
1375 { "level", bind_parse_level, 1 }, /* set the unix socket admin level */
1376 { NULL, NULL, 0 },
1377}};
1378
1379__attribute__((constructor))
1380static void __dumpstats_module_init(void)
1381{
1382 cfg_register_keywords(&cfg_kws);
Willy Tarreau0a739292016-11-22 20:21:23 +01001383 cli_register_kw(&cli_kws);
William Lallemand74c24fb2016-11-21 17:18:36 +01001384 bind_register_keywords(&bind_kws);
1385}
1386
1387/*
1388 * Local variables:
1389 * c-indent-level: 8
1390 * c-basic-offset: 8
1391 * End:
1392 */