mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
6050aa5ad7
Signed-off-by: Mcat12 <newtoncat12@yahoo.com>
215 lines
5.3 KiB
C
215 lines
5.3 KiB
C
/* Pi-hole: A black hole for Internet advertisements
|
|
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
|
|
* Network-wide ad blocking via your own hardware.
|
|
*
|
|
* FTL Engine
|
|
* General API commands
|
|
*
|
|
* 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 "cJSON.h"
|
|
|
|
void sendAPIResponse(int sock, char type, char http_code) {
|
|
sendAPIResponseWithCookie(sock, type, http_code, NULL);
|
|
}
|
|
|
|
void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session) {
|
|
char *http_status;
|
|
|
|
switch(http_code) {
|
|
default:
|
|
case OK:
|
|
http_status = "200 OK";
|
|
break;
|
|
case BAD_REQUEST:
|
|
http_status = "400 Bad Request";
|
|
break;
|
|
case INTERNAL_ERROR:
|
|
http_status = "500 Internal Server Error";
|
|
break;
|
|
case NOT_FOUND:
|
|
http_status = "404 Not Found";
|
|
break;
|
|
case UNAUTHORIZED:
|
|
http_status = "401 Unauthorized";
|
|
break;
|
|
}
|
|
|
|
// Send header only for full HTTP requests
|
|
if(type == APIH)
|
|
{
|
|
if(session == NULL) {
|
|
// No cookie to send
|
|
ssend(sock,
|
|
"HTTP/1.0 %s\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n"
|
|
"Content-Type: application/json\n\n{", http_status);
|
|
}
|
|
else {
|
|
// Send cookie
|
|
ssend(sock,
|
|
"HTTP/1.0 %s\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n"
|
|
"Set-Cookie: FTL_SESSION=%ld\nContent-Type: application/json\n\n{", http_status, *session);
|
|
}
|
|
}
|
|
}
|
|
|
|
// session will have the client's valid session written to, if it's not unauthorized
|
|
enum Auth authenticate(char *with_headers, char *payload, long *session, int sock) {
|
|
// First figure out if the client has authenticated before.
|
|
char *sessionStr;
|
|
AuthData *auth = NULL;
|
|
|
|
// Find the cookie header (will contain a long int value)
|
|
if(strstr(with_headers, "Cookie: ") != NULL && (sessionStr = strstr(with_headers, "FTL_SESSION=")) != NULL) {
|
|
// Find the start of the cookie (strtol will stop once it gets to a non-numeric character)
|
|
sessionStr += 12;
|
|
|
|
// Convert to int
|
|
*session = strtol(sessionStr, NULL, 10);
|
|
|
|
if(errno == ERANGE) {
|
|
logg("Failed to decode the authentication cookie");
|
|
return AUTH_UNAUTHORIZED;
|
|
}
|
|
|
|
int i;
|
|
for(i = 0; i < authLength; i++) {
|
|
// Check if the authentication is still valid, has the same session token, and is coming from the same IP
|
|
if(authData[i].valid && authData[i].session == *session && strcmp(clientip[sock], authData[i].ip) == 0) {
|
|
auth = &authData[i];
|
|
time_t currentTime = time(NULL);
|
|
|
|
// Check to see if the session had expired (24 minutes)
|
|
if(currentTime > auth->lastQueryTime + 1440) {
|
|
authData[i].valid = false;
|
|
return AUTH_UNAUTHORIZED;
|
|
}
|
|
|
|
auth->lastQueryTime = currentTime;
|
|
}
|
|
}
|
|
|
|
// auth will be null if we didn't find a matching session
|
|
if(auth == NULL)
|
|
return AUTH_UNAUTHORIZED;
|
|
return AUTH_PREVIOUS;
|
|
}
|
|
|
|
// Otherwise, check if they are trying to authenticate
|
|
cJSON *input_root = cJSON_Parse(payload);
|
|
cJSON *password_json = cJSON_GetObjectItemCaseSensitive(input_root, "password");
|
|
|
|
if(!cJSON_IsString(password_json)) {
|
|
cJSON_Delete(input_root);
|
|
return AUTH_UNAUTHORIZED;
|
|
}
|
|
|
|
char *password = password_json->valuestring;
|
|
|
|
// todo: use real password
|
|
if(strcmp(password, "password") == 0) {
|
|
auth = malloc(sizeof(AuthData));
|
|
|
|
auth->lastQueryTime = time(NULL);
|
|
auth->ip = strdup(clientip[sock]);
|
|
|
|
// Find a unique session number
|
|
while(true) {
|
|
auth->session = random();
|
|
|
|
bool unique = true;
|
|
int i;
|
|
for(i = 0; i < authLength; i++) {
|
|
if(authData[i].session == auth->session) {
|
|
unique = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Found a unique session number
|
|
if(unique)
|
|
break;
|
|
}
|
|
|
|
auth->valid = true;
|
|
|
|
// Add to auth storage
|
|
bool found = false;
|
|
int i;
|
|
for(i = 0; i < authLength; i++) {
|
|
if(!authData[i].valid) {
|
|
// Found an invalid auth we can reuse
|
|
found = true;
|
|
free(authData[i].ip);
|
|
authData[i] = *auth;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found) {
|
|
// Couldn't reuse any existing auth structures
|
|
memory_check(AUTHDATA);
|
|
authData[authLength] = *auth;
|
|
authLength++;
|
|
}
|
|
|
|
*session = auth->session;
|
|
free(auth);
|
|
cJSON_Delete(input_root);
|
|
|
|
return AUTH_NEW;
|
|
}
|
|
|
|
cJSON_Delete(input_root);
|
|
|
|
return AUTH_UNAUTHORIZED;
|
|
}
|
|
|
|
char* getPayload(char *http_message) {
|
|
char *data_start;
|
|
char *unix_newline = strstr(http_message, "\n\n");
|
|
char *win_newline = strstr(http_message, "\r\n\r\n");
|
|
|
|
if(unix_newline != NULL)
|
|
data_start = unix_newline + 2;
|
|
else if(win_newline != NULL)
|
|
data_start = win_newline + 4;
|
|
else
|
|
return NULL;
|
|
|
|
if(strlen(data_start) == 0)
|
|
return NULL;
|
|
|
|
return data_start;
|
|
}
|
|
|
|
bool matchesRegex(char *regex_expression, char *input) {
|
|
regex_t regex;
|
|
int result;
|
|
|
|
result = regcomp(®ex, regex_expression, REG_EXTENDED);
|
|
|
|
if(result != 0) {
|
|
logg("Failed to compile regex");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
result = regexec(®ex, input, 0, NULL, 0);
|
|
regfree(®ex);
|
|
|
|
return result == 0;
|
|
}
|
|
|
|
bool isValidDomain(char *domain) {
|
|
char *valid_chars_regex = "^((-|_)*[a-z0-9]((-|_)*[a-z0-9])*(-|_)*)(\\.(-|_)*([a-z0-9]((-|_)*[a-z0-9])*))*$";
|
|
char *total_length_regex = "^.{1,253}$";
|
|
char *label_length_regex = "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$";
|
|
|
|
return matchesRegex(valid_chars_regex, domain) &&
|
|
matchesRegex(total_length_regex, domain) &&
|
|
matchesRegex(label_length_regex, domain);
|
|
}
|