MEDIUM: cli: allow custom pattern for payload

The CLI payload syntax has some limitation, it can't handle payloads
with empty lines, which is a common problem when uploading a PEM file
over the CLI.

This patch implements a way to customize the ending pattern of the CLI,
so we can't look for other things than empty lines.

A char cli_payload_pat[8] is used in the appctx to store the customized
pattern. The pattern can't be more than 7 characters and can still empty
to match an empty line.

The cli_io_handler() identifies the pattern and stores it, and
cli_parse_request() identifies the end of the payload.

If the customized pattern between "<<" and "\n" is more than 7
characters, it is not considered as a pattern.

This patch only implements the parser for the 'stats socket', another
patch is needed for the 'master CLI'.
This commit is contained in:
William Lallemand 2023-11-27 16:04:20 +01:00
parent 23c810d042
commit e3557c7d45
2 changed files with 55 additions and 21 deletions

View File

@ -70,6 +70,7 @@ struct appctx {
if the command is terminated or the session released */
int cli_severity_output; /* used within the cli_io_handler to format severity output of informational feedback */
int cli_level; /* the level of CLI which can be lowered dynamically */
char cli_payload_pat[8]; /* Payload pattern */
uint32_t cli_anon_key; /* the key to anonymise with the hash in cli */
struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */
struct task *t; /* task associated to the applet */

View File

@ -757,13 +757,22 @@ static int cli_parse_request(struct appctx *appctx)
if (!*p)
break;
if (strcmp(p, PAYLOAD_PATTERN) == 0) {
/* payload pattern recognized here, this is not an arg anymore,
* the payload starts at the first byte that follows the zero
* after the pattern.
*/
payload = p + strlen(PAYLOAD_PATTERN) + 1;
break;
/* first check if the '<<' is present, but this is not enough
* because we don't know if this is the end of the string */
if (strncmp(p, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
int pat_len = strlen(appctx->cli_payload_pat);
/* then if the customized pattern is empty, check if the next character is '\0' */
if (pat_len == 0 && p[strlen(PAYLOAD_PATTERN)] == '\0') {
payload = p + strlen(PAYLOAD_PATTERN) + 1;
break;
}
/* else if we found the customized pattern at the end of the string */
if (strcmp(p + strlen(PAYLOAD_PATTERN), appctx->cli_payload_pat) == 0) {
payload = p + strlen(PAYLOAD_PATTERN) + pat_len + 1;
break;
}
}
args[i] = p;
@ -1010,29 +1019,53 @@ static void cli_io_handler(struct appctx *appctx)
appctx->st0 = CLI_ST_PROMPT;
if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
/* empty line */
if (!len) {
/* remove the last two \n */
appctx->chunk->data -= 2;
appctx->chunk->area[appctx->chunk->data] = 0;
cli_parse_request(appctx);
chunk_reset(appctx->chunk);
/* NB: cli_sock_parse_request() may have put
* another CLI_ST_O_* into appctx->st0.
*/
/* look for a pattern */
if (len == strlen(appctx->cli_payload_pat)) {
/* here use 'len' because str still contains the \n */
if (strncmp(str, appctx->cli_payload_pat, len) == 0) {
/* remove the last two \n */
appctx->chunk->data -= strlen(appctx->cli_payload_pat) + 2;
appctx->chunk->area[appctx->chunk->data] = 0;
cli_parse_request(appctx);
chunk_reset(appctx->chunk);
/* NB: cli_sock_parse_request() may have put
* another CLI_ST_O_* into appctx->st0.
*/
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
}
}
}
else {
char *last_arg;
/*
* Look for the "payload start" pattern at the end of a line
* Its location is not remembered here, this is just to switch
* to a gathering mode.
* The pattern must start by << followed by 0
* to 7 characters, and finished by the end of
* the command (\n or ;).
*/
if (strcmp(appctx->chunk->area + appctx->chunk->data - strlen(PAYLOAD_PATTERN), PAYLOAD_PATTERN) == 0) {
appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
appctx->chunk->data++; // keep the trailing \0 after '<<'
/* look for the first space starting by the end of the line */
for (last_arg = appctx->chunk->area + appctx->chunk->data; last_arg != appctx->chunk->area; last_arg--) {
if (*last_arg == ' ' || *last_arg == '\t') {
last_arg++;
break;
}
}
if (strncmp(last_arg, PAYLOAD_PATTERN, strlen(PAYLOAD_PATTERN)) == 0) {
ssize_t pat_len = strlen(last_arg + strlen(PAYLOAD_PATTERN));
/* A customized pattern can't be more than 7 characters
* if it's more, don't make it a payload
*/
if (pat_len < sizeof(appctx->cli_payload_pat)) {
appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
/* copy the customized pattern, don't store the << */
strncpy(appctx->cli_payload_pat, last_arg + strlen(PAYLOAD_PATTERN), sizeof(appctx->cli_payload_pat)-1);
appctx->cli_payload_pat[sizeof(appctx->cli_payload_pat)-1] = '\0';
appctx->chunk->data++; // keep the trailing \0 after the pattern
}
}
else {
/* no payload, the command is complete: parse the request */