Files
FTL/src/api/action.c
Adam Warner 5155088458 Make ANSI color codes opt-in for gravity API endpoint
The gravity action endpoint now only includes ANSI escape codes when the
client explicitly requests them via the 'color=true' query parameter. This
prevents ANSI codes from appearing in API responses for consumers that don't
need them, fixing issue #2671.

The web interface will request colors by adding '?color=true' to its API call.
Other API consumers will receive plain output by default.

Addresses: pi-hole/FTL#2671
2025-11-19 21:24:25 +00:00

187 lines
5.1 KiB
C

/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/action
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "webserver/http-common.h"
#include "webserver/json_macros.h"
#include "api/api.h"
// wait()
#include <sys/wait.h>
// reboot()
#include <sys/reboot.h>
#include <unistd.h>
// exit_code
#include "signals.h"
// flush_network_table()
#include "database/network-table.h"
#include "config/config.h"
static int run_and_stream_command(struct ftl_conn *api, const char *path, const char *const args[], const char *extra_env)
{
// Create a pipe for communication with our child
int pipefd[2];
if(pipe(pipefd) !=0)
{
log_err("Cannot create pipe while running gravity action: %s", strerror(errno));
return false;
}
// Fork!
pid_t cpid = fork();
int code = -1;
bool crashed = false;
if (cpid == 0)
{
/*** CHILD ***/
// Close the reading end of the pipe
close(pipefd[0]);
// Disable logging
log_ctrl(false, false);
// Flush STDERR
fflush(stderr);
// Redirect STDERR into our pipe
dup2(pipefd[1], STDERR_FILENO);
dup2(pipefd[1], STDOUT_FILENO);
// Set extra environment variable if requested
if(extra_env != NULL)
setenv(extra_env, "1", 1);
// Run pihole -g
execv(path, (char *const *)args);
// Exit the fork
exit(EXIT_SUCCESS);
}
else
{
/*** PARENT ***/
// Close the writing end of the pipe
close(pipefd[1]);
// Send 200 OK with chunked size (-1)
mg_send_http_ok(api->conn, "text/plain", -1);
// Read readirected STDOUT/STDERR until EOF
// We are only interested in the last pipe line
char errbuf[1024] = "";
while(read(pipefd[0], errbuf, sizeof(errbuf)) > 0)
{
// Send chunked data
// The chunked size is the length of the string in hex and has to be
// transferred in advance, followed by \r\n as line separator and
// followed by a chunk of data (the string itself) of the specified
// size
mg_printf(api->conn, "%zX\r\n%s\r\n", strlen(errbuf), errbuf);
// Reset buffer
memset(errbuf, 0, sizeof(errbuf));
}
// Wait until child has exited to get its return code
int status;
waitpid(cpid, &status, 0);
code = WEXITSTATUS(status);
if(WIFSIGNALED(status))
{
crashed = true;
log_err("gravity failed with signal %d %s",
WTERMSIG(status),
WCOREDUMP(status) ? "(core dumped)" : "");
}
log_debug(DEBUG_API, "Gravity return code: %d", code);
// Close the reading end of the pipe
close(pipefd[0]);
}
// Send final chunk of size 0 showing end of data
mg_printf(api->conn, "0\r\n\r\n");
if(code == EXIT_SUCCESS && !crashed)
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Gravity failed",
NULL);
}
int api_action_gravity(struct ftl_conn *api)
{
// Only set FORCE_COLOR if the client explicitly requests it via "color=true" query parameter
// This prevents ANSI escape codes from being included in the output for API consumers that don't need them
bool color = false;
const char *query = api->request != NULL ? api->request->query_string : "";
if(query != NULL)
get_bool_var(query, "color", &color);
const char *extra_env = color ? "FORCE_COLOR" : NULL;
return run_and_stream_command(api, "/usr/local/bin/pihole", (const char *const []){ "pihole", "-g", NULL }, extra_env);
}
int api_action_restartDNS(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"Restarting DNS is not allowed",
"Check setting webserver.api.allow_destructive");
restart_ftl("API action request");
return send_json_success(api);
}
int api_action_flush_logs(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"Flushing the logs is not allowed",
"Check setting webserver.api.allow_destructive");
log_info("Received API request to flush the logs");
// Flush the logs
if(flush_dnsmasq_log())
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Cannot flush the logs",
NULL);
}
int api_action_flush_network(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"Flushing the network tables is not allowed",
"Check setting webserver.api.allow_destructive");
log_info("Received API request to flush the network tables");
// Flush the network tables
if(flush_network_table())
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Cannot flush the network tables",
NULL);
}