Files
FTL/src/webserver/lua_web.c
T
2023-10-15 10:44:54 +02:00

187 lines
6.2 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
* Lua-related webserver routines
*
* 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/lua_web.h"
#include "api/api.h"
// luaL_dostring()
#include "lua/lauxlib.h"
// struct config
#include "config/config.h"
// log_web()
#include "log.h"
// directory_exists()
#include "files.h"
static char *login_uri = NULL, *admin_api_uri = NULL;
void allocate_lua(void)
{
// Build login URI string (webhome + login)
// Append "login" to webhome string
const size_t login_uri_len = strlen(config.webserver.paths.webhome.v.s);
login_uri = calloc(login_uri_len + 6, sizeof(char));
memcpy(login_uri, config.webserver.paths.webhome.v.s, login_uri_len);
strcpy(login_uri + login_uri_len, "login");
login_uri[login_uri_len + 5u] = '\0';
log_debug(DEBUG_API, "Login URI: %s", login_uri);
// Build "wrong" API URI string (webhome + api)
// Append "api" to webhome string
const size_t admin_api_uri_len = strlen(config.webserver.paths.webhome.v.s);
admin_api_uri = calloc(admin_api_uri_len + 4, sizeof(char));
memcpy(admin_api_uri, config.webserver.paths.webhome.v.s, admin_api_uri_len);
strcpy(admin_api_uri + admin_api_uri_len, "api");
admin_api_uri[admin_api_uri_len + 3u] = '\0';
log_debug(DEBUG_API, "Admin API URI: %s", admin_api_uri);
}
void free_lua(void)
{
// Free login_uri
if(login_uri != NULL)
free(login_uri);
// Free admin_api_uri
if(admin_api_uri != NULL)
free(admin_api_uri);
}
void init_lua(const struct mg_connection *conn, void *L, unsigned context_flags)
{
// Set onerror handler to print errors to the log
if(luaL_dostring(L, "mg.onerror = function(e) mg.cry('Error at ' .. e) end") != LUA_OK)
{
log_err("Error setting Lua onerror handler: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
int request_handler(struct mg_connection *conn, void *cbdata)
{
// Fall back to CivetWeb's default handler if login URI is not available
// (should never happen)
if(login_uri == NULL || admin_api_uri == NULL)
return 0;
/* Handler may access the request info using mg_get_request_info */
const struct mg_request_info *req_info = mg_get_request_info(conn);
const size_t uri_raw_len = strlen(req_info->local_uri_raw);
// Build minimal api struct to check authentication
struct ftl_conn api = { 0 };
api.conn = conn;
api.request = req_info;
api.now = double_time();
// Check if the request is for the API under /admin/api
// (it is posted at /api)
if(strncmp(req_info->local_uri_raw, admin_api_uri, strlen(admin_api_uri)) == 0)
{
const size_t hint_len = 38 + strlen(admin_api_uri) + 2*strlen(config.webserver.domain.v.s);
char *hint = calloc(hint_len, sizeof(char));
snprintf(hint, hint_len, "The API is hosted at %s/api, not %s%s",
config.webserver.domain.v.s, config.webserver.domain.v.s, admin_api_uri);
hint[hint_len - 1u] = '\0';
// not found or invalid request
return send_json_error_free(&api, 400,
"bad_request",
"Bad request",
hint,
true);
}
// Check if last part of the URI contains a dot (is a file)
const char *last_dot = strrchr(req_info->local_uri_raw, '.');
const char *last_slash = strrchr(req_info->local_uri_raw, '/');
const bool no_dot = (last_dot == NULL || last_slash > last_dot);
// Check if the request is for the login page
const bool login = (strcmp(req_info->local_uri_raw, login_uri) == 0);
// Check if the request is for a LUA page (every XYZ.lp has already been
// rewritten at this point to XYZ)
if(!no_dot)
{
// Not a LUA page - fall back to CivetWeb's default handler
return 0;
}
// Every LUA page except admin/login requires authentication
const int authorized = check_client_auth(&api, false) != API_AUTH_UNAUTHORIZED;
if(!login)
{
// This is not the login page - check if the user is authenticated
// Check if the user is authenticated
if(!authorized)
{
// Append query string to target
char *target = NULL;
if(req_info->query_string != NULL)
{
target = calloc(uri_raw_len + strlen(req_info->query_string) + 2u, sizeof(char));
strcpy(target, req_info->local_uri_raw);
strcat(target, "?");
strcat(target, req_info->query_string);
}
else
{
target = strdup(req_info->local_uri_raw);
}
if(target == NULL)
{
log_err("Error allocating memory for redirection target");
return send_json_error(&api, 500,
"internal_error",
"Internal server error",
"Cannot allocate memory for redirection target");
}
// Encode target string
const size_t encoded_target_len = strlen(target) * 3u + 1u;
char *encoded_target = calloc(encoded_target_len, sizeof(char));
if(encoded_target == NULL)
{
log_err("Error allocating memory for encoded redirection target");
return send_json_error(&api, 500,
"internal_error",
"Internal server error",
"Cannot allocate memory for encoded redirection target");
}
// Encode target string
mg_url_encode(target, encoded_target, encoded_target_len);
free(target);
// User is not authenticated, redirect to login page
log_web("Authentication required, redirecting to %slogin?target=%s", config.webserver.paths.webhome.v.s, encoded_target);
mg_printf(conn, "HTTP/1.1 302 Found\r\nLocation: %slogin?target=%s\r\n\r\n", config.webserver.paths.webhome.v.s, encoded_target);
free(encoded_target);
return 302;
}
}
else
{
// This is the login page - check if the user is already authenticated
// Check if the user is authenticated
if(authorized)
{
// User is already authenticated, redirect to index page
log_web("User is already authenticated, redirecting to %s", config.webserver.paths.webhome.v.s);
mg_printf(conn, "HTTP/1.1 302 Found\r\nLocation: %s\r\n\r\n", config.webserver.paths.webhome.v.s);
return 302;
}
}
// No special handling required, fall back to default CivetWeb handler
return 0;
}