mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
Add simple /api/auth implementation. Currently, all passwords are accepted and a login session is valid for 5 minutes.
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
@@ -17,7 +17,7 @@ DNSMASQ_OPTS = -DHAVE_DNSSEC -DHAVE_DNSSEC_STATIC -DHAVE_IDN
|
||||
FTL_DEPS = *.h database/*.h api/*.h version.h
|
||||
FTL_DB_OBJ = database/common.o database/query-table.o database/network-table.o database/gravity-db.o database/database-thread.o \
|
||||
database/sqlite3-ext.o database/message-table.o
|
||||
FTL_API_OBJ = api/http.o api/ftl.o api/stats.o api/dns.o api/version.o
|
||||
FTL_API_OBJ = api/http.o api/ftl.o api/stats.o api/dns.o api/version.o api/auth.o api/settings.o
|
||||
FTL_OBJ = $(FTL_DB_OBJ) $(FTL_API_OBJ) main.o memory.o log.o daemon.o datastructure.o signals.o files.o setupVars.o args.o gc.o config.o dnsmasq_interface.o resolve.o regex.o shmem.o capabilities.o overTime.o timers.o vector.o
|
||||
|
||||
DNSMASQ_DEPS = config.h dhcp-protocol.h dns-protocol.h radv-protocol.h dhcp6-protocol.h dnsmasq.h ip6addr.h metrics.h ../dnsmasq_interface.h
|
||||
|
||||
@@ -101,6 +101,10 @@
|
||||
// Default: 1000 (one second)
|
||||
#define DATABASE_BUSY_TIMEOUT 1000
|
||||
|
||||
// After how much time does a valid API session expire? [seconds]
|
||||
// Default: 300 (five minutes)
|
||||
#define API_SESSION_EXPIRE 300
|
||||
|
||||
// FTLDNS enums
|
||||
enum { QUERIES, UPSTREAMS, CLIENTS, DOMAINS, OVERTIME, WILDCARD, DNS_CACHE };
|
||||
enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED };
|
||||
|
||||
@@ -41,4 +41,10 @@ int api_dns_somelist(struct mg_connection *conn,
|
||||
// Version method
|
||||
int api_version(struct mg_connection *conn);
|
||||
|
||||
// Auth method
|
||||
int api_auth(struct mg_connection *conn);
|
||||
|
||||
// Settings methods
|
||||
int api_settings_web(struct mg_connection *conn);
|
||||
|
||||
#endif // API_H
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* FTL Engine
|
||||
* API Implementation /api/auth
|
||||
*
|
||||
* 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 "api.h"
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
|
||||
int api_auth(struct mg_connection *conn)
|
||||
{
|
||||
bool auth = false;
|
||||
|
||||
// Does the client try to authenticate through a set header?
|
||||
const char *xHeader = mg_get_header(conn, "X-Pi-hole-Authenticate");
|
||||
if(xHeader != NULL && strlen(xHeader) > 0)
|
||||
{
|
||||
auth = true;
|
||||
|
||||
if(config.debug & DEBUG_API)
|
||||
logg("Received X-Pi-hole-Authenticate: %s", xHeader);
|
||||
}
|
||||
|
||||
// Does the client provide a user_id cookie?
|
||||
int user_id = 0;
|
||||
if(http_get_cookie_int(conn, "user_id", &user_id))
|
||||
{
|
||||
auth = true;
|
||||
|
||||
if(config.debug & DEBUG_API)
|
||||
logg("Read user_id=%i from user-provided cookie", user_id);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJ();
|
||||
if(auth)
|
||||
{
|
||||
if(config.debug & DEBUG_API)
|
||||
logg("Authentification: OK");
|
||||
|
||||
JSON_OBJ_REF_STR(json, "status", "success");
|
||||
// Ten minutes validity
|
||||
char *additional_headers = NULL;
|
||||
if(asprintf(&additional_headers, "Set-Cookie: user_id=%u; Path=/; Max-Age=%u\r\n", 1, API_SESSION_EXPIRE) > 0)
|
||||
{
|
||||
JSON_SENT_OBJECT_AND_HEADERS(json, additional_headers);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_SENT_OBJECT(json);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(config.debug & DEBUG_API)
|
||||
logg("Authentification: FAIL");
|
||||
|
||||
JSON_OBJ_REF_STR(json, "key", "unauthorized");
|
||||
char *additional_headers = strdup("Set-Cookie: user_id=deleted; Path=/; Max-Age=-1\r\n");
|
||||
JSON_SENT_OBJECT_AND_HEADERS_CODE(json, 401, additional_headers);
|
||||
}
|
||||
}
|
||||
+47
-5
@@ -18,12 +18,21 @@
|
||||
// Server context handle
|
||||
static struct mg_context *ctx = NULL;
|
||||
|
||||
int send_http(struct mg_connection *conn, const char *mime_type, const char *msg)
|
||||
int send_http(struct mg_connection *conn, const char *mime_type,
|
||||
const char *additional_headers, const char *msg)
|
||||
{
|
||||
mg_send_http_ok(conn, mime_type, strlen(msg));
|
||||
mg_send_http_ok(conn, mime_type, additional_headers, strlen(msg));
|
||||
return mg_write(conn, msg, strlen(msg));
|
||||
}
|
||||
|
||||
int send_http_unauth(struct mg_connection *conn,
|
||||
const char *additional_headers, const char *msg)
|
||||
{
|
||||
// MPayload will be sent with text/plain encoding due to
|
||||
// the first line being "Error 401" by definition
|
||||
return mg_send_http_error(conn, 401, "%s", msg);
|
||||
}
|
||||
|
||||
int send_http_error(struct mg_connection *conn)
|
||||
{
|
||||
return mg_send_http_error(conn, 500, "Internal server error");
|
||||
@@ -41,7 +50,7 @@ void __attribute__ ((format (gnu_printf, 3, 4))) http_send(struct mg_connection
|
||||
if(!chunk)
|
||||
{
|
||||
// Send 200 HTTP header with content size
|
||||
mg_send_http_ok(conn, "application/json", len);
|
||||
mg_send_http_ok(conn, "application/json", NULL, len);
|
||||
}
|
||||
if(chunk && mg_send_chunk(conn, buffer, len) < 0)
|
||||
{
|
||||
@@ -60,7 +69,7 @@ void __attribute__ ((format (gnu_printf, 3, 4))) http_send(struct mg_connection
|
||||
// Print passed string directly
|
||||
static int print_simple(struct mg_connection *conn, void *input)
|
||||
{
|
||||
return send_http(conn, "text/plain", input);
|
||||
return send_http(conn, "text/plain", NULL, input);
|
||||
}
|
||||
|
||||
static int api_handler(struct mg_connection *conn, void *ignored)
|
||||
@@ -161,6 +170,16 @@ static int api_handler(struct mg_connection *conn, void *ignored)
|
||||
{
|
||||
ret = api_version(conn);
|
||||
}
|
||||
/******************************** api/auth ****************************/
|
||||
else if(strcasecmp("/api/auth", request->local_uri) == 0)
|
||||
{
|
||||
ret = api_auth(conn);
|
||||
}
|
||||
/******************************** api/settings ****************************/
|
||||
else if(strcasecmp("/api/settings/web", request->local_uri) == 0)
|
||||
{
|
||||
ret = api_settings_web(conn);
|
||||
}
|
||||
/******************************** not found ******************************/
|
||||
/* else
|
||||
{
|
||||
@@ -213,4 +232,27 @@ void http_terminate(void)
|
||||
|
||||
/* Un-initialize the library */
|
||||
mg_exit_library();
|
||||
}
|
||||
}
|
||||
|
||||
bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i)
|
||||
{
|
||||
// Maximum cookie length is 4KB
|
||||
char cookieValue[4096];
|
||||
const char *cookie = mg_get_header(conn, "Cookie");
|
||||
if(mg_get_cookie(cookie, cookieName, cookieValue, sizeof(cookieValue)) > 0)
|
||||
{
|
||||
*i = atoi(cookieValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size)
|
||||
{
|
||||
const char *cookie = mg_get_header(conn, "Cookie");
|
||||
if(mg_get_cookie(cookie, cookieName, str, str_size) > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+8
-1
@@ -24,9 +24,16 @@ void http_init(void);
|
||||
void http_terminate(void);
|
||||
void http_send(struct mg_connection *conn, bool chunk, const char *format, ...) __attribute__ ((format (gnu_printf, 3, 4)));
|
||||
|
||||
int send_http(struct mg_connection *conn, const char *mime_type, const char *msg);
|
||||
int send_http(struct mg_connection *conn, const char *mime_type,
|
||||
const char *additional_headers, const char *msg);
|
||||
int send_http_unauth(struct mg_connection *conn,
|
||||
const char *additional_headers, const char *msg);
|
||||
int send_http_error(struct mg_connection *conn);
|
||||
|
||||
// Cookie routines
|
||||
bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i);
|
||||
bool http_get_cookie_str(struct mg_connection *conn, const char *cookieName, char *str, size_t str_size);
|
||||
|
||||
// HTTP macros
|
||||
#define GET_VAR(variable, destination, source) mg_get_var(source, strlen(source), variable, destination, sizeof(destination))
|
||||
|
||||
|
||||
+40
-1
@@ -77,6 +77,17 @@
|
||||
cJSON_AddItemToObject(object, key, null_item); \
|
||||
}
|
||||
|
||||
#define JSON_OBJ_ADD_BOOL(object, key, value) {\
|
||||
cJSON *bool_item = cJSON_CreateBool(value); \
|
||||
if(bool_item == NULL) \
|
||||
{ \
|
||||
cJSON_Delete(object); \
|
||||
send_http_error(conn); \
|
||||
return 500; \
|
||||
} \
|
||||
cJSON_AddItemToObject(object, key, bool_item); \
|
||||
}
|
||||
|
||||
#define JSON_ARRAY_ADD_NUMBER(object, number){ \
|
||||
cJSON *number_item = cJSON_CreateNumber((double)number); \
|
||||
cJSON_AddItemToArray(object, number_item); \
|
||||
@@ -119,7 +130,35 @@
|
||||
send_http_error(conn); \
|
||||
return 500; \
|
||||
} \
|
||||
send_http(conn, "application/json", msg); \
|
||||
send_http(conn, "application/json", NULL, msg); \
|
||||
cJSON_Delete(object); \
|
||||
return 200; \
|
||||
}
|
||||
|
||||
#define JSON_SENT_OBJECT_AND_HEADERS(object, additional_headers){ \
|
||||
const char* msg = JSON_FORMATTER(object); \
|
||||
if(msg == NULL) \
|
||||
{ \
|
||||
cJSON_Delete(object); \
|
||||
send_http_error(conn); \
|
||||
return 500; \
|
||||
} \
|
||||
send_http(conn, "application/json", additional_headers, msg); \
|
||||
cJSON_Delete(object); \
|
||||
free(additional_headers); \
|
||||
return 200; \
|
||||
}
|
||||
|
||||
#define JSON_SENT_OBJECT_AND_HEADERS_CODE(object, code, additional_headers){ \
|
||||
const char* msg = JSON_FORMATTER(object); \
|
||||
if(msg == NULL) \
|
||||
{ \
|
||||
cJSON_Delete(object); \
|
||||
send_http_error(conn); \
|
||||
return 500; \
|
||||
} \
|
||||
send_http_unauth(conn, additional_headers, msg); \
|
||||
cJSON_Delete(object); \
|
||||
free(additional_headers); \
|
||||
return code; \
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* FTL Engine
|
||||
* API Implementation /api/settings
|
||||
*
|
||||
* 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 "api.h"
|
||||
|
||||
int api_settings_web(struct mg_connection *conn)
|
||||
{
|
||||
cJSON *json = JSON_NEW_OBJ();
|
||||
JSON_OBJ_REF_STR(json, "layout", "boxed");
|
||||
JSON_OBJ_REF_STR(json, "language", "en");
|
||||
JSON_SENT_OBJECT(json);
|
||||
}
|
||||
@@ -4802,6 +4802,7 @@ mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
|
||||
int
|
||||
mg_send_http_ok(struct mg_connection *conn,
|
||||
const char *mime_type,
|
||||
const char *additional_headers,
|
||||
long long content_length)
|
||||
{
|
||||
char date[64];
|
||||
@@ -4823,6 +4824,11 @@ mg_send_http_ok(struct mg_connection *conn,
|
||||
date,
|
||||
suggest_connection_header(conn));
|
||||
|
||||
if(additional_headers != NULL && strlen(additional_headers) > 0)
|
||||
{
|
||||
mg_write(conn, additional_headers, strlen(additional_headers));
|
||||
}
|
||||
|
||||
send_no_cache_header(conn);
|
||||
send_additional_header(conn);
|
||||
if (content_length < 0) {
|
||||
|
||||
@@ -939,6 +939,8 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn,
|
||||
* Parameters:
|
||||
* conn: Current connection handle.
|
||||
* mime_type: Set Content-Type for the following content.
|
||||
* additional_header: Pi-hole addition for sending custom additional
|
||||
* information (like settingcookies)
|
||||
* content_length: Size of the following content, if content_length >= 0.
|
||||
* Will set transfer-encoding to chunked, if set to -1.
|
||||
* Return:
|
||||
@@ -946,6 +948,7 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn,
|
||||
*/
|
||||
CIVETWEB_API int mg_send_http_ok(struct mg_connection *conn,
|
||||
const char *mime_type,
|
||||
const char *additional_headers,
|
||||
long long content_length);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user