From 941dfb2493482af871cbb0df6592f49ede69da9d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 18:52:08 +0200 Subject: [PATCH 001/168] Implement HTTP server into FTL, listening on port 4747 by default --- FTL.h | 6 +-- main.c | 12 ++++- routines.h | 3 +- socket.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 163 insertions(+), 13 deletions(-) diff --git a/FTL.h b/FTL.h index a4febb48..8426f5da 100644 --- a/FTL.h +++ b/FTL.h @@ -8,6 +8,8 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ +#define __USE_XOPEN +#define _GNU_SOURCE #include // variable argument lists #include @@ -18,8 +20,6 @@ #include #include #include -#define __USE_XOPEN -#define _GNU_SOURCE #include #include #include @@ -173,7 +173,7 @@ typedef struct { } memoryStruct; enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; -enum { SOCKET }; +enum { SOCKET, API }; logFileNamesStruct files; FTLFileNamesStruct FTLfiles; diff --git a/main.c b/main.c index 2ae0af6e..e22c563b 100644 --- a/main.c +++ b/main.c @@ -62,6 +62,7 @@ int main (int argc, char* argv[]) { // the system without the need for another thread to join with the terminated thread pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + // Start log analyzing thread pthread_t piholelogthread; if(pthread_create( &piholelogthread, &attr, pihole_log_thread, NULL ) != 0) { @@ -69,13 +70,22 @@ int main (int argc, char* argv[]) { killed = 1; } + // Start SOCKET thread pthread_t socket_listenthread; - if(pthread_create( &socket_listenthread, &attr, socket_listenting_thread, NULL ) != 0) + if(pthread_create( &socket_listenthread, &attr, socket_listening_thread, NULL ) != 0) { logg("Unable to open socket listening thread. Exiting..."); killed = 1; } + // Start API thread + pthread_t api_listenthread; + if(pthread_create( &api_listenthread, &attr, api_listening_thread, NULL ) != 0) + { + logg("Unable to open API listening thread. Exiting..."); + killed = 1; + } + while(!killed) { sleepms(100); diff --git a/routines.h b/routines.h index 43444d3a..c1824c6d 100644 --- a/routines.h +++ b/routines.h @@ -38,7 +38,8 @@ void memory_check(int which); void close_socket(char type); void seom(char server_message[], int sock); void swrite(char server_message[], int sock); -void *socket_listenting_thread(void *args); +void *socket_listening_thread(void *args); +void *api_listening_thread(void *args); void process_request(char *client_message, int *sock); bool command(char *client_message, const char* cmd); diff --git a/socket.c b/socket.c index 9a88c8c0..ac365ac0 100644 --- a/socket.c +++ b/socket.c @@ -21,7 +21,7 @@ #define BACKLOG 5 // File descriptors -int socketfd; +int socketfd, apifd; void saveport(int port) { @@ -80,6 +80,9 @@ void bind_to_port(char type, int *socketdescriptor) case SOCKET: port_init = 4711; break; + case API: + port_init = 4747; + break; default: logg("Incompatible socket type %i", (int)type); exit(EXIT_FAILURE); @@ -116,8 +119,18 @@ void bind_to_port(char type, int *socketdescriptor) logg("Error on listening"); exit(EXIT_FAILURE); } - - logg("Listening on port %i for incoming connections", port); + switch(type) + { + case SOCKET: + logg("Listening on port %i for incoming socket connections", port); + break; + case API: + logg("Listening on port %i for incoming API connections", port); + break; + default: + /* That cannot happen */ + break; + } } // Called from main() at graceful shutdown @@ -144,6 +157,18 @@ void swrite(char server_message[SOCKETBUFFERLEN], int sock) logg("WARNING: Socket write returned error code %i", errno); } +void ssend(int sock, const char *format, ...) +{ + char *buffer; + va_list args; + va_start(args, format); + int ret = vasprintf(&buffer, format, args); + va_end(args); + if(ret > 0) + if(!write(sock, buffer, strlen(buffer))) + logg("WARNING: Socket write returned error code %i", errno); +} + int listener(int sockfd) { struct sockaddr_in cli_addr; @@ -167,6 +192,9 @@ void close_socket(char type) // Using global variable here close(socketfd); break; + case API: + close(apifd); + break; default: logg("Incompatible socket type %i, cannot close",(int)type); exit(EXIT_FAILURE); @@ -184,7 +212,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Set thread name char threadname[16]; - sprintf(threadname,"client-%i",sockID); + sprintf(threadname,"socket-%i",sockID); prctl(PR_SET_NAME,threadname,0,0,0); //Receive from client ssize_t n; @@ -210,7 +238,7 @@ void *socket_connection_handler_thread(void *socket_desc) if(sock == 0) { - // Client disconnected by seding EOT or ">quit" + // Client disconnected by sending EOT or ">quit" break; } } @@ -231,7 +259,7 @@ void *socket_connection_handler_thread(void *socket_desc) return 0; } -void *socket_listenting_thread(void *args) +void *socket_listening_thread(void *args) { int *newsock; // We will use the attributes object later to start all threads in detached mode @@ -245,7 +273,7 @@ void *socket_listenting_thread(void *args) // Set thread name prctl(PR_SET_NAME,"socket listener",0,0,0); - // Initialize sockets only after initial log parsing in listenting_thread + // Initialize sockets only after initial log parsing in listening_thread bind_to_port(SOCKET, &socketfd); // Listen as long as FTL is not killed @@ -263,7 +291,118 @@ void *socket_listenting_thread(void *args) if(pthread_create( &socket_connection_thread, &attr, socket_connection_handler_thread, (void*) newsock ) != 0) { // Log the error code description - logg("WARNING: Unable to open clients processing thread, error: %s", strerror(errno)); + logg("WARNING: Unable to open client socket thread, error: %s", strerror(errno)); + } + } + return 0; +} + +void *api_connection_handler_thread(void *socket_desc) +{ + //Get the socket descriptor + int sock = *(int*)socket_desc; + // Store copy only for displaying the debug messages + int sockID = sock; + char client_message[SOCKETBUFFERLEN] = ""; + + // Set thread name + char threadname[16]; + sprintf(threadname,"api-%i",sockID); + prctl(PR_SET_NAME,threadname,0,0,0); + //Receive from client + ssize_t n; + while((n = recv(sock,client_message,SOCKETBUFFERLEN-1, 0))) + { + if (n > 0) + { + char *message = calloc(strlen(client_message)+1,sizeof(char)); + strcpy(message, client_message); + + // Clear client message receive buffer + memset(client_message, 0, sizeof client_message); + + if(strncmp(message, "GET ", 4) == 0) + { + logg("GET request received"); + // HTTP requests can be simple or full. + // A simple request contains one line only, and looks like this: + // GET / + // A full request can contain more than one line and may look like this: + // GET / HTTP/1.1 + // User-Agent: Wget/1.16 (linux-gnueabihf) + // Accept: */* + // Host: 127.0.0.1:4747 + // Connection: Keep-Alive + if(strstr(message, "HTTP/")) + { + // Output HTTP response headers only if we have a full request + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nContent-Type: application/json\n\n"); + } + + // Now we have to transmit the response + + // ... + + // Close connection to show that we reached the end of the transmission + close(sock); + sock = 0; + } + else + { + logg("API received something different"); + } + + free(message); + + if(sock == 0) + { + // Client disconnected + break; + } + } + } + + //Free the socket pointer + if(sock != 0) + close(sock); + free(socket_desc); + + return 0; +} + +void *api_listening_thread(void *args) +{ + int *newsock; + // We will use the attributes object later to start all threads in detached mode + pthread_attr_t attr; + // Initialize thread attributes object with default attribute values + pthread_attr_init(&attr); + // When a detached thread terminates, its resources are automatically released back to + // the system without the need for another thread to join with the terminated thread + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + // Set thread name + prctl(PR_SET_NAME,"API listener",0,0,0); + + // Initialize sockets only after initial log parsing in listening_thread + bind_to_port(API, &apifd); + + // Listen as long as FTL is not killed + while(!killed) + { + // Look for new clients that want to connect + int csck = listener(apifd); + + // Allocate memory used to transport client socket ID to client listening thread + newsock = calloc(1,sizeof(int)); + *newsock = csck; + + pthread_t api_connection_thread; + // Create a new thread + if(pthread_create( &api_connection_thread, &attr, api_connection_handler_thread, (void*) newsock ) != 0) + { + // Log the error code description + logg("WARNING: Unable to open client API thread, error: %s", strerror(errno)); } } return 0; From 3b99d828b79c39e7583c8f56a542f6c6f8fb4ce7 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 19:11:53 +0200 Subject: [PATCH 002/168] Return error 404 if a browser is asking for favicon.ico --- socket.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/socket.c b/socket.c index ac365ac0..607a30b0 100644 --- a/socket.c +++ b/socket.c @@ -321,22 +321,30 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); + logg("%s",message); + if(strncmp(message, "GET ", 4) == 0) { - logg("GET request received"); + if(debug) + logg("API request received"); // HTTP requests can be simple or full. // A simple request contains one line only, and looks like this: - // GET / + // GET /index.html // A full request can contain more than one line and may look like this: - // GET / HTTP/1.1 + // GET /index.html HTTP/1.1 // User-Agent: Wget/1.16 (linux-gnueabihf) // Accept: */* // Host: 127.0.0.1:4747 // Connection: Keep-Alive - if(strstr(message, "HTTP/")) + if(strstr(message, "HTTP/") != NULL) { // Output HTTP response headers only if we have a full request - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nContent-Type: application/json\n\n"); + + // Are we asked for a favicon? + if(strstr(message, "GET /favicon.ico") != NULL) + ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); + else + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nContent-Type: application/json\n\n"); } // Now we have to transmit the response @@ -349,16 +357,14 @@ void *api_connection_handler_thread(void *socket_desc) } else { - logg("API received something different"); + if(debug) + logg("API received something strange"); } free(message); - if(sock == 0) - { - // Client disconnected - break; - } + // Disconnect client + break; } } From 03fc5be4a74dfeff7595753c99df98af7817efb4 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 19:20:20 +0200 Subject: [PATCH 003/168] Interpret HEAD request --- socket.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/socket.c b/socket.c index 607a30b0..c063e483 100644 --- a/socket.c +++ b/socket.c @@ -326,7 +326,7 @@ void *api_connection_handler_thread(void *socket_desc) if(strncmp(message, "GET ", 4) == 0) { if(debug) - logg("API request received"); + logg("API GET request received"); // HTTP requests can be simple or full. // A simple request contains one line only, and looks like this: // GET /index.html @@ -355,6 +355,18 @@ void *api_connection_handler_thread(void *socket_desc) close(sock); sock = 0; } + else if(strncmp(message, "HEAD ", 5) == 0) + { + // HEAD request: We do not send any content at all + if(debug) + logg("API HEAD request received"); + + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); + + // Close connection to show that we reached the end of the transmission + close(sock); + sock = 0; + } else { if(debug) From e159b5626e0c4202a3aeffcc78f3fa3b87975fef Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 19:33:23 +0200 Subject: [PATCH 004/168] We don't allow caching the responds of FTL --- request.c | 2 +- routines.h | 2 +- socket.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/request.c b/request.c index 31e9a528..f218ae9b 100644 --- a/request.c +++ b/request.c @@ -32,7 +32,7 @@ void getQueryTypesOverTime(int *sock); void getVersion(int *sock); void getDBstats(int *sock); -void process_request(char *client_message, int *sock) +void process_socket_request(char *client_message, int *sock) { char EOT[2]; EOT[0] = 0x04; diff --git a/routines.h b/routines.h index 2acf9d7e..e590943f 100644 --- a/routines.h +++ b/routines.h @@ -41,7 +41,7 @@ void swrite(char server_message[], int sock); void *socket_listening_thread(void *args); void *api_listening_thread(void *args); -void process_request(char *client_message, int *sock); +void process_socket_request(char *client_message, int *sock); bool command(char *client_message, const char* cmd); void formatNumber(bool raw, int n, char* buffer); diff --git a/socket.c b/socket.c index c063e483..2ba0fbf7 100644 --- a/socket.c +++ b/socket.c @@ -230,7 +230,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_request(message, &sock); + process_socket_request(message, &sock); free(message); // Release thread lock @@ -344,7 +344,7 @@ void *api_connection_handler_thread(void *socket_desc) if(strstr(message, "GET /favicon.ico") != NULL) ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); else - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nContent-Type: application/json\n\n"); + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nContent-Type: application/json\n\n"); } // Now we have to transmit the response From 19c54b0e0c26398b287c1b295520d9e9352f1ff6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 19:53:20 +0200 Subject: [PATCH 005/168] Replace sprintf() + swrite() by the new ssend() routine. This enhancement removes the fixed buffer we used before and allows for responses having arbitraty lengths --- request.c | 92 ++++++++++++++++-------------------------------------- routines.h | 4 +-- socket.c | 11 ++----- 3 files changed, 31 insertions(+), 76 deletions(-) diff --git a/request.c b/request.c index f218ae9b..69b81ff5 100644 --- a/request.c +++ b/request.c @@ -37,7 +37,6 @@ void process_socket_request(char *client_message, int *sock) char EOT[2]; EOT[0] = 0x04; EOT[1] = 0x00; - char server_message[SOCKETBUFFERLEN]; bool processed = false; if(command(client_message, ">stats")) { @@ -119,7 +118,7 @@ void process_socket_request(char *client_message, int *sock) if(processed) { // Send EOM - seom(server_message, *sock); + seom(*sock); } // Test only at the end if we want to quit or kill @@ -142,8 +141,7 @@ void process_socket_request(char *client_message, int *sock) if(!processed) { - sprintf(server_message,"unknown command: %s\n",client_message); - swrite(server_message, *sock); + ssend(*sock,"unknown command: %s\n",client_message); } } @@ -218,8 +216,6 @@ int cmpdesc(const void *a, const void *b) void getStats(int *sock) { - char server_message[SOCKETBUFFERLEN]; - int blocked = counters.blocked + counters.wildcardblocked; int total = counters.queries - counters.invalidqueries; float percentage = 0.0; @@ -228,19 +224,16 @@ void getStats(int *sock) { percentage = 1e2*blocked/total; } - sprintf(server_message,"domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ + ssend(*sock,"domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ counters.gravity,total,blocked,percentage); - swrite(server_message, *sock); - sprintf(server_message,"unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ + ssend(*sock,"unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ counters.domains,counters.forwardedqueries,counters.cached); - swrite(server_message, *sock); if(debugclients) logg("Sent stats data to client, ID: %i", *sock); } void getOverTime(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i; bool sendit = false; for(i=0; i < counters.overTime; i++) @@ -252,8 +245,7 @@ void getOverTime(int *sock) } if(sendit) { - sprintf(server_message,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); - swrite(server_message, *sock); + ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); } } if(debugclients) @@ -262,7 +254,6 @@ void getOverTime(int *sock) void getTopDomains(char *client_message, int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, temparray[counters.domains][2], count=10, num; bool blocked = command(client_message, ">top-ads"), audit = false, desc = false; @@ -368,15 +359,13 @@ void getTopDomains(char *client_message, int *sock) if(blocked && showblocked && domains[j].blockedcount > 0) { if(audit && domains[j].wildcard) - sprintf(server_message,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); + ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); else - sprintf(server_message,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); - swrite(server_message, *sock); + ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); } else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) { - sprintf(server_message,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); - swrite(server_message, *sock); + ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); } } if(excludedomains != NULL) @@ -392,7 +381,6 @@ void getTopDomains(char *client_message, int *sock) void getTopClients(char *client_message, int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, temparray[counters.clients][2], count=10, num; if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) @@ -440,8 +428,7 @@ void getTopClients(char *client_message, int *sock) if(clients[j].count > 0) { - sprintf(server_message,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); - swrite(server_message, *sock); + ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); } } if(excludeclients != NULL) @@ -453,7 +440,6 @@ void getTopClients(char *client_message, int *sock) void getForwardDestinations(int *sock) { - char server_message[SOCKETBUFFERLEN]; bool allocated = false; int i, temparray[counters.forwarded+1][2]; for(i=0; i < counters.forwarded; i++) @@ -501,8 +487,7 @@ void getForwardDestinations(int *sock) // Send data if count > 0 if(count > 0) { - sprintf(server_message,"%i %i %s %s\n",i,count,ip,name); - swrite(server_message, *sock); + ssend(*sock,"%i %i %s %s\n",i,count,ip,name); } // Free previously allocated memory only if we allocated it @@ -519,20 +504,17 @@ void getForwardDestinations(int *sock) void getForwardNames(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i; for(i=0; i < counters.forwarded; i++) { validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); // Get sorted indices - sprintf(server_message,"%i %i %s %s\n",i,forwarded[i].count,forwarded[i].ip,forwarded[i].name); - swrite(server_message, *sock); + ssend(*sock,"%i %i %s %s\n",i,forwarded[i].count,forwarded[i].ip,forwarded[i].name); } // Add "local" forward destination - sprintf(server_message,"%i %i ::1 local\n",counters.forwarded,counters.cached); - swrite(server_message, *sock); + ssend(*sock,"%i %i ::1 local\n",counters.forwarded,counters.cached); if(debugclients) logg("Sent forward destination names to client, ID: %i", *sock); @@ -541,10 +523,8 @@ void getForwardNames(int *sock) void getQueryTypes(int *sock) { - char server_message[SOCKETBUFFERLEN]; - sprintf(server_message,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); - swrite(server_message, *sock); + ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); if(debugclients) logg("Sent query type data to client, ID: %i", *sock); } @@ -552,7 +532,6 @@ void getQueryTypes(int *sock) void getAllQueries(char *client_message, int *sock) { - char server_message[SOCKETBUFFERLEN]; // Exit before processing any data if requested via config setting if(!config.query_display) @@ -705,15 +684,14 @@ void getAllQueries(char *client_message, int *sock) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - sprintf(server_message,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - sprintf(server_message,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else { - sprintf(server_message,"%i %s %s hidden %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); } - swrite(server_message, *sock); } // Free allocated memory @@ -728,7 +706,6 @@ void getAllQueries(char *client_message, int *sock) void getRecentBlocked(char *client_message, int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, num=1; // Exit before processing any data if requested via config setting @@ -757,8 +734,7 @@ void getRecentBlocked(char *client_message, int *sock) if(queries[i].status == 1 || queries[i].status == 4) { found++; - sprintf(server_message,"%s\n",domains[queries[i].domainID].domain); - swrite(server_message, *sock); + ssend(*sock,"%s\n",domains[queries[i].domainID].domain); } if(found >= num) @@ -770,27 +746,23 @@ void getRecentBlocked(char *client_message, int *sock) void getMemoryUsage(int *sock) { - char server_message[SOCKETBUFFERLEN]; unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); char *structprefix = calloc(2, sizeof(char)); double formated = 0.0; format_memory_size(structprefix, structbytes, &formated); - sprintf(server_message,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); - swrite(server_message, *sock); + ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); free(structprefix); unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; char *dynamicprefix = calloc(2, sizeof(char)); format_memory_size(dynamicprefix, dynamicbytes, &formated); - sprintf(server_message,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); - swrite(server_message, *sock); + ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); free(dynamicprefix); unsigned long int totalbytes = structbytes + dynamicbytes; char *totalprefix = calloc(2, sizeof(char)); format_memory_size(totalprefix, totalbytes, &formated); - sprintf(server_message,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); - swrite(server_message, *sock); + ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); free(totalprefix); if(debugclients) @@ -799,7 +771,6 @@ void getMemoryUsage(int *sock) void getForwardDestinationsOverTime(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, sendit = -1; for(i = 0; i < counters.overTime; i++) { @@ -815,7 +786,7 @@ void getForwardDestinationsOverTime(int *sock) for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - sprintf(server_message, "%i", overTime[i].timestamp); + ssend(*sock, "%i", overTime[i].timestamp); int j; @@ -827,11 +798,10 @@ void getForwardDestinationsOverTime(int *sock) else k = 0; - sprintf(server_message + strlen(server_message), " %i", k); + ssend(*sock, " %i", k); } - sprintf(server_message + strlen(server_message), " %i\n", overTime[i].cached + overTime[i].blocked); - swrite(server_message, *sock); + ssend(*sock, " %i\n", overTime[i].cached + overTime[i].blocked); } } if(debugclients) @@ -840,10 +810,8 @@ void getForwardDestinationsOverTime(int *sock) void getClientID(int *sock) { - char server_message[SOCKETBUFFERLEN]; - sprintf(server_message,"%i\n", *sock); - swrite(server_message, *sock); + ssend(*sock,"%i\n", *sock); if(debugclients) logg("Sent client ID to client, ID: %i", *sock); @@ -851,7 +819,6 @@ void getClientID(int *sock) void getQueryTypesOverTime(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, sendit = -1; for(i = 0; i < counters.overTime; i++) { @@ -867,8 +834,7 @@ void getQueryTypesOverTime(int *sock) for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - sprintf(server_message, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); - swrite(server_message, *sock); + ssend(*sock, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); } } if(debugclients) @@ -877,9 +843,7 @@ void getQueryTypesOverTime(int *sock) void getVersion(int *sock) { - char server_message[SOCKETBUFFERLEN]; - sprintf(server_message,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); - swrite(server_message, *sock); + ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); if(debugclients) logg("Sent version info to client, ID: %i", *sock); @@ -900,9 +864,7 @@ void getDBstats(int *sock) double formated = 0.0; format_memory_size(prefix, filesize, &formated); - char server_message[SOCKETBUFFERLEN]; - sprintf(server_message,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); - swrite(server_message, *sock); + ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); if(debugclients) logg("Sent DB info to client, ID: %i", *sock); diff --git a/routines.h b/routines.h index e590943f..e8d3338c 100644 --- a/routines.h +++ b/routines.h @@ -36,8 +36,8 @@ void pihole_log_flushed(bool message); void memory_check(int which); void close_socket(char type); -void seom(char server_message[], int sock); -void swrite(char server_message[], int sock); +void seom(int sock); +void ssend(int sock, const char *format, ...); void *socket_listening_thread(void *args); void *api_listening_thread(void *args); diff --git a/socket.c b/socket.c index 2ba0fbf7..5ab87897 100644 --- a/socket.c +++ b/socket.c @@ -145,16 +145,9 @@ void removeport(void) fclose(f); } -void seom(char server_message[SOCKETBUFFERLEN], int sock) +void seom(int sock) { - sprintf(server_message,"---EOM---\n\n"); - swrite(server_message, sock); -} - -void swrite(char server_message[SOCKETBUFFERLEN], int sock) -{ - if(!write(sock, server_message, strlen(server_message))) - logg("WARNING: Socket write returned error code %i", errno); + ssend(sock, "---EOM---\n\n"); } void ssend(int sock, const char *format, ...) From 35a1882e3bc1fc9ec2f00c2a0924bf2686ceee16 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:01:54 +0200 Subject: [PATCH 006/168] Add HTTP server tests --- test/test_suite.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_suite.sh b/test/test_suite.sh index 09bfa2ae..121ff880 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -162,3 +162,19 @@ load 'libs/bats-support/load' [[ "${lines[@]}" == *"CREATE TABLE ftl ( id INTEGER PRIMARY KEY NOT NULL, value BLOB NOT NULL );"* ]] [[ "${lines[@]}" == *"INSERT INTO \"ftl\" VALUES(0,1);"* ]] } + +@test "HTTP server: FTL responding correctly to HEAD request" { + run bash -c "curl --head 127.0.0.1:4747" + echo "output: ${lines[@]}" + echo "curl exit code: ${status}" + [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] + [[ ${lines[1]} == "Server: FTL" ]] + [[ ${lines[2]} == "" ]] +} + +@test "HTTP server: FTL responding correctly to GET request" { + run bash -c "curl 127.0.0.1:4747" + echo "output: ${lines[@]}" + echo "curl exit code: ${status}" + [[ "${status}" -eq 0 ]] +} From a4fc9339663ed64c60a3129fe71d68ab6dabcf4e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:08:33 +0200 Subject: [PATCH 007/168] Use silent mode if curl based tests to suppress progress meter display --- test/test_suite.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 121ff880..7e70784b 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -164,7 +164,7 @@ load 'libs/bats-support/load' } @test "HTTP server: FTL responding correctly to HEAD request" { - run bash -c "curl --head 127.0.0.1:4747" + run bash -c "curl --head -s 127.0.0.1:4747" echo "output: ${lines[@]}" echo "curl exit code: ${status}" [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] @@ -173,7 +173,7 @@ load 'libs/bats-support/load' } @test "HTTP server: FTL responding correctly to GET request" { - run bash -c "curl 127.0.0.1:4747" + run bash -c "curl -s 127.0.0.1:4747" echo "output: ${lines[@]}" echo "curl exit code: ${status}" [[ "${status}" -eq 0 ]] From d7d57a85a2c1968cbbe320dcca7317ffcf604336 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:10:47 +0200 Subject: [PATCH 008/168] Show last two lines of FTL logs when waiting for socket to become ready --- test/run.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/run.sh b/test/run.sh index 8e8526f4..e69a20ac 100755 --- a/test/run.sh +++ b/test/run.sh @@ -52,6 +52,9 @@ n=0 until [ $n -ge 45 ]; do nc -vv -z -w 30 127.0.0.1 4711 && break n=$[$n+1] + echo "..." + tail -n2 pihole-FTL.log + echo "..." sleep 1 done From f84b461f3a62f940bdb51d13b04df8b30bff7998 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:23:14 +0200 Subject: [PATCH 009/168] Add a small delay when starting the threads --- main.c | 2 ++ test/test_suite.sh | 1 + 2 files changed, 3 insertions(+) diff --git a/main.c b/main.c index a35634e0..56920b2c 100644 --- a/main.c +++ b/main.c @@ -73,6 +73,7 @@ int main (int argc, char* argv[]) { logg("Unable to open Pi-hole log processing thread. Exiting..."); killed = 1; } + sleepms(100); // Start SOCKET thread pthread_t socket_listenthread; @@ -81,6 +82,7 @@ int main (int argc, char* argv[]) { logg("Unable to open socket listening thread. Exiting..."); killed = 1; } + sleepms(100); // Start API thread pthread_t api_listenthread; diff --git a/test/test_suite.sh b/test/test_suite.sh index 7e70784b..b8ddfc6b 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -170,6 +170,7 @@ load 'libs/bats-support/load' [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] [[ ${lines[1]} == "Server: FTL" ]] [[ ${lines[2]} == "" ]] + [[ "${status}" -eq 0 ]] } @test "HTTP server: FTL responding correctly to GET request" { From 0ef2256fa5b5491aace40aaca4c781b722863c2e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:28:46 +0200 Subject: [PATCH 010/168] Test for both ports 4711 and 4747 before starting the tests --- test/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run.sh b/test/run.sh index e69a20ac..493316de 100755 --- a/test/run.sh +++ b/test/run.sh @@ -50,7 +50,7 @@ git submodule add https://github.com/ztombol/bats-support test/libs/bats-support # Block until FTL is ready, retry once per second for 45 seconds n=0 until [ $n -ge 45 ]; do - nc -vv -z -w 30 127.0.0.1 4711 && break + (nc -vv -z -w 30 127.0.0.1 4711 && nc -z -w 30 127.0.0.1 4747) && break n=$[$n+1] echo "..." tail -n2 pihole-FTL.log From b2688bfd3515bc6594bbfeac0455340777a13371 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 12 Jun 2017 20:48:50 +0200 Subject: [PATCH 011/168] Add type variable to all request subroutines which can later be used to decide whether the output whould be in telnet format, or JSON (possibly even "v2" JSON) --- request.c | 92 +++++++++++++++++++++++++++-------------------------- test/run.sh | 2 +- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/request.c b/request.c index 69b81ff5..a3481097 100644 --- a/request.c +++ b/request.c @@ -16,21 +16,21 @@ #define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) // Local prototypes -void getStats(int *sock); -void getOverTime(int *sock); -void getTopDomains (char *client_message, int *sock); -void getTopClients(char *client_message, int *sock); -void getForwardDestinations(int *sock); -void getForwardNames(int *sock); -void getQueryTypes(int *sock); -void getAllQueries(char *client_message, int *sock); -void getRecentBlocked(char *client_message, int *sock); -void getMemoryUsage(int *sock); -void getForwardDestinationsOverTime(int *sock); -void getClientID(int *sock); -void getQueryTypesOverTime(int *sock); -void getVersion(int *sock); -void getDBstats(int *sock); +void getStats(int *sock, char type); +void getOverTime(int *sock, char type); +void getTopDomains (char *client_message, int *sock, char type); +void getTopClients(char *client_message, int *sock, char type); +void getForwardDestinations(int *sock, char type); +void getForwardNames(int *sock, char type); +void getQueryTypes(int *sock, char type); +void getAllQueries(char *client_message, int *sock, char type); +void getRecentBlocked(char *client_message, int *sock, char type); +void getMemoryUsage(int *sock, char type); +void getForwardDestinationsOverTime(int *sock, char type); +void getClientID(int *sock, char type); +void getQueryTypesOverTime(int *sock, char type); +void getVersion(int *sock, char type); +void getDBstats(int *sock, char type); void process_socket_request(char *client_message, int *sock) { @@ -38,80 +38,82 @@ void process_socket_request(char *client_message, int *sock) EOT[0] = 0x04; EOT[1] = 0x00; bool processed = false; + char type = SOCKET; + if(command(client_message, ">stats")) { processed = true; - getStats(sock); + getStats(sock, type); } else if(command(client_message, ">overTime")) { processed = true; - getOverTime(sock); + getOverTime(sock, type); } else if(command(client_message, ">top-domains") || command(client_message, ">top-ads")) { processed = true; - getTopDomains(client_message, sock); + getTopDomains(client_message, sock, type); } else if(command(client_message, ">top-clients")) { processed = true; - getTopClients(client_message, sock); + getTopClients(client_message, sock, type); } else if(command(client_message, ">forward-dest")) { processed = true; - getForwardDestinations(sock); + getForwardDestinations(sock, type); } else if(command(client_message, ">forward-names")) { processed = true; - getForwardNames(sock); + getForwardNames(sock, type); } else if(command(client_message, ">querytypes")) { processed = true; - getQueryTypes(sock); + getQueryTypes(sock, type); } else if(command(client_message, ">getallqueries")) { processed = true; - getAllQueries(client_message, sock); + getAllQueries(client_message, sock, type); } else if(command(client_message, ">recentBlocked")) { processed = true; - getRecentBlocked(client_message, sock); + getRecentBlocked(client_message, sock, type); } else if(command(client_message, ">memory")) { processed = true; - getMemoryUsage(sock); + getMemoryUsage(sock, type); } else if(command(client_message, ">clientID")) { processed = true; - getClientID(sock); + getClientID(sock, type); } else if(command(client_message, ">ForwardedoverTime")) { processed = true; - getForwardDestinationsOverTime(sock); + getForwardDestinationsOverTime(sock, type); } else if(command(client_message, ">QueryTypesoverTime")) { processed = true; - getQueryTypesOverTime(sock); + getQueryTypesOverTime(sock, type); } else if(command(client_message, ">version")) { processed = true; - getVersion(sock); + getVersion(sock, type); } else if(command(client_message, ">dbstats")) { processed = true; - getDBstats(sock); + getDBstats(sock, type); } // End of queryable commands @@ -214,7 +216,7 @@ int cmpdesc(const void *a, const void *b) return 0; } -void getStats(int *sock) +void getStats(int *sock, char type) { int blocked = counters.blocked + counters.wildcardblocked; int total = counters.queries - counters.invalidqueries; @@ -232,7 +234,7 @@ void getStats(int *sock) logg("Sent stats data to client, ID: %i", *sock); } -void getOverTime(int *sock) +void getOverTime(int *sock, char type) { int i; bool sendit = false; @@ -252,7 +254,7 @@ void getOverTime(int *sock) logg("Sent overTime data to client, ID: %i", *sock); } -void getTopDomains(char *client_message, int *sock) +void getTopDomains(char *client_message, int *sock, char type) { int i, temparray[counters.domains][2], count=10, num; bool blocked = command(client_message, ">top-ads"), audit = false, desc = false; @@ -379,7 +381,7 @@ void getTopDomains(char *client_message, int *sock) } } -void getTopClients(char *client_message, int *sock) +void getTopClients(char *client_message, int *sock, char type) { int i, temparray[counters.clients][2], count=10, num; @@ -438,7 +440,7 @@ void getTopClients(char *client_message, int *sock) } -void getForwardDestinations(int *sock) +void getForwardDestinations(int *sock, char type) { bool allocated = false; int i, temparray[counters.forwarded+1][2]; @@ -502,7 +504,7 @@ void getForwardDestinations(int *sock) } -void getForwardNames(int *sock) +void getForwardNames(int *sock, char type) { int i; @@ -521,7 +523,7 @@ void getForwardNames(int *sock) } -void getQueryTypes(int *sock) +void getQueryTypes(int *sock, char type) { ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); @@ -530,7 +532,7 @@ void getQueryTypes(int *sock) } -void getAllQueries(char *client_message, int *sock) +void getAllQueries(char *client_message, int *sock, char type) { // Exit before processing any data if requested via config setting @@ -704,7 +706,7 @@ void getAllQueries(char *client_message, int *sock) logg("Sent all queries data to client, ID: %i", *sock); } -void getRecentBlocked(char *client_message, int *sock) +void getRecentBlocked(char *client_message, int *sock, char type) { int i, num=1; @@ -744,7 +746,7 @@ void getRecentBlocked(char *client_message, int *sock) } } -void getMemoryUsage(int *sock) +void getMemoryUsage(int *sock, char type) { unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); char *structprefix = calloc(2, sizeof(char)); @@ -769,7 +771,7 @@ void getMemoryUsage(int *sock) logg("Sent memory data to client, ID: %i", *sock); } -void getForwardDestinationsOverTime(int *sock) +void getForwardDestinationsOverTime(int *sock, char type) { int i, sendit = -1; for(i = 0; i < counters.overTime; i++) @@ -808,7 +810,7 @@ void getForwardDestinationsOverTime(int *sock) logg("Sent overTime forwarded data to client, ID: %i", *sock); } -void getClientID(int *sock) +void getClientID(int *sock, char type) { ssend(*sock,"%i\n", *sock); @@ -817,7 +819,7 @@ void getClientID(int *sock) logg("Sent client ID to client, ID: %i", *sock); } -void getQueryTypesOverTime(int *sock) +void getQueryTypesOverTime(int *sock, char type) { int i, sendit = -1; for(i = 0; i < counters.overTime; i++) @@ -841,7 +843,7 @@ void getQueryTypesOverTime(int *sock) logg("Sent overTime query types data to client, ID: %i", *sock); } -void getVersion(int *sock) +void getVersion(int *sock, char type) { ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); @@ -849,7 +851,7 @@ void getVersion(int *sock) logg("Sent version info to client, ID: %i", *sock); } -void getDBstats(int *sock) +void getDBstats(int *sock, char type) { // Get file details struct stat st; diff --git a/test/run.sh b/test/run.sh index 493316de..79b8bb71 100755 --- a/test/run.sh +++ b/test/run.sh @@ -50,7 +50,7 @@ git submodule add https://github.com/ztombol/bats-support test/libs/bats-support # Block until FTL is ready, retry once per second for 45 seconds n=0 until [ $n -ge 45 ]; do - (nc -vv -z -w 30 127.0.0.1 4711 && nc -z -w 30 127.0.0.1 4747) && break + (nc -vv -z -w 30 127.0.0.1 4711 && nc -vv -z -w 30 127.0.0.1 4747) && break n=$[$n+1] echo "..." tail -n2 pihole-FTL.log From 1d29630db2409ed558828d552535a7da1a8cf571 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 12 Jun 2017 21:32:53 -0400 Subject: [PATCH 012/168] Implement API JSON output for summary Currently using the cJSON library, as it's entirely contained within one source and one header file and is very easy to use. By adding on the API output formats to the previous raw socket code, we can simply change the output format when the response type is `API` instead of `SOCKET`. As shown in the implemented summary API code, this should be very simple for most commands. --- Makefile | 5 +- cJSON.c | 2634 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cJSON.h | 263 ++++++ request.c | 39 +- routines.h | 2 +- socket.c | 10 +- 6 files changed, 2943 insertions(+), 10 deletions(-) create mode 100644 cJSON.c create mode 100644 cJSON.h diff --git a/Makefile b/Makefile index 6cb3df85..ab0b474a 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,10 @@ $(ODIR): $(ODIR)/sqlite3.o: sqlite3.c $(CC) -c -o $@ $< $(CCFLAGS) -pihole-FTL: $(_OBJ) $(ODIR)/sqlite3.o +$(ODIR)/cJSON.o: cJSON.c + $(CC) -c -o $@ $< $(CCFLAGS) + +pihole-FTL: $(_OBJ) $(ODIR)/sqlite3.o $(ODIR)/cJSON.o $(CC) -v $(CCFLAGS) -o $@ $^ $(LIBS) .PHONY: clean force install diff --git a/cJSON.c b/cJSON.c new file mode 100644 index 00000000..7a97a86f --- /dev/null +++ b/cJSON.c @@ -0,0 +1,2634 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#define true ((cJSON_bool)1) +#define false ((cJSON_bool)0) + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 5) || (CJSON_VERSION_PATCH != 4) +#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(*allocate)(size_t size); + void (*deallocate)(void *pointer); + void *(*reallocate)(void *pointer, size_t size); +} internal_hooks; + +static internal_hooks global_hooks = { malloc, free, realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + if (!(copy = (unsigned char*)hooks->allocate(length))) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ +if (hooks == NULL) +{ +/* Reset hooks */ +global_hooks.allocate = malloc; +global_hooks.deallocate = free; +global_hooks.reallocate = realloc; +return; +} + +global_hooks.allocate = malloc; +if (hooks->malloc_fn != NULL) +{ +global_hooks.allocate = hooks->malloc_fn; +} + +global_hooks.deallocate = free; +if (hooks->free_fn != NULL) +{ +global_hooks.deallocate = hooks->free_fn; +} + +/* use realloc only if both free and malloc are used */ +global_hooks.reallocate = NULL; +if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) +{ +global_hooks.reallocate = realloc; +} +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ +cJSON *next = NULL; +while (item != NULL) +{ +next = item->next; +if (!(item->type & cJSON_IsReference) && (item->child != NULL)) +{ +cJSON_Delete(item->child); +} +if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) +{ +global_hooks.deallocate(item->valuestring); +} +if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) +{ +global_hooks.deallocate(item->string); +} +global_hooks.deallocate(item); +item = next; +} +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +#define cannot_read(buffer, size) (!can_read(buffer, size)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ +double number = 0; +unsigned char *after_end = NULL; +unsigned char number_c_string[64]; +unsigned char decimal_point = get_decimal_point(); +size_t i = 0; + +if ((input_buffer == NULL) || (input_buffer->content == NULL)) +{ +return false; +} + +/* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ +for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) +{ +switch (buffer_at_offset(input_buffer)[i]) +{ +case '0': +case '1': +case '2': +case '3': +case '4': +case '5': +case '6': +case '7': +case '8': +case '9': +case '+': +case '-': +case 'e': +case 'E': +number_c_string[i] = buffer_at_offset(input_buffer)[i]; +break; + +case '.': +number_c_string[i] = decimal_point; +break; + +default: +goto loop_end; +} +} +loop_end: + number_c_string[i] = '\0'; + +number = strtod((const char*)number_c_string, (char**)&after_end); +if (number_c_string == after_end) +{ +return false; /* parse_error */ +} + +item->valuedouble = number; + +/* use saturation in case of overflow */ +if (number >= INT_MAX) +{ +item->valueint = INT_MAX; +} +else if (number <= INT_MIN) +{ +item->valueint = INT_MIN; +} +else +{ +item->valueint = (int)number; +} + +item->type = cJSON_Number; + +input_buffer->offset += (size_t)(after_end - number_c_string); +return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ +if (number >= INT_MAX) +{ +object->valueint = INT_MAX; +} +else if (number <= INT_MIN) +{ +object->valueint = INT_MIN; +} +else +{ +object->valueint = (int)number; +} + +return object->valuedouble = number; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26]; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occured */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + + fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ +const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; +const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; +unsigned char *output_pointer = NULL; +unsigned char *output = NULL; + +/* not a string */ +if (buffer_at_offset(input_buffer)[0] != '\"') +{ +goto fail; +} + +{ +/* calculate approximate size of the output (overestimate) */ +size_t allocation_length = 0; +size_t skipped_bytes = 0; +while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) +{ +/* is escape sequence */ +if (input_end[0] == '\\') +{ +if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) +{ +/* prevent buffer overflow when last input character is a backslash */ +goto fail; +} +skipped_bytes++; +input_end++; +} +input_end++; +} +if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) +{ +goto fail; /* string ended unexpectedly */ +} + +/* This is at most how much we need for the output */ +allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; +output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); +if (output == NULL) +{ +goto fail; /* allocation failure */ +} +} + +output_pointer = output; +/* loop through the string literal */ +while (input_pointer < input_end) +{ +if (*input_pointer != '\\') +{ +*output_pointer++ = *input_pointer++; +} +/* escape sequence */ +else +{ +unsigned char sequence_length = 2; +if ((input_end - input_pointer) < 1) +{ +goto fail; +} + +switch (input_pointer[1]) +{ +case 'b': +*output_pointer++ = '\b'; +break; +case 'f': +*output_pointer++ = '\f'; +break; +case 'n': +*output_pointer++ = '\n'; +break; +case 'r': +*output_pointer++ = '\r'; +break; +case 't': +*output_pointer++ = '\t'; +break; +case '\"': +case '\\': +case '/': +*output_pointer++ = input_pointer[1]; +break; + +/* UTF-16 literal */ +case 'u': +sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); +if (sequence_length == 0) +{ +/* failed to convert UTF16-literal to UTF-8 */ +goto fail; +} +break; + +default: +goto fail; +} +input_pointer += sequence_length; +} +} + +/* zero terminate the output */ +*output_pointer = '\0'; + +item->type = cJSON_String; +item->valuestring = (char*)output; + +input_buffer->offset = (size_t) (input_end - input_buffer->content); +input_buffer->offset++; + +return true; + +fail: +if (output != NULL) +{ +input_buffer->hooks.deallocate(output); +} + +if (input_pointer != NULL) +{ +input_buffer->offset = (size_t)(input_pointer - input_buffer->content); +} + +return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = strlen((const char*)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(&buffer))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + + fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + else + { + global_error = local_error; + } + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(256); + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->length); + buffer->buffer = NULL; + if (printed == NULL) { + goto fail; + } + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + + fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) +{ +printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + +if (len < 0) +{ +return false; +} + +p.buffer = (unsigned char*)buf; +p.length = (size_t)len; +p.offset = 0; +p.noalloc = true; +p.format = fmt; +p.hooks = global_hooks; + +return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ +if ((input_buffer == NULL) || (input_buffer->content == NULL)) +{ +return false; /* no input */ +} + +/* parse the different types of values */ +/* null */ +if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) +{ +item->type = cJSON_NULL; +input_buffer->offset += 4; +return true; +} +/* false */ +if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) +{ +item->type = cJSON_False; +input_buffer->offset += 5; +return true; +} +/* true */ +if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) +{ +item->type = cJSON_True; +item->valueint = 1; +input_buffer->offset += 4; +return true; +} +/* string */ +if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) +{ +return parse_string(item, input_buffer); +} +/* number */ +if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) +{ +return parse_number(item, input_buffer); +} +/* array */ +if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) +{ +return parse_array(item, input_buffer); +} +/* object */ +if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) +{ +return parse_object(item, input_buffer); +} + + +return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + if (!output_buffer->noalloc) + { + output_buffer->hooks.deallocate(output_buffer->buffer); + } + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ +cJSON *head = NULL; /* head of the linked list */ +cJSON *current_item = NULL; + +if (input_buffer->depth >= CJSON_NESTING_LIMIT) +{ +return false; /* to deeply nested */ +} +input_buffer->depth++; + +if (buffer_at_offset(input_buffer)[0] != '[') +{ +/* not an array */ +goto fail; +} + +input_buffer->offset++; +buffer_skip_whitespace(input_buffer); +if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) +{ +/* empty array */ +goto success; +} + +/* check if we skipped to the end of the buffer */ +if (cannot_access_at_index(input_buffer, 0)) +{ +input_buffer->offset--; +goto fail; +} + +/* step back to character in front of the first element */ +input_buffer->offset--; +/* loop through the comma separated array elements */ +do +{ +/* allocate next item */ +cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); +if (new_item == NULL) +{ +goto fail; /* allocation failure */ +} + +/* attach next item to list */ +if (head == NULL) +{ +/* start the linked list */ +current_item = head = new_item; +} +else +{ +/* add to the end and advance */ +current_item->next = new_item; +new_item->prev = current_item; +current_item = new_item; +} + +/* parse next value */ +input_buffer->offset++; +buffer_skip_whitespace(input_buffer); +if (!parse_value(current_item, input_buffer)) +{ +goto fail; /* failed to parse value */ +} +buffer_skip_whitespace(input_buffer); +} +while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + +if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') +{ +goto fail; /* expected end of array */ +} + +success: + input_buffer->depth--; + +item->type = cJSON_Array; +item->child = head; + +input_buffer->offset++; + +return true; + +fail: +if (head != NULL) +{ +cJSON_Delete(head); +} + +return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ +cJSON *head = NULL; /* linked list head */ +cJSON *current_item = NULL; + +if (input_buffer->depth >= CJSON_NESTING_LIMIT) +{ +return false; /* to deeply nested */ +} +input_buffer->depth++; + +if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) +{ +goto fail; /* not an object */ +} + +input_buffer->offset++; +buffer_skip_whitespace(input_buffer); +if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) +{ +goto success; /* empty object */ +} + +/* check if we skipped to the end of the buffer */ +if (cannot_access_at_index(input_buffer, 0)) +{ +input_buffer->offset--; +goto fail; +} + +/* step back to character in front of the first element */ +input_buffer->offset--; +/* loop through the comma separated array elements */ +do +{ +/* allocate next item */ +cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); +if (new_item == NULL) +{ +goto fail; /* allocation failure */ +} + +/* attach next item to list */ +if (head == NULL) +{ +/* start the linked list */ +current_item = head = new_item; +} +else +{ +/* add to the end and advance */ +current_item->next = new_item; +new_item->prev = current_item; +current_item = new_item; +} + +/* parse the name of the child */ +input_buffer->offset++; +buffer_skip_whitespace(input_buffer); +if (!parse_string(current_item, input_buffer)) +{ +goto fail; /* faile to parse name */ +} +buffer_skip_whitespace(input_buffer); + +/* swap valuestring and string, because we parsed the name */ +current_item->string = current_item->valuestring; +current_item->valuestring = NULL; + +if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) +{ +goto fail; /* invalid object */ +} + +/* parse the value */ +input_buffer->offset++; +buffer_skip_whitespace(input_buffer); +if (!parse_value(current_item, input_buffer)) +{ +goto fail; /* failed to parse value */ +} +buffer_skip_whitespace(input_buffer); +} +while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + +if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) +{ +goto fail; /* expected end of object */ +} + +success: + input_buffer->depth--; + +item->type = cJSON_Object; +item->child = head; + +input_buffer->offset++; +return true; + +fail: +if (head != NULL) +{ +cJSON_Delete(head); +} + +return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *c = array->child; + size_t i = 0; + while(c) + { + i++; + c = c->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)i; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *ref = cJSON_New_Item(hooks); + if (!ref) + { + return NULL; + } + memcpy(ref, item, sizeof(cJSON)); + ref->string = NULL; + ref->type |= cJSON_IsReference; + ref->next = ref->prev = NULL; + return ref; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ +cJSON *child = NULL; + +if ((item == NULL) || (array == NULL)) +{ +return; +} + +child = array->child; + +if (child == NULL) +{ +/* list is empty, start new one */ +array->child = item; +} +else +{ +/* append to the end */ +while (child->next) +{ +child = child->next; +} +suffix_object(child, item); +} +} + +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ +/* call cJSON_AddItemToObjectCS for code reuse */ +cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); +/* remove cJSON_StringIsConst flag */ +item->type &= ~cJSON_StringIsConst; +} + +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ +if (!item) +{ +return; +} +if (!(item->type & cJSON_StringIsConst) && item->string) +{ +global_hooks.deallocate(item->string); +} +item->string = (char*)string; +item->type |= cJSON_StringIsConst; +cJSON_AddItemToArray(object, item); +} +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ +cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ +cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ +if ((parent == NULL) || (item == NULL)) +{ +return NULL; +} + +if (item->prev != NULL) +{ +/* not the first element */ +item->prev->next = item->next; +} +if (item->next != NULL) +{ +/* not the last element */ +item->next->prev = item->prev; +} + +if (item == parent->child) +{ +/* first element */ +parent->child = item->next; +} +/* make sure the detached item doesn't point anywhere anymore */ +item->prev = NULL; +item->next = NULL; + +return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ +if (which < 0) +{ +return NULL; +} + +return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ +cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ +cJSON *to_detach = cJSON_GetObjectItem(object, string); + +return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ +cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + +return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ +cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ +cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ +cJSON *after_inserted = NULL; + +if (which < 0) +{ +return; +} + +after_inserted = get_array_item(array, (size_t)which); +if (after_inserted == NULL) +{ +cJSON_AddItemToArray(array, newitem); +return; +} + +newitem->next = after_inserted; +newitem->prev = after_inserted->prev; +after_inserted->prev = newitem; +if (after_inserted == array->child) +{ +array->child = newitem; +} +else +{ +newitem->prev->next = newitem; +} +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ +if ((parent == NULL) || (replacement == NULL)) +{ +return false; +} + +if (replacement == item) +{ +return true; +} + +replacement->next = item->next; +replacement->prev = item->prev; + +if (replacement->next != NULL) +{ +replacement->next->prev = replacement; +} +if (replacement->prev != NULL) +{ +replacement->prev->next = replacement; +} +if (parent->child == item) +{ +parent->child = replacement; +} + +item->next = NULL; +item->prev = NULL; +cJSON_Delete(item); + +return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ +if (which < 0) +{ +return; +} + +cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ +if (replacement == NULL) +{ +return false; +} + +/* replace the name in the replacement */ +if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) +{ +cJSON_free(replacement->string); +} +replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); +replacement->type &= ~cJSON_StringIsConst; + +cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); + +return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ +replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ +replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) +{ +cJSON *item = cJSON_New_Item(&global_hooks); +if(item) +{ +item->type = b ? cJSON_True : cJSON_False; +} + +return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if (count < 0) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if (count < 0) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if (count < 0) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if (count < 0) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + + fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + unsigned char *into = (unsigned char*)json; + while (*json) + { + if (*json == ' ') + { + json++; + } + else if (*json == '\t') + { + /* Whitespace characters. */ + json++; + } + else if (*json == '\r') + { + json++; + } + else if (*json=='\n') + { + json++; + } + else if ((*json == '/') && (json[1] == '/')) + { + /* double-slash comments, to end of line. */ + while (*json && (*json != '\n')) + { + json++; + } + } + else if ((*json == '/') && (json[1] == '*')) + { + /* multiline comments. */ + while (*json && !((*json == '*') && (json[1] == '/'))) + { + json++; + } + json += 2; + } + else if (*json == '\"') + { + /* string literals, which are \" sensitive. */ + *into++ = (unsigned char)*json++; + while (*json && (*json != '\"')) + { + if (*json == '\\') + { + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + else + { + /* All other characters. */ + *into++ = (unsigned char)*json++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (a->valuedouble == b->valuedouble) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + cJSON *b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} \ No newline at end of file diff --git a/cJSON.h b/cJSON.h new file mode 100644 index 00000000..51b0aea2 --- /dev/null +++ b/cJSON.h @@ -0,0 +1,263 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 5 +#define CJSON_VERSION_PATCH 4 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type __stdcall +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall +#endif +#else /* !WIN32 */ +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will +need to be released. With recurse!=0, it will duplicate any children connected to the item. +The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + + +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); + +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Macros for creating things quickly. */ +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) +#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s)) + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/request.c b/request.c index a3481097..05c615bb 100644 --- a/request.c +++ b/request.c @@ -10,6 +10,7 @@ #include "FTL.h" #include "version.h" +#include "cJSON.h" // Private #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) @@ -32,13 +33,12 @@ void getQueryTypesOverTime(int *sock, char type); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); -void process_socket_request(char *client_message, int *sock) +void process_socket_request(char *client_message, int *sock, char type) { char EOT[2]; EOT[0] = 0x04; EOT[1] = 0x00; bool processed = false; - char type = SOCKET; if(command(client_message, ">stats")) { @@ -155,6 +155,16 @@ bool command(char *client_message, const char* cmd) return false; } +void sendAPIResponse(int sock, char *content) { + ssend( + sock, + "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" + "Content-Type: application/json\nContent-Length: %i\n\n%s", + strlen(content), + content + ); +} + // void formatNumber(bool raw, int n, char* buffer) // { // if(raw) @@ -226,10 +236,27 @@ void getStats(int *sock, char type) { percentage = 1e2*blocked/total; } - ssend(*sock,"domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ - counters.gravity,total,blocked,percentage); - ssend(*sock,"unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ - counters.domains,counters.forwardedqueries,counters.cached); + + if(type == SOCKET) { + ssend(*sock, "domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ + counters.gravity, total, blocked, percentage); + ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ + counters.domains, counters.forwardedqueries, counters.cached); + } + else if(type == API) { + cJSON *response = cJSON_CreateObject(); + + cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); + cJSON_AddNumberToObject(response, "dns_queries_today", total); + cJSON_AddNumberToObject(response, "ads_blocked_today", blocked); + cJSON_AddNumberToObject(response, "ads_percentage_today", percentage); + cJSON_AddNumberToObject(response, "unique_domains", counters.domains); + cJSON_AddNumberToObject(response, "queries_forwarded", counters.forwardedqueries); + cJSON_AddNumberToObject(response, "queries_cached", counters.cached); + + sendAPIResponse(*sock, cJSON_Print(response)); + } + if(debugclients) logg("Sent stats data to client, ID: %i", *sock); } diff --git a/routines.h b/routines.h index e8d3338c..a8230331 100644 --- a/routines.h +++ b/routines.h @@ -41,7 +41,7 @@ void ssend(int sock, const char *format, ...); void *socket_listening_thread(void *args); void *api_listening_thread(void *args); -void process_socket_request(char *client_message, int *sock); +void process_socket_request(char *client_message, int *sock, char type); bool command(char *client_message, const char* cmd); void formatNumber(bool raw, int n, char* buffer); diff --git a/socket.c b/socket.c index 5ab87897..a77dd893 100644 --- a/socket.c +++ b/socket.c @@ -223,7 +223,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_socket_request(message, &sock); + process_socket_request(message, &sock, SOCKET); free(message); // Release thread lock @@ -336,8 +336,14 @@ void *api_connection_handler_thread(void *socket_desc) // Are we asked for a favicon? if(strstr(message, "GET /favicon.ico") != NULL) ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); + else if(strstr(message, "GET /stats/summary") != NULL) + process_socket_request(">stats", &sock, API); else - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nContent-Type: application/json\n\n"); + ssend( + sock, + "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" + "Content-Type: application/json\n\n{status: \"not_found\"}" + ); } // Now we have to transmit the response From 7617293c970f7c8e490dd67699e5ede57160fa74 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 12 Jun 2017 21:47:53 -0400 Subject: [PATCH 013/168] Only show API request HTTP data when debugging --- socket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/socket.c b/socket.c index a77dd893..9c9ef014 100644 --- a/socket.c +++ b/socket.c @@ -314,7 +314,8 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); - logg("%s",message); + if(debug) + logg("%s", message); if(strncmp(message, "GET ", 4) == 0) { From 5f59aff7c6082dae2ed7fbdb7ddd2c9e5c8d99d0 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 12 Jun 2017 22:06:54 -0400 Subject: [PATCH 014/168] Add Content-Length to 404 API response --- socket.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/socket.c b/socket.c index 9c9ef014..2a92fa59 100644 --- a/socket.c +++ b/socket.c @@ -343,14 +343,10 @@ void *api_connection_handler_thread(void *socket_desc) ssend( sock, "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" - "Content-Type: application/json\n\n{status: \"not_found\"}" + "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\"}" ); } - // Now we have to transmit the response - - // ... - // Close connection to show that we reached the end of the transmission close(sock); sock = 0; From e20fe2a53384d8fb7bb601c40dfc73650da2899c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 10:45:17 +0200 Subject: [PATCH 015/168] Output JSON data w/o using any extra JSON module --- request.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/request.c b/request.c index 05c615bb..d3d171c8 100644 --- a/request.c +++ b/request.c @@ -244,17 +244,24 @@ void getStats(int *sock, char type) counters.domains, counters.forwardedqueries, counters.cached); } else if(type == API) { - cJSON *response = cJSON_CreateObject(); + // cJSON *response = cJSON_CreateObject(); - cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); - cJSON_AddNumberToObject(response, "dns_queries_today", total); - cJSON_AddNumberToObject(response, "ads_blocked_today", blocked); - cJSON_AddNumberToObject(response, "ads_percentage_today", percentage); - cJSON_AddNumberToObject(response, "unique_domains", counters.domains); - cJSON_AddNumberToObject(response, "queries_forwarded", counters.forwardedqueries); - cJSON_AddNumberToObject(response, "queries_cached", counters.cached); + // cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); + // cJSON_AddNumberToObject(response, "dns_queries_today", total); + // cJSON_AddNumberToObject(response, "ads_blocked_today", blocked); + // cJSON_AddNumberToObject(response, "ads_percentage_today", percentage); + // cJSON_AddNumberToObject(response, "unique_domains", counters.domains); + // cJSON_AddNumberToObject(response, "queries_forwarded", counters.forwardedqueries); + // cJSON_AddNumberToObject(response, "queries_cached", counters.cached); - sendAPIResponse(*sock, cJSON_Print(response)); + // sendAPIResponse(*sock, cJSON_Print(response)); + char *sendbuffer; + int ret = asprintf(&sendbuffer,"{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); + if(ret > 0) + sendAPIResponse(*sock,sendbuffer); + else + logg("Error allocating memory for API response"); + free(sendbuffer); } if(debugclients) From b15f537d14dddb0a27d5038936150dc549ee65fe Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 11:03:39 +0200 Subject: [PATCH 016/168] Clean socket routines, move detection of what the client is asking for into process_api_request() (request.c) much like we already do it for socket requests --- FTL.h | 2 +- request.c | 54 ++++++++++++++++++++++++++++++++++++++++++------------ routines.h | 3 ++- socket.c | 22 +++++++++------------- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/FTL.h b/FTL.h index 9dd3aa70..c664da79 100644 --- a/FTL.h +++ b/FTL.h @@ -182,7 +182,7 @@ typedef struct { } memoryStruct; enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; -enum { SOCKET, API }; +enum { SOCKET, API, APIH }; logFileNamesStruct files; FTLFileNamesStruct FTLfiles; diff --git a/request.c b/request.c index d3d171c8..661dc553 100644 --- a/request.c +++ b/request.c @@ -33,12 +33,13 @@ void getQueryTypesOverTime(int *sock, char type); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); -void process_socket_request(char *client_message, int *sock, char type) +void process_socket_request(char *client_message, int *sock) { char EOT[2]; EOT[0] = 0x04; EOT[1] = 0x00; bool processed = false; + char type = SOCKET; if(command(client_message, ">stats")) { @@ -147,6 +148,28 @@ void process_socket_request(char *client_message, int *sock, char type) } } +void process_api_request(char *client_message, int *sock, bool header) +{ + bool processed = false; + char type; + if(header) + type = APIH; + else + type = API; + + if(command(client_message, "GET /stats/summary")) + { + processed = true; + getStats(sock, type); + } + if(!processed && header) + { + ssend(*sock, + "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" + "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\"}"); + } +} + bool command(char *client_message, const char* cmd) { if(strstr(client_message,cmd) != NULL) @@ -155,14 +178,21 @@ bool command(char *client_message, const char* cmd) return false; } -void sendAPIResponse(int sock, char *content) { - ssend( - sock, - "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" - "Content-Type: application/json\nContent-Length: %i\n\n%s", - strlen(content), - content - ); +void sendAPIResponse(int sock, char *content, char type) { + if(type == APIH) + { + // Send header and payload + ssend(sock, + "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" + "Content-Type: application/json\nContent-Length: %i\n\n%s", + strlen(content), + content); + } + else + { + // Simple request: Don't send header, only payload + ssend(sock,"%s",content); + } } // void formatNumber(bool raw, int n, char* buffer) @@ -243,7 +273,7 @@ void getStats(int *sock, char type) ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ counters.domains, counters.forwardedqueries, counters.cached); } - else if(type == API) { + else if(type == API || type == APIH) { // cJSON *response = cJSON_CreateObject(); // cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); @@ -256,9 +286,9 @@ void getStats(int *sock, char type) // sendAPIResponse(*sock, cJSON_Print(response)); char *sendbuffer; - int ret = asprintf(&sendbuffer,"{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); + int ret = asprintf(&sendbuffer, "{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); if(ret > 0) - sendAPIResponse(*sock,sendbuffer); + sendAPIResponse(*sock, sendbuffer, type); else logg("Error allocating memory for API response"); free(sendbuffer); diff --git a/routines.h b/routines.h index a8230331..aae0aa29 100644 --- a/routines.h +++ b/routines.h @@ -41,7 +41,8 @@ void ssend(int sock, const char *format, ...); void *socket_listening_thread(void *args); void *api_listening_thread(void *args); -void process_socket_request(char *client_message, int *sock, char type); +void process_socket_request(char *client_message, int *sock); +void process_api_request(char *client_message, int *sock, bool header); bool command(char *client_message, const char* cmd); void formatNumber(bool raw, int n, char* buffer); diff --git a/socket.c b/socket.c index 2a92fa59..8411dcd3 100644 --- a/socket.c +++ b/socket.c @@ -223,7 +223,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_socket_request(message, &sock, SOCKET); + process_socket_request(message, &sock); free(message); // Release thread lock @@ -330,23 +330,19 @@ void *api_connection_handler_thread(void *socket_desc) // Accept: */* // Host: 127.0.0.1:4747 // Connection: Keep-Alive + bool header = false; if(strstr(message, "HTTP/") != NULL) { // Output HTTP response headers only if we have a full request - - // Are we asked for a favicon? - if(strstr(message, "GET /favicon.ico") != NULL) - ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); - else if(strstr(message, "GET /stats/summary") != NULL) - process_socket_request(">stats", &sock, API); - else - ssend( - sock, - "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" - "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\"}" - ); + header = true; } + // Are we asked for a favicon? + if(strstr(message, "GET /favicon.ico") != NULL) + ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); + else + process_api_request(message, &sock, header); + // Close connection to show that we reached the end of the transmission close(sock); sock = 0; From 1481176d1bf61353f04d2b819eabc3472b7c4a2d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 11:30:45 +0200 Subject: [PATCH 017/168] Added overTime statistics to API, http://pi.hole:4747/stats/overTime and http://pi.hole/admin/api.php?overTimeData10mins give identical results --- request.c | 58 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/request.c b/request.c index 661dc553..a29cee72 100644 --- a/request.c +++ b/request.c @@ -150,7 +150,6 @@ void process_socket_request(char *client_message, int *sock) void process_api_request(char *client_message, int *sock, bool header) { - bool processed = false; char type; if(header) type = APIH; @@ -159,10 +158,13 @@ void process_api_request(char *client_message, int *sock, bool header) if(command(client_message, "GET /stats/summary")) { - processed = true; getStats(sock, type); } - if(!processed && header) + else if(command(client_message, "GET /stats/overTime")) + { + getOverTime(sock, type); + } + else if(header) { ssend(*sock, "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" @@ -179,7 +181,7 @@ bool command(char *client_message, const char* cmd) } void sendAPIResponse(int sock, char *content, char type) { - if(type == APIH) + if(type == APIH && strlen(content) > 0) { // Send header and payload ssend(sock, @@ -188,6 +190,13 @@ void sendAPIResponse(int sock, char *content, char type) { strlen(content), content); } + else if(type == APIH) + { + // Send only header (length of content is not yet known and will be sent out in smaller packets) + ssend(sock, + "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" + "Content-Type: application/json\n\n"); + } else { // Simple request: Don't send header, only payload @@ -273,7 +282,8 @@ void getStats(int *sock, char type) ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ counters.domains, counters.forwardedqueries, counters.cached); } - else if(type == API || type == APIH) { + else + { // cJSON *response = cJSON_CreateObject(); // cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); @@ -290,7 +300,7 @@ void getStats(int *sock, char type) if(ret > 0) sendAPIResponse(*sock, sendbuffer, type); else - logg("Error allocating memory for API response"); + logg("Error allocating memory for API response (getStats)"); free(sendbuffer); } @@ -300,20 +310,46 @@ void getStats(int *sock, char type) void getOverTime(int *sock, char type) { - int i; - bool sendit = false; + int i, j = 9999999; + for(i=0; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0) && !sendit) + if(overTime[i].total > 0 || overTime[i].blocked > 0) { - sendit = true; + j = i; + break; } - if(sendit) + } + + if(type == SOCKET) + { + for(i = j; i < counters.overTime; i++) { ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); } } + else + { + // First send header outside of the for-loop + sendAPIResponse(*sock, "", type); + ssend(*sock,"{\"domains_over_time\":{"); + + for(i = j; i < counters.overTime; i++) + { + if(i != j) ssend(*sock, ","); + ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); + } + ssend(*sock,"},\"ads_over_time\":{"); + + for(i = j; i < counters.overTime; i++) + { + if(i != j) ssend(*sock, ","); + ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); + } + ssend(*sock,"}}"); + } + if(debugclients) logg("Sent overTime data to client, ID: %i", *sock); } From 824b9e97dc55d7635411c7716040f6caaa31bd79 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 11:39:38 +0200 Subject: [PATCH 018/168] Improve/add more comments to the code --- request.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/request.c b/request.c index a29cee72..165818c9 100644 --- a/request.c +++ b/request.c @@ -312,6 +312,7 @@ void getOverTime(int *sock, char type) { int i, j = 9999999; + // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) for(i=0; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); @@ -322,6 +323,7 @@ void getOverTime(int *sock, char type) } } + // Send data in socket format if requested if(type == SOCKET) { for(i = j; i < counters.overTime; i++) @@ -331,10 +333,11 @@ void getOverTime(int *sock, char type) } else { - // First send header outside of the for-loop + // First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, "", type); ssend(*sock,"{\"domains_over_time\":{"); + // Send "domains_over_time" data for(i = j; i < counters.overTime; i++) { if(i != j) ssend(*sock, ","); @@ -342,6 +345,7 @@ void getOverTime(int *sock, char type) } ssend(*sock,"},\"ads_over_time\":{"); + // Send "ads_over_time" data for(i = j; i < counters.overTime; i++) { if(i != j) ssend(*sock, ","); From 4643f97fbc2e2a416b3af14d7439338eec6c3ecf Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 23:53:54 +0200 Subject: [PATCH 019/168] Add /stats/top_domains and /stats/top_ads and parameters therein --- request.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/request.c b/request.c index 165818c9..1f3ebd74 100644 --- a/request.c +++ b/request.c @@ -164,6 +164,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getOverTime(sock, type); } + else if(command(client_message, "GET /stats/top_domains") || command(client_message, "GET /stats/top_ads")) + { + getTopDomains(client_message, sock, type); + } else if(header) { ssend(*sock, @@ -361,30 +365,49 @@ void getOverTime(int *sock, char type) void getTopDomains(char *client_message, int *sock, char type) { int i, temparray[counters.domains][2], count=10, num; - bool blocked = command(client_message, ">top-ads"), audit = false, desc = false; + bool blocked, audit = false, desc = false; + + if(type == SOCKET) + blocked = command(client_message, ">top-ads"); + else + blocked = command(client_message, "/top_ads"); // Exit before processing any data if requested via config setting if(!config.query_display) return; // Match both top-domains and top-ads - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) + // SOCKET: >top-domains (15) + // API: /top/domains?limit=15 + if(sscanf(client_message, "%*[^0123456789H\n]%i", &num) > 0) { // User wants a different number of requests count = num; } // Apply Audit Log filtering? - if(command(client_message, " for audit")) + // SOCKET: >top-domains for audit + // API: /top/domains?audit + if(type == SOCKET && command(client_message, " for audit")) + { + audit = true; + } + else if(type != SOCKET && command(client_message, "audit")) { audit = true; } // Sort in descending order? - if(command(client_message, " desc")) + // SOCKET: >top-domains desc + // API: /top/domains?order=desc + if(type == SOCKET && command(client_message, " desc")) { desc = true; } + else if(type != SOCKET && command(client_message, "desc")) + { + audit = true; + } for(i=0; i < counters.domains; i++) { @@ -438,7 +461,16 @@ void getTopDomains(char *client_message, int *sock, char type) } } - int skip = 0; + if(type != SOCKET) + {// First send header with unspecified content-length outside of the for-loop + sendAPIResponse(*sock, "", type); + if(blocked) + ssend(*sock, "{\"top_ads\":{"); + else + ssend(*sock, "{\"top_queries\":{"); + } + + int skip = 0; bool first = true; for(i=0; i < min(counters.domains, count+skip); i++) { // Get sorted indices @@ -464,16 +496,38 @@ void getTopDomains(char *client_message, int *sock, char type) if(blocked && showblocked && domains[j].blockedcount > 0) { - if(audit && domains[j].wildcard) - ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); + if(type == SOCKET) + { + if(audit && domains[j].wildcard) + ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); + else + ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); + } else - ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); + { + if(!first) ssend(*sock,","); + first = false; + ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); + } } else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) { - ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); + if(type == SOCKET) + { + ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); + } + else + { + if(!first) ssend(*sock,","); + first = false; + ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); + } } } + + if(type != SOCKET) + ssend(*sock,"}}"); + if(excludedomains != NULL) clearSetupVarsArray(); if(debugclients) From 2b439cfbb03316a49288cec6c251139afaa672b6 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 13 Jun 2017 23:57:16 +0200 Subject: [PATCH 020/168] Remove cJSON files. Note that it can be re-added cleanly into the code by reverting this commit. --- Makefile | 5 +- cJSON.c | 2634 ----------------------------------------------------- cJSON.h | 263 ------ request.c | 16 +- 4 files changed, 3 insertions(+), 2915 deletions(-) delete mode 100644 cJSON.c delete mode 100644 cJSON.h diff --git a/Makefile b/Makefile index ab0b474a..6cb3df85 100644 --- a/Makefile +++ b/Makefile @@ -57,10 +57,7 @@ $(ODIR): $(ODIR)/sqlite3.o: sqlite3.c $(CC) -c -o $@ $< $(CCFLAGS) -$(ODIR)/cJSON.o: cJSON.c - $(CC) -c -o $@ $< $(CCFLAGS) - -pihole-FTL: $(_OBJ) $(ODIR)/sqlite3.o $(ODIR)/cJSON.o +pihole-FTL: $(_OBJ) $(ODIR)/sqlite3.o $(CC) -v $(CCFLAGS) -o $@ $^ $(LIBS) .PHONY: clean force install diff --git a/cJSON.c b/cJSON.c deleted file mode 100644 index 7a97a86f..00000000 --- a/cJSON.c +++ /dev/null @@ -1,2634 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -/* cJSON */ -/* JSON parser in C. */ - -#ifdef __GNUC__ -#pragma GCC visibility push(default) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __GNUC__ -#pragma GCC visibility pop -#endif - -#include "cJSON.h" - -/* define our own boolean type */ -#define true ((cJSON_bool)1) -#define false ((cJSON_bool)0) - -typedef struct { - const unsigned char *json; - size_t position; -} error; -static error global_error = { NULL, 0 }; - -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) -{ - return (const char*) (global_error.json + global_error.position); -} - -/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 5) || (CJSON_VERSION_PATCH != 4) -#error cJSON.h and cJSON.c have different versions. Make sure that both have the same. -#endif - -CJSON_PUBLIC(const char*) cJSON_Version(void) -{ - static char version[15]; - sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); - - return version; -} - -/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ -static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) -{ - if ((string1 == NULL) || (string2 == NULL)) - { - return 1; - } - - if (string1 == string2) - { - return 0; - } - - for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) - { - if (*string1 == '\0') - { - return 0; - } - } - - return tolower(*string1) - tolower(*string2); -} - -typedef struct internal_hooks -{ - void *(*allocate)(size_t size); - void (*deallocate)(void *pointer); - void *(*reallocate)(void *pointer, size_t size); -} internal_hooks; - -static internal_hooks global_hooks = { malloc, free, realloc }; - -static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) -{ - size_t length = 0; - unsigned char *copy = NULL; - - if (string == NULL) - { - return NULL; - } - - length = strlen((const char*)string) + sizeof(""); - if (!(copy = (unsigned char*)hooks->allocate(length))) - { - return NULL; - } - memcpy(copy, string, length); - - return copy; -} - -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) -{ -if (hooks == NULL) -{ -/* Reset hooks */ -global_hooks.allocate = malloc; -global_hooks.deallocate = free; -global_hooks.reallocate = realloc; -return; -} - -global_hooks.allocate = malloc; -if (hooks->malloc_fn != NULL) -{ -global_hooks.allocate = hooks->malloc_fn; -} - -global_hooks.deallocate = free; -if (hooks->free_fn != NULL) -{ -global_hooks.deallocate = hooks->free_fn; -} - -/* use realloc only if both free and malloc are used */ -global_hooks.reallocate = NULL; -if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) -{ -global_hooks.reallocate = realloc; -} -} - -/* Internal constructor. */ -static cJSON *cJSON_New_Item(const internal_hooks * const hooks) -{ - cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); - if (node) - { - memset(node, '\0', sizeof(cJSON)); - } - - return node; -} - -/* Delete a cJSON structure. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) -{ -cJSON *next = NULL; -while (item != NULL) -{ -next = item->next; -if (!(item->type & cJSON_IsReference) && (item->child != NULL)) -{ -cJSON_Delete(item->child); -} -if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) -{ -global_hooks.deallocate(item->valuestring); -} -if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) -{ -global_hooks.deallocate(item->string); -} -global_hooks.deallocate(item); -item = next; -} -} - -/* get the decimal point character of the current locale */ -static unsigned char get_decimal_point(void) -{ - struct lconv *lconv = localeconv(); - return (unsigned char) lconv->decimal_point[0]; -} - -typedef struct -{ - const unsigned char *content; - size_t length; - size_t offset; - size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ - internal_hooks hooks; -} parse_buffer; - -/* check if the given size is left to read in a given parse buffer (starting with 1) */ -#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) -#define cannot_read(buffer, size) (!can_read(buffer, size)) -/* check if the buffer can be accessed at the given index (starting with 0) */ -#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) -#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) -/* get a pointer to the buffer at the position */ -#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) - -/* Parse the input text to generate a number, and populate the result into item. */ -static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) -{ -double number = 0; -unsigned char *after_end = NULL; -unsigned char number_c_string[64]; -unsigned char decimal_point = get_decimal_point(); -size_t i = 0; - -if ((input_buffer == NULL) || (input_buffer->content == NULL)) -{ -return false; -} - -/* copy the number into a temporary buffer and replace '.' with the decimal point - * of the current locale (for strtod) - * This also takes care of '\0' not necessarily being available for marking the end of the input */ -for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) -{ -switch (buffer_at_offset(input_buffer)[i]) -{ -case '0': -case '1': -case '2': -case '3': -case '4': -case '5': -case '6': -case '7': -case '8': -case '9': -case '+': -case '-': -case 'e': -case 'E': -number_c_string[i] = buffer_at_offset(input_buffer)[i]; -break; - -case '.': -number_c_string[i] = decimal_point; -break; - -default: -goto loop_end; -} -} -loop_end: - number_c_string[i] = '\0'; - -number = strtod((const char*)number_c_string, (char**)&after_end); -if (number_c_string == after_end) -{ -return false; /* parse_error */ -} - -item->valuedouble = number; - -/* use saturation in case of overflow */ -if (number >= INT_MAX) -{ -item->valueint = INT_MAX; -} -else if (number <= INT_MIN) -{ -item->valueint = INT_MIN; -} -else -{ -item->valueint = (int)number; -} - -item->type = cJSON_Number; - -input_buffer->offset += (size_t)(after_end - number_c_string); -return true; -} - -/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) -{ -if (number >= INT_MAX) -{ -object->valueint = INT_MAX; -} -else if (number <= INT_MIN) -{ -object->valueint = INT_MIN; -} -else -{ -object->valueint = (int)number; -} - -return object->valuedouble = number; -} - -typedef struct -{ - unsigned char *buffer; - size_t length; - size_t offset; - size_t depth; /* current nesting depth (for formatted printing) */ - cJSON_bool noalloc; - cJSON_bool format; /* is this print a formatted print */ - internal_hooks hooks; -} printbuffer; - -/* realloc printbuffer if necessary to have at least "needed" bytes more */ -static unsigned char* ensure(printbuffer * const p, size_t needed) -{ - unsigned char *newbuffer = NULL; - size_t newsize = 0; - - if ((p == NULL) || (p->buffer == NULL)) - { - return NULL; - } - - if ((p->length > 0) && (p->offset >= p->length)) - { - /* make sure that offset is valid */ - return NULL; - } - - if (needed > INT_MAX) - { - /* sizes bigger than INT_MAX are currently not supported */ - return NULL; - } - - needed += p->offset + 1; - if (needed <= p->length) - { - return p->buffer + p->offset; - } - - if (p->noalloc) { - return NULL; - } - - /* calculate new buffer size */ - if (needed > (INT_MAX / 2)) - { - /* overflow of int, use INT_MAX if possible */ - if (needed <= INT_MAX) - { - newsize = INT_MAX; - } - else - { - return NULL; - } - } - else - { - newsize = needed * 2; - } - - if (p->hooks.reallocate != NULL) - { - /* reallocate with realloc if available */ - newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); - } - else - { - /* otherwise reallocate manually */ - newbuffer = (unsigned char*)p->hooks.allocate(newsize); - if (!newbuffer) - { - p->hooks.deallocate(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; - } - if (newbuffer) - { - memcpy(newbuffer, p->buffer, p->offset + 1); - } - p->hooks.deallocate(p->buffer); - } - p->length = newsize; - p->buffer = newbuffer; - - return newbuffer + p->offset; -} - -/* calculate the new length of the string in a printbuffer and update the offset */ -static void update_offset(printbuffer * const buffer) -{ - const unsigned char *buffer_pointer = NULL; - if ((buffer == NULL) || (buffer->buffer == NULL)) - { - return; - } - buffer_pointer = buffer->buffer + buffer->offset; - - buffer->offset += strlen((const char*)buffer_pointer); -} - -/* Render the number nicely from the given item into a string. */ -static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - double d = item->valuedouble; - int length = 0; - size_t i = 0; - unsigned char number_buffer[26]; /* temporary buffer to print the number into */ - unsigned char decimal_point = get_decimal_point(); - double test; - - if (output_buffer == NULL) - { - return false; - } - - /* This checks for NaN and Infinity */ - if ((d * 0) != 0) - { - length = sprintf((char*)number_buffer, "null"); - } - else - { - /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ - length = sprintf((char*)number_buffer, "%1.15g", d); - - /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) - { - /* If not, print with 17 decimal places of precision */ - length = sprintf((char*)number_buffer, "%1.17g", d); - } - } - - /* sprintf failed or buffer overrun occured */ - if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) - { - return false; - } - - /* reserve appropriate space in the output */ - output_pointer = ensure(output_buffer, (size_t)length); - if (output_pointer == NULL) - { - return false; - } - - /* copy the printed number to the output and replace locale - * dependent decimal point with '.' */ - for (i = 0; i < ((size_t)length); i++) - { - if (number_buffer[i] == decimal_point) - { - output_pointer[i] = '.'; - continue; - } - - output_pointer[i] = number_buffer[i]; - } - output_pointer[i] = '\0'; - - output_buffer->offset += (size_t)length; - - return true; -} - -/* parse 4 digit hexadecimal number */ -static unsigned parse_hex4(const unsigned char * const input) -{ - unsigned int h = 0; - size_t i = 0; - - for (i = 0; i < 4; i++) - { - /* parse digit */ - if ((input[i] >= '0') && (input[i] <= '9')) - { - h += (unsigned int) input[i] - '0'; - } - else if ((input[i] >= 'A') && (input[i] <= 'F')) - { - h += (unsigned int) 10 + input[i] - 'A'; - } - else if ((input[i] >= 'a') && (input[i] <= 'f')) - { - h += (unsigned int) 10 + input[i] - 'a'; - } - else /* invalid */ - { - return 0; - } - - if (i < 3) - { - /* shift left to make place for the next nibble */ - h = h << 4; - } - } - - return h; -} - -/* converts a UTF-16 literal to UTF-8 - * A literal can be one or two sequences of the form \uXXXX */ -static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) -{ - long unsigned int codepoint = 0; - unsigned int first_code = 0; - const unsigned char *first_sequence = input_pointer; - unsigned char utf8_length = 0; - unsigned char utf8_position = 0; - unsigned char sequence_length = 0; - unsigned char first_byte_mark = 0; - - if ((input_end - first_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - /* get the first utf16 sequence */ - first_code = parse_hex4(first_sequence + 2); - - /* check that the code is valid */ - if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) - { - goto fail; - } - - /* UTF16 surrogate pair */ - if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) - { - const unsigned char *second_sequence = first_sequence + 6; - unsigned int second_code = 0; - sequence_length = 12; /* \uXXXX\uXXXX */ - - if ((input_end - second_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) - { - /* missing second half of the surrogate pair */ - goto fail; - } - - /* get the second utf16 sequence */ - second_code = parse_hex4(second_sequence + 2); - /* check that the code is valid */ - if ((second_code < 0xDC00) || (second_code > 0xDFFF)) - { - /* invalid second half of the surrogate pair */ - goto fail; - } - - - /* calculate the unicode codepoint from the surrogate pair */ - codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); - } - else - { - sequence_length = 6; /* \uXXXX */ - codepoint = first_code; - } - - /* encode as UTF-8 - * takes at maximum 4 bytes to encode: - * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - if (codepoint < 0x80) - { - /* normal ascii, encoding 0xxxxxxx */ - utf8_length = 1; - } - else if (codepoint < 0x800) - { - /* two bytes, encoding 110xxxxx 10xxxxxx */ - utf8_length = 2; - first_byte_mark = 0xC0; /* 11000000 */ - } - else if (codepoint < 0x10000) - { - /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ - utf8_length = 3; - first_byte_mark = 0xE0; /* 11100000 */ - } - else if (codepoint <= 0x10FFFF) - { - /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ - utf8_length = 4; - first_byte_mark = 0xF0; /* 11110000 */ - } - else - { - /* invalid unicode codepoint */ - goto fail; - } - - /* encode as utf8 */ - for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) - { - /* 10xxxxxx */ - (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - } - /* encode first byte */ - if (utf8_length > 1) - { - (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); - } - else - { - (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); - } - - *output_pointer += utf8_length; - - return sequence_length; - - fail: - return 0; -} - -/* Parse the input text into an unescaped cinput, and populate item. */ -static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) -{ -const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; -const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; -unsigned char *output_pointer = NULL; -unsigned char *output = NULL; - -/* not a string */ -if (buffer_at_offset(input_buffer)[0] != '\"') -{ -goto fail; -} - -{ -/* calculate approximate size of the output (overestimate) */ -size_t allocation_length = 0; -size_t skipped_bytes = 0; -while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) -{ -/* is escape sequence */ -if (input_end[0] == '\\') -{ -if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) -{ -/* prevent buffer overflow when last input character is a backslash */ -goto fail; -} -skipped_bytes++; -input_end++; -} -input_end++; -} -if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) -{ -goto fail; /* string ended unexpectedly */ -} - -/* This is at most how much we need for the output */ -allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; -output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); -if (output == NULL) -{ -goto fail; /* allocation failure */ -} -} - -output_pointer = output; -/* loop through the string literal */ -while (input_pointer < input_end) -{ -if (*input_pointer != '\\') -{ -*output_pointer++ = *input_pointer++; -} -/* escape sequence */ -else -{ -unsigned char sequence_length = 2; -if ((input_end - input_pointer) < 1) -{ -goto fail; -} - -switch (input_pointer[1]) -{ -case 'b': -*output_pointer++ = '\b'; -break; -case 'f': -*output_pointer++ = '\f'; -break; -case 'n': -*output_pointer++ = '\n'; -break; -case 'r': -*output_pointer++ = '\r'; -break; -case 't': -*output_pointer++ = '\t'; -break; -case '\"': -case '\\': -case '/': -*output_pointer++ = input_pointer[1]; -break; - -/* UTF-16 literal */ -case 'u': -sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); -if (sequence_length == 0) -{ -/* failed to convert UTF16-literal to UTF-8 */ -goto fail; -} -break; - -default: -goto fail; -} -input_pointer += sequence_length; -} -} - -/* zero terminate the output */ -*output_pointer = '\0'; - -item->type = cJSON_String; -item->valuestring = (char*)output; - -input_buffer->offset = (size_t) (input_end - input_buffer->content); -input_buffer->offset++; - -return true; - -fail: -if (output != NULL) -{ -input_buffer->hooks.deallocate(output); -} - -if (input_pointer != NULL) -{ -input_buffer->offset = (size_t)(input_pointer - input_buffer->content); -} - -return false; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) -{ - const unsigned char *input_pointer = NULL; - unsigned char *output = NULL; - unsigned char *output_pointer = NULL; - size_t output_length = 0; - /* numbers of additional characters needed for escaping */ - size_t escape_characters = 0; - - if (output_buffer == NULL) - { - return false; - } - - /* empty string */ - if (input == NULL) - { - output = ensure(output_buffer, sizeof("\"\"")); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "\"\""); - - return true; - } - - /* set "flag" to 1 if something needs to be escaped */ - for (input_pointer = input; *input_pointer; input_pointer++) - { - switch (*input_pointer) - { - case '\"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - /* one character escape sequence */ - escape_characters++; - break; - default: - if (*input_pointer < 32) - { - /* UTF-16 escape sequence uXXXX */ - escape_characters += 5; - } - break; - } - } - output_length = (size_t)(input_pointer - input) + escape_characters; - - output = ensure(output_buffer, output_length + sizeof("\"\"")); - if (output == NULL) - { - return false; - } - - /* no characters have to be escaped */ - if (escape_characters == 0) - { - output[0] = '\"'; - memcpy(output + 1, input, output_length); - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; - } - - output[0] = '\"'; - output_pointer = output + 1; - /* copy the string */ - for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) - { - if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) - { - /* normal character, copy */ - *output_pointer = *input_pointer; - } - else - { - /* character needs to be escaped */ - *output_pointer++ = '\\'; - switch (*input_pointer) - { - case '\\': - *output_pointer = '\\'; - break; - case '\"': - *output_pointer = '\"'; - break; - case '\b': - *output_pointer = 'b'; - break; - case '\f': - *output_pointer = 'f'; - break; - case '\n': - *output_pointer = 'n'; - break; - case '\r': - *output_pointer = 'r'; - break; - case '\t': - *output_pointer = 't'; - break; - default: - /* escape and print as unicode codepoint */ - sprintf((char*)output_pointer, "u%04x", *input_pointer); - output_pointer += 4; - break; - } - } - } - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; -} - -/* Invoke print_string_ptr (which is useful) on an item. */ -static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) -{ - return print_string_ptr((unsigned char*)item->valuestring, p); -} - -/* Predeclare these prototypes. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); - -/* Utility to jump whitespace and cr/lf */ -static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL)) - { - return NULL; - } - - while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) - { - buffer->offset++; - } - - if (buffer->offset == buffer->length) - { - buffer->offset--; - } - - return buffer; -} - -/* Parse an object - create a new root, and populate. */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) -{ - parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; - cJSON *item = NULL; - - /* reset error position */ - global_error.json = NULL; - global_error.position = 0; - - if (value == NULL) - { - goto fail; - } - - buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + sizeof(""); - buffer.offset = 0; - buffer.hooks = global_hooks; - - item = cJSON_New_Item(&global_hooks); - if (item == NULL) /* memory fail */ - { - goto fail; - } - - if (!parse_value(item, buffer_skip_whitespace(&buffer))) - { - /* parse failure. ep is set. */ - goto fail; - } - - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) - { - buffer_skip_whitespace(&buffer); - if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') - { - goto fail; - } - } - if (return_parse_end) - { - *return_parse_end = (const char*)buffer_at_offset(&buffer); - } - - return item; - - fail: - if (item != NULL) - { - cJSON_Delete(item); - } - - if (value != NULL) - { - error local_error; - local_error.json = (const unsigned char*)value; - local_error.position = 0; - - if (buffer.offset < buffer.length) - { - local_error.position = buffer.offset; - } - else if (buffer.length > 0) - { - local_error.position = buffer.length - 1; - } - - if (return_parse_end != NULL) - { - *return_parse_end = (const char*)local_error.json + local_error.position; - } - else - { - global_error = local_error; - } - } - - return NULL; -} - -/* Default options for cJSON_Parse */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) -{ - return cJSON_ParseWithOpts(value, 0, 0); -} - -#define cjson_min(a, b) ((a < b) ? a : b) - -static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) -{ - printbuffer buffer[1]; - unsigned char *printed = NULL; - - memset(buffer, 0, sizeof(buffer)); - - /* create buffer */ - buffer->buffer = (unsigned char*) hooks->allocate(256); - buffer->format = format; - buffer->hooks = *hooks; - if (buffer->buffer == NULL) - { - goto fail; - } - - /* print the value */ - if (!print_value(item, buffer)) - { - goto fail; - } - update_offset(buffer); - - /* check if reallocate is available */ - if (hooks->reallocate != NULL) - { - printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->length); - buffer->buffer = NULL; - if (printed == NULL) { - goto fail; - } - } - else /* otherwise copy the JSON over to a new buffer */ - { - printed = (unsigned char*) hooks->allocate(buffer->offset + 1); - if (printed == NULL) - { - goto fail; - } - memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); - printed[buffer->offset] = '\0'; /* just to be sure */ - - /* free the buffer */ - hooks->deallocate(buffer->buffer); - } - - return printed; - - fail: - if (buffer->buffer != NULL) - { - hooks->deallocate(buffer->buffer); - } - - if (printed != NULL) - { - hooks->deallocate(printed); - } - - return NULL; -} - -/* Render a cJSON item/entity/structure to text. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) -{ - return (char*)print(item, true, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) -{ - return (char*)print(item, false, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - - if (prebuffer < 0) - { - return NULL; - } - - p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); - if (!p.buffer) - { - return NULL; - } - - p.length = (size_t)prebuffer; - p.offset = 0; - p.noalloc = false; - p.format = fmt; - p.hooks = global_hooks; - - if (!print_value(item, &p)) - { - return NULL; - } - - return (char*)p.buffer; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) -{ -printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - -if (len < 0) -{ -return false; -} - -p.buffer = (unsigned char*)buf; -p.length = (size_t)len; -p.offset = 0; -p.noalloc = true; -p.format = fmt; -p.hooks = global_hooks; - -return print_value(item, &p); -} - -/* Parser core - when encountering text, process appropriately. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) -{ -if ((input_buffer == NULL) || (input_buffer->content == NULL)) -{ -return false; /* no input */ -} - -/* parse the different types of values */ -/* null */ -if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) -{ -item->type = cJSON_NULL; -input_buffer->offset += 4; -return true; -} -/* false */ -if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) -{ -item->type = cJSON_False; -input_buffer->offset += 5; -return true; -} -/* true */ -if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) -{ -item->type = cJSON_True; -item->valueint = 1; -input_buffer->offset += 4; -return true; -} -/* string */ -if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) -{ -return parse_string(item, input_buffer); -} -/* number */ -if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) -{ -return parse_number(item, input_buffer); -} -/* array */ -if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) -{ -return parse_array(item, input_buffer); -} -/* object */ -if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) -{ -return parse_object(item, input_buffer); -} - - -return false; -} - -/* Render a value to text. */ -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output = NULL; - - if ((item == NULL) || (output_buffer == NULL)) - { - return false; - } - - switch ((item->type) & 0xFF) - { - case cJSON_NULL: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "null"); - return true; - - case cJSON_False: - output = ensure(output_buffer, 6); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "false"); - return true; - - case cJSON_True: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "true"); - return true; - - case cJSON_Number: - return print_number(item, output_buffer); - - case cJSON_Raw: - { - size_t raw_length = 0; - if (item->valuestring == NULL) - { - if (!output_buffer->noalloc) - { - output_buffer->hooks.deallocate(output_buffer->buffer); - } - return false; - } - - raw_length = strlen(item->valuestring) + sizeof(""); - output = ensure(output_buffer, raw_length); - if (output == NULL) - { - return false; - } - memcpy(output, item->valuestring, raw_length); - return true; - } - - case cJSON_String: - return print_string(item, output_buffer); - - case cJSON_Array: - return print_array(item, output_buffer); - - case cJSON_Object: - return print_object(item, output_buffer); - - default: - return false; - } -} - -/* Build an array from input text. */ -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) -{ -cJSON *head = NULL; /* head of the linked list */ -cJSON *current_item = NULL; - -if (input_buffer->depth >= CJSON_NESTING_LIMIT) -{ -return false; /* to deeply nested */ -} -input_buffer->depth++; - -if (buffer_at_offset(input_buffer)[0] != '[') -{ -/* not an array */ -goto fail; -} - -input_buffer->offset++; -buffer_skip_whitespace(input_buffer); -if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) -{ -/* empty array */ -goto success; -} - -/* check if we skipped to the end of the buffer */ -if (cannot_access_at_index(input_buffer, 0)) -{ -input_buffer->offset--; -goto fail; -} - -/* step back to character in front of the first element */ -input_buffer->offset--; -/* loop through the comma separated array elements */ -do -{ -/* allocate next item */ -cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); -if (new_item == NULL) -{ -goto fail; /* allocation failure */ -} - -/* attach next item to list */ -if (head == NULL) -{ -/* start the linked list */ -current_item = head = new_item; -} -else -{ -/* add to the end and advance */ -current_item->next = new_item; -new_item->prev = current_item; -current_item = new_item; -} - -/* parse next value */ -input_buffer->offset++; -buffer_skip_whitespace(input_buffer); -if (!parse_value(current_item, input_buffer)) -{ -goto fail; /* failed to parse value */ -} -buffer_skip_whitespace(input_buffer); -} -while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - -if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') -{ -goto fail; /* expected end of array */ -} - -success: - input_buffer->depth--; - -item->type = cJSON_Array; -item->child = head; - -input_buffer->offset++; - -return true; - -fail: -if (head != NULL) -{ -cJSON_Delete(head); -} - -return false; -} - -/* Render an array to text */ -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_element = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output array. */ - /* opening square bracket */ - output_pointer = ensure(output_buffer, 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer = '['; - output_buffer->offset++; - output_buffer->depth++; - - while (current_element != NULL) - { - if (!print_value(current_element, output_buffer)) - { - return false; - } - update_offset(output_buffer); - if (current_element->next) - { - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ','; - if(output_buffer->format) - { - *output_pointer++ = ' '; - } - *output_pointer = '\0'; - output_buffer->offset += length; - } - current_element = current_element->next; - } - - output_pointer = ensure(output_buffer, 2); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ']'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Build an object from the text. */ -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) -{ -cJSON *head = NULL; /* linked list head */ -cJSON *current_item = NULL; - -if (input_buffer->depth >= CJSON_NESTING_LIMIT) -{ -return false; /* to deeply nested */ -} -input_buffer->depth++; - -if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) -{ -goto fail; /* not an object */ -} - -input_buffer->offset++; -buffer_skip_whitespace(input_buffer); -if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) -{ -goto success; /* empty object */ -} - -/* check if we skipped to the end of the buffer */ -if (cannot_access_at_index(input_buffer, 0)) -{ -input_buffer->offset--; -goto fail; -} - -/* step back to character in front of the first element */ -input_buffer->offset--; -/* loop through the comma separated array elements */ -do -{ -/* allocate next item */ -cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); -if (new_item == NULL) -{ -goto fail; /* allocation failure */ -} - -/* attach next item to list */ -if (head == NULL) -{ -/* start the linked list */ -current_item = head = new_item; -} -else -{ -/* add to the end and advance */ -current_item->next = new_item; -new_item->prev = current_item; -current_item = new_item; -} - -/* parse the name of the child */ -input_buffer->offset++; -buffer_skip_whitespace(input_buffer); -if (!parse_string(current_item, input_buffer)) -{ -goto fail; /* faile to parse name */ -} -buffer_skip_whitespace(input_buffer); - -/* swap valuestring and string, because we parsed the name */ -current_item->string = current_item->valuestring; -current_item->valuestring = NULL; - -if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) -{ -goto fail; /* invalid object */ -} - -/* parse the value */ -input_buffer->offset++; -buffer_skip_whitespace(input_buffer); -if (!parse_value(current_item, input_buffer)) -{ -goto fail; /* failed to parse value */ -} -buffer_skip_whitespace(input_buffer); -} -while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - -if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) -{ -goto fail; /* expected end of object */ -} - -success: - input_buffer->depth--; - -item->type = cJSON_Object; -item->child = head; - -input_buffer->offset++; -return true; - -fail: -if (head != NULL) -{ -cJSON_Delete(head); -} - -return false; -} - -/* Render an object to text. */ -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_item = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output: */ - length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer++ = '{'; - output_buffer->depth++; - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - output_buffer->offset += length; - - while (current_item) - { - if (output_buffer->format) - { - size_t i; - output_pointer = ensure(output_buffer, output_buffer->depth); - if (output_pointer == NULL) - { - return false; - } - for (i = 0; i < output_buffer->depth; i++) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += output_buffer->depth; - } - - /* print key */ - if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ':'; - if (output_buffer->format) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += length; - - /* print value */ - if (!print_value(current_item, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - /* print comma if not last */ - length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0)); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - if (current_item->next) - { - *output_pointer++ = ','; - } - - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - *output_pointer = '\0'; - output_buffer->offset += length; - - current_item = current_item->next; - } - - output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); - if (output_pointer == NULL) - { - return false; - } - if (output_buffer->format) - { - size_t i; - for (i = 0; i < (output_buffer->depth - 1); i++) - { - *output_pointer++ = '\t'; - } - } - *output_pointer++ = '}'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Get Array size/item / object item. */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) -{ - cJSON *c = array->child; - size_t i = 0; - while(c) - { - i++; - c = c->next; - } - - /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ - - return (int)i; -} - -static cJSON* get_array_item(const cJSON *array, size_t index) -{ - cJSON *current_child = NULL; - - if (array == NULL) - { - return NULL; - } - - current_child = array->child; - while ((current_child != NULL) && (index > 0)) - { - index--; - current_child = current_child->next; - } - - return current_child; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) -{ - if (index < 0) - { - return NULL; - } - - return get_array_item(array, (size_t)index); -} - -static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) -{ - cJSON *current_element = NULL; - - if ((object == NULL) || (name == NULL)) - { - return NULL; - } - - current_element = object->child; - if (case_sensitive) - { - while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) - { - current_element = current_element->next; - } - } - else - { - while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) - { - current_element = current_element->next; - } - } - - return current_element; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, false); -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, true); -} - -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) -{ - return cJSON_GetObjectItem(object, string) ? 1 : 0; -} - -/* Utility for array list handling. */ -static void suffix_object(cJSON *prev, cJSON *item) -{ - prev->next = item; - item->prev = prev; -} - -/* Utility for handling references. */ -static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) -{ - cJSON *ref = cJSON_New_Item(hooks); - if (!ref) - { - return NULL; - } - memcpy(ref, item, sizeof(cJSON)); - ref->string = NULL; - ref->type |= cJSON_IsReference; - ref->next = ref->prev = NULL; - return ref; -} - -/* Add item to array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) -{ -cJSON *child = NULL; - -if ((item == NULL) || (array == NULL)) -{ -return; -} - -child = array->child; - -if (child == NULL) -{ -/* list is empty, start new one */ -array->child = item; -} -else -{ -/* append to the end */ -while (child->next) -{ -child = child->next; -} -suffix_object(child, item); -} -} - -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) -{ -/* call cJSON_AddItemToObjectCS for code reuse */ -cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); -/* remove cJSON_StringIsConst flag */ -item->type &= ~cJSON_StringIsConst; -} - -#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) -#pragma GCC diagnostic push -#endif -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -/* Add an item to an object with constant string as key */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) -{ -if (!item) -{ -return; -} -if (!(item->type & cJSON_StringIsConst) && item->string) -{ -global_hooks.deallocate(item->string); -} -item->string = (char*)string; -item->type |= cJSON_StringIsConst; -cJSON_AddItemToArray(object, item); -} -#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) -#pragma GCC diagnostic pop -#endif - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) -{ -cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) -{ -cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) -{ -if ((parent == NULL) || (item == NULL)) -{ -return NULL; -} - -if (item->prev != NULL) -{ -/* not the first element */ -item->prev->next = item->next; -} -if (item->next != NULL) -{ -/* not the last element */ -item->next->prev = item->prev; -} - -if (item == parent->child) -{ -/* first element */ -parent->child = item->next; -} -/* make sure the detached item doesn't point anywhere anymore */ -item->prev = NULL; -item->next = NULL; - -return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) -{ -if (which < 0) -{ -return NULL; -} - -return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) -{ -cJSON_Delete(cJSON_DetachItemFromArray(array, which)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) -{ -cJSON *to_detach = cJSON_GetObjectItem(object, string); - -return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ -cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); - -return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) -{ -cJSON_Delete(cJSON_DetachItemFromObject(object, string)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ -cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); -} - -/* Replace array/object items with new ones. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) -{ -cJSON *after_inserted = NULL; - -if (which < 0) -{ -return; -} - -after_inserted = get_array_item(array, (size_t)which); -if (after_inserted == NULL) -{ -cJSON_AddItemToArray(array, newitem); -return; -} - -newitem->next = after_inserted; -newitem->prev = after_inserted->prev; -after_inserted->prev = newitem; -if (after_inserted == array->child) -{ -array->child = newitem; -} -else -{ -newitem->prev->next = newitem; -} -} - -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) -{ -if ((parent == NULL) || (replacement == NULL)) -{ -return false; -} - -if (replacement == item) -{ -return true; -} - -replacement->next = item->next; -replacement->prev = item->prev; - -if (replacement->next != NULL) -{ -replacement->next->prev = replacement; -} -if (replacement->prev != NULL) -{ -replacement->prev->next = replacement; -} -if (parent->child == item) -{ -parent->child = replacement; -} - -item->next = NULL; -item->prev = NULL; -cJSON_Delete(item); - -return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) -{ -if (which < 0) -{ -return; -} - -cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); -} - -static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) -{ -if (replacement == NULL) -{ -return false; -} - -/* replace the name in the replacement */ -if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) -{ -cJSON_free(replacement->string); -} -replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); -replacement->type &= ~cJSON_StringIsConst; - -cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); - -return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) -{ -replace_item_in_object(object, string, newitem, false); -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) -{ -replace_item_in_object(object, string, newitem, true); -} - -/* Create basic types: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_NULL; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_True; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_False; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) -{ -cJSON *item = cJSON_New_Item(&global_hooks); -if(item) -{ -item->type = b ? cJSON_True : cJSON_False; -} - -return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Number; - item->valuedouble = num; - - /* use saturation in case of overflow */ - if (num >= INT_MAX) - { - item->valueint = INT_MAX; - } - else if (num <= INT_MIN) - { - item->valueint = INT_MIN; - } - else - { - item->valueint = (int)num; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_String; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Raw; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type=cJSON_Array; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if (item) - { - item->type = cJSON_Object; - } - - return item; -} - -/* Create Arrays: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if (count < 0) - { - return NULL; - } - - a = cJSON_CreateArray(); - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if (!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if (count < 0) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber((double)numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if (count < 0) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0;a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if (count < 0) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for (i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateString(strings[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p,n); - } - p = n; - } - - return a; -} - -/* Duplication */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) -{ - cJSON *newitem = NULL; - cJSON *child = NULL; - cJSON *next = NULL; - cJSON *newchild = NULL; - - /* Bail on bad ptr */ - if (!item) - { - goto fail; - } - /* Create new item */ - newitem = cJSON_New_Item(&global_hooks); - if (!newitem) - { - goto fail; - } - /* Copy over all vars */ - newitem->type = item->type & (~cJSON_IsReference); - newitem->valueint = item->valueint; - newitem->valuedouble = item->valuedouble; - if (item->valuestring) - { - newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); - if (!newitem->valuestring) - { - goto fail; - } - } - if (item->string) - { - newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); - if (!newitem->string) - { - goto fail; - } - } - /* If non-recursive, then we're done! */ - if (!recurse) - { - return newitem; - } - /* Walk the ->next chain for the child. */ - child = item->child; - while (child != NULL) - { - newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) - { - goto fail; - } - if (next != NULL) - { - /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - next->next = newchild; - newchild->prev = next; - next = newchild; - } - else - { - /* Set newitem->child and move to it */ - newitem->child = newchild; - next = newchild; - } - child = child->next; - } - - return newitem; - - fail: - if (newitem != NULL) - { - cJSON_Delete(newitem); - } - - return NULL; -} - -CJSON_PUBLIC(void) cJSON_Minify(char *json) -{ - unsigned char *into = (unsigned char*)json; - while (*json) - { - if (*json == ' ') - { - json++; - } - else if (*json == '\t') - { - /* Whitespace characters. */ - json++; - } - else if (*json == '\r') - { - json++; - } - else if (*json=='\n') - { - json++; - } - else if ((*json == '/') && (json[1] == '/')) - { - /* double-slash comments, to end of line. */ - while (*json && (*json != '\n')) - { - json++; - } - } - else if ((*json == '/') && (json[1] == '*')) - { - /* multiline comments. */ - while (*json && !((*json == '*') && (json[1] == '/'))) - { - json++; - } - json += 2; - } - else if (*json == '\"') - { - /* string literals, which are \" sensitive. */ - *into++ = (unsigned char)*json++; - while (*json && (*json != '\"')) - { - if (*json == '\\') - { - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - else - { - /* All other characters. */ - *into++ = (unsigned char)*json++; - } - } - - /* and null-terminate. */ - *into = '\0'; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Invalid; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_False; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xff) == cJSON_True; -} - - -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & (cJSON_True | cJSON_False)) != 0; -} -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_NULL; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Number; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_String; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Array; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Object; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Raw; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) -{ - if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) - { - return false; - } - - /* check if type is valid */ - switch (a->type & 0xFF) - { - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - case cJSON_Number: - case cJSON_String: - case cJSON_Raw: - case cJSON_Array: - case cJSON_Object: - break; - - default: - return false; - } - - /* identical objects are equal */ - if (a == b) - { - return true; - } - - switch (a->type & 0xFF) - { - /* in these cases and equal type is enough */ - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - return true; - - case cJSON_Number: - if (a->valuedouble == b->valuedouble) - { - return true; - } - return false; - - case cJSON_String: - case cJSON_Raw: - if ((a->valuestring == NULL) || (b->valuestring == NULL)) - { - return false; - } - if (strcmp(a->valuestring, b->valuestring) == 0) - { - return true; - } - - return false; - - case cJSON_Array: - { - cJSON *a_element = a->child; - cJSON *b_element = b->child; - - for (; (a_element != NULL) && (b_element != NULL);) - { - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - - a_element = a_element->next; - b_element = b_element->next; - } - - return true; - } - - case cJSON_Object: - { - cJSON *a_element = NULL; - cJSON_ArrayForEach(a_element, a) - { - /* TODO This has O(n^2) runtime, which is horrible! */ - cJSON *b_element = get_object_item(b, a_element->string, case_sensitive); - if (b_element == NULL) - { - return false; - } - - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - } - - return true; - } - - default: - return false; - } -} - -CJSON_PUBLIC(void *) cJSON_malloc(size_t size) -{ - return global_hooks.allocate(size); -} - -CJSON_PUBLIC(void) cJSON_free(void *object) -{ - global_hooks.deallocate(object); -} \ No newline at end of file diff --git a/cJSON.h b/cJSON.h deleted file mode 100644 index 51b0aea2..00000000 --- a/cJSON.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef cJSON__h -#define cJSON__h - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* project version */ -#define CJSON_VERSION_MAJOR 1 -#define CJSON_VERSION_MINOR 5 -#define CJSON_VERSION_PATCH 4 - -#include - -/* cJSON Types: */ -#define cJSON_Invalid (0) -#define cJSON_False (1 << 0) -#define cJSON_True (1 << 1) -#define cJSON_NULL (1 << 2) -#define cJSON_Number (1 << 3) -#define cJSON_String (1 << 4) -#define cJSON_Array (1 << 5) -#define cJSON_Object (1 << 6) -#define cJSON_Raw (1 << 7) /* raw json */ - -#define cJSON_IsReference 256 -#define cJSON_StringIsConst 512 - -/* The cJSON structure: */ -typedef struct cJSON -{ - /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON *next; - struct cJSON *prev; - /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - struct cJSON *child; - - /* The type of the item, as above. */ - int type; - - /* The item's string, if type==cJSON_String and type == cJSON_Raw */ - char *valuestring; - /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ - int valueint; - /* The item's number, if type==cJSON_Number */ - double valuedouble; - - /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ - char *string; -} cJSON; - -typedef struct cJSON_Hooks -{ - void *(*malloc_fn)(size_t sz); - void (*free_fn)(void *ptr); -} cJSON_Hooks; - -typedef int cJSON_bool; - -#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) -#define __WINDOWS__ -#endif -#ifdef __WINDOWS__ - -/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: - -CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols -CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) -CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol - -For *nix builds that support visibility attribute, you can define similar behavior by - -setting default visibility to hidden by adding --fvisibility=hidden (for gcc) -or --xldscope=hidden (for sun cc) -to CFLAGS - -then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does - -*/ - -/* export symbols by default, this is necessary for copy pasting the C and header file */ -#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_EXPORT_SYMBOLS -#endif - -#if defined(CJSON_HIDE_SYMBOLS) -#define CJSON_PUBLIC(type) type __stdcall -#elif defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall -#elif defined(CJSON_IMPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall -#endif -#else /* !WIN32 */ -#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) -#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type -#else -#define CJSON_PUBLIC(type) type -#endif -#endif - -/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. - * This is to prevent stack overflows. */ -#ifndef CJSON_NESTING_LIMIT -#define CJSON_NESTING_LIMIT 1000 -#endif - -/* returns the version of cJSON as a string */ -CJSON_PUBLIC(const char*) cJSON_Version(void); - -/* Supply malloc, realloc and free functions to cJSON */ -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); - -/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); -/* Render a cJSON entity to text for transfer/storage. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); -/* Render a cJSON entity to text for transfer/storage without any formatting. */ -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); -/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); -/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ -/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); -/* Delete a cJSON entity and all subentities. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); - -/* Returns the number of items in an array (or object). */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); -/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); -/* Get item "string" from object. Case insensitive. */ -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); - -/* These functions check the type of an item */ -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); - -/* These calls create a cJSON item of the appropriate type. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); -/* raw json */ -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); - -/* These utilities create an Array of count items. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); - -/* Append item to the specified array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); -/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. - * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before - * writing to `item->string` */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); -/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); - -/* Remove/Detatch items from Arrays/Objects. */ -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); - -/* Update array items. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); - -/* Duplicate a cJSON item */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); -/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will -need to be released. With recurse!=0, it will duplicate any children connected to the item. -The item->next and ->prev pointers are always zero on return from Duplicate. */ -/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. - * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); - - -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); - -CJSON_PUBLIC(void) cJSON_Minify(char *json); - -/* Macros for creating things quickly. */ -#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) -#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) -#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) -#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) -#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) -#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) -#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s)) - -/* When assigning an integer value, it needs to be propagated to valuedouble too. */ -#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) -/* helper for the cJSON_SetNumberValue macro */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); -#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) - -/* Macro for iterating over an array or object */ -#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) - -/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ -CJSON_PUBLIC(void *) cJSON_malloc(size_t size); -CJSON_PUBLIC(void) cJSON_free(void *object); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/request.c b/request.c index 1f3ebd74..d1e2b125 100644 --- a/request.c +++ b/request.c @@ -10,7 +10,6 @@ #include "FTL.h" #include "version.h" -#include "cJSON.h" // Private #define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) @@ -282,23 +281,12 @@ void getStats(int *sock, char type) if(type == SOCKET) { ssend(*sock, "domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ - counters.gravity, total, blocked, percentage); + counters.gravity, total, blocked, percentage); ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ - counters.domains, counters.forwardedqueries, counters.cached); + counters.domains, counters.forwardedqueries, counters.cached); } else { - // cJSON *response = cJSON_CreateObject(); - - // cJSON_AddNumberToObject(response, "domains_being_blocked", counters.gravity); - // cJSON_AddNumberToObject(response, "dns_queries_today", total); - // cJSON_AddNumberToObject(response, "ads_blocked_today", blocked); - // cJSON_AddNumberToObject(response, "ads_percentage_today", percentage); - // cJSON_AddNumberToObject(response, "unique_domains", counters.domains); - // cJSON_AddNumberToObject(response, "queries_forwarded", counters.forwardedqueries); - // cJSON_AddNumberToObject(response, "queries_cached", counters.cached); - - // sendAPIResponse(*sock, cJSON_Print(response)); char *sendbuffer; int ret = asprintf(&sendbuffer, "{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); if(ret > 0) From 776bf43e9ed33af6d49e4d46f1989a4fd94dec4a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 00:05:17 +0200 Subject: [PATCH 021/168] Added /stats/top_clients --- request.c | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/request.c b/request.c index d1e2b125..d1a4924b 100644 --- a/request.c +++ b/request.c @@ -167,6 +167,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getTopDomains(client_message, sock, type); } + else if(command(client_message, "GET /stats/top_clients")) + { + getTopClients(client_message, sock, type); + } else if(header) { ssend(*sock, @@ -366,7 +370,7 @@ void getTopDomains(char *client_message, int *sock, char type) // Match both top-domains and top-ads // SOCKET: >top-domains (15) - // API: /top/domains?limit=15 + // API: /stats/top_domains?limit=15 if(sscanf(client_message, "%*[^0123456789H\n]%i", &num) > 0) { // User wants a different number of requests @@ -375,7 +379,7 @@ void getTopDomains(char *client_message, int *sock, char type) // Apply Audit Log filtering? // SOCKET: >top-domains for audit - // API: /top/domains?audit + // API: /stats/top_domains?audit if(type == SOCKET && command(client_message, " for audit")) { audit = true; @@ -387,7 +391,7 @@ void getTopDomains(char *client_message, int *sock, char type) // Sort in descending order? // SOCKET: >top-domains desc - // API: /top/domains?order=desc + // API: /stats/top_domains?order=desc if(type == SOCKET && command(client_message, " desc")) { desc = true; @@ -518,6 +522,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(excludedomains != NULL) clearSetupVarsArray(); + if(debugclients) { if(blocked) @@ -531,7 +536,10 @@ void getTopClients(char *client_message, int *sock, char type) { int i, temparray[counters.clients][2], count=10, num; - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) + // Match both top-domains and top-ads + // SOCKET: >top-clients (15) + // API: /stats/top_clients?limit=15 + if(sscanf(client_message, "%*[^0123456789H\n]%i", &num) > 0) { // User wants a different number of requests count = num; @@ -556,7 +564,13 @@ void getTopClients(char *client_message, int *sock, char type) logg("Excluding %i clients from being displayed", setupVarsElements); } - int skip = 0; + if(type != SOCKET) + {// First send header with unspecified content-length outside of the for-loop + sendAPIResponse(*sock, "", type); + ssend(*sock, "{\"top_clients\":{"); + } + + int skip = 0; bool first = true; for(i=0; i < min(counters.clients, count+skip); i++) { // Get sorted indices @@ -576,11 +590,28 @@ void getTopClients(char *client_message, int *sock, char type) if(clients[j].count > 0) { - ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); + if(type == SOCKET) + { + ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); + } + else + { + if(!first) ssend(*sock,","); + first = false; + if(strlen(clients[j].name) > 0) + ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); + else + ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); + } } } + + if(type != SOCKET) + ssend(*sock,"}}"); + if(excludeclients != NULL) clearSetupVarsArray(); + if(debugclients) logg("Sent top clients data to client, ID: %i", *sock); } From 7af31af652444450efb2817efd6364fc05278115 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 13 Jun 2017 21:37:11 -0400 Subject: [PATCH 022/168] Add `/stats/forward_dest` --- request.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index d1a4924b..e7c66068 100644 --- a/request.c +++ b/request.c @@ -171,6 +171,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getTopClients(client_message, sock, type); } + else if(command(client_message, "GET /stats/forward_dest")) + { + getForwardDestinations(sock, type); + } else if(header) { ssend(*sock, @@ -619,7 +623,7 @@ void getTopClients(char *client_message, int *sock, char type) void getForwardDestinations(int *sock, char type) { - bool allocated = false; + bool allocated = false, first = true; int i, temparray[counters.forwarded+1][2]; for(i=0; i < counters.forwarded; i++) { @@ -635,6 +639,13 @@ void getForwardDestinations(int *sock, char type) // Sort temporary array in descending order qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); + // Send HTTP headers with unknown content length + sendAPIResponse(*sock, "", type); + + // Send initial JSON output + if(type != SOCKET) + ssend(*sock, "{\"forward_destinations\":{"); + // Loop over available forward destinations for(i=0; i < min(counters.forwarded+1, 10); i++) { @@ -666,7 +677,20 @@ void getForwardDestinations(int *sock, char type) // Send data if count > 0 if(count > 0) { - ssend(*sock,"%i %i %s %s\n",i,count,ip,name); + if(type == SOCKET) + { + ssend(*sock,"%i %i %s %s\n",i,count,ip,name); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + + if(strlen(name) > 0) + ssend(*sock, "\"%s|%s\":%i", name, ip, count); + else + ssend(*sock, "\"%s\":%i", ip, count); + } } // Free previously allocated memory only if we allocated it @@ -676,6 +700,10 @@ void getForwardDestinations(int *sock, char type) free(name); } } + + if(type != SOCKET) + ssend(*sock, "}}"); + if(debugclients) logg("Sent forward destination data to client, ID: %i", *sock); } From 0e7f484673e1d8c4676f104d13c8c6eea429b708 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 13 Jun 2017 21:59:34 -0400 Subject: [PATCH 023/168] Add `/stats/query_types` --- request.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/request.c b/request.c index e7c66068..7f6401f0 100644 --- a/request.c +++ b/request.c @@ -175,6 +175,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getForwardDestinations(sock, type); } + else if(command(client_message, "GET /stats/query_types")) + { + getQueryTypes(sock, type); + } else if(header) { ssend(*sock, @@ -730,8 +734,28 @@ void getForwardNames(int *sock, char type) void getQueryTypes(int *sock, char type) { + if(type == SOCKET) + ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); + else + { + char * response; + if(0 > asprintf( + &response, + "{\"querytypes\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", + counters.IPv4, + counters.IPv6, + counters.PTR, + counters.SRV + )) + { + logg("FATAL: Unable to allocate memory for /stats/query_types"); + exit(EXIT_FAILURE); + } + + sendAPIResponse(*sock, response, type); + free(response); + } - ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); if(debugclients) logg("Sent query type data to client, ID: %i", *sock); } From 249b9e981dabe74c006c9b25945f4d0be785d09b Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 13 Jun 2017 22:21:48 -0400 Subject: [PATCH 024/168] Add alias path for forward destinations Also make query types output more like the other responses. --- request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index 7f6401f0..fef1bbf0 100644 --- a/request.c +++ b/request.c @@ -171,7 +171,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getTopClients(client_message, sock, type); } - else if(command(client_message, "GET /stats/forward_dest")) + else if(command(client_message, "GET /stats/forward_dest") || command(client_message, "GET /stats/forward_destinations")) { getForwardDestinations(sock, type); } @@ -741,7 +741,7 @@ void getQueryTypes(int *sock, char type) char * response; if(0 > asprintf( &response, - "{\"querytypes\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", + "{\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", counters.IPv4, counters.IPv6, counters.PTR, From ea0a7cc127033ac2557c900d01ffedd4a847ad9f Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 13 Jun 2017 22:48:09 -0400 Subject: [PATCH 025/168] Make memory error in getQueryTypes not fatal --- request.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/request.c b/request.c index fef1bbf0..a77138af 100644 --- a/request.c +++ b/request.c @@ -739,20 +739,20 @@ void getQueryTypes(int *sock, char type) else { char * response; - if(0 > asprintf( + int ret = asprintf( &response, "{\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", counters.IPv4, counters.IPv6, counters.PTR, counters.SRV - )) - { - logg("FATAL: Unable to allocate memory for /stats/query_types"); - exit(EXIT_FAILURE); - } + ); + + if(ret > 0) + sendAPIResponse(*sock, response, type); + else + logg("Error allocating memory for API response (getQueryTypes)"); - sendAPIResponse(*sock, response, type); free(response); } From 9cff88566c3d31087245b54d6af2d832f2da818d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 13:01:27 +0200 Subject: [PATCH 026/168] Two improvements: 1. Analyze only first line of HTTP request (before the whole header was analyzed), 2. Anaylze passed GET arguments properly, e.g. allow ?limit=123, but ignore ?sfqw=123 --- request.c | 46 +++++++++++++++---- socket.c | 132 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 114 insertions(+), 64 deletions(-) diff --git a/request.c b/request.c index a77138af..654a2839 100644 --- a/request.c +++ b/request.c @@ -379,10 +379,25 @@ void getTopDomains(char *client_message, int *sock, char type) // Match both top-domains and top-ads // SOCKET: >top-domains (15) // API: /stats/top_domains?limit=15 - if(sscanf(client_message, "%*[^0123456789H\n]%i", &num) > 0) + if(type == SOCKET) { - // User wants a different number of requests - count = num; + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } } // Apply Audit Log filtering? @@ -404,9 +419,9 @@ void getTopDomains(char *client_message, int *sock, char type) { desc = true; } - else if(type != SOCKET && command(client_message, "desc")) + else if(type != SOCKET && command(client_message, "order=desc")) { - audit = true; + desc = true; } for(i=0; i < counters.domains; i++) @@ -547,10 +562,25 @@ void getTopClients(char *client_message, int *sock, char type) // Match both top-domains and top-ads // SOCKET: >top-clients (15) // API: /stats/top_clients?limit=15 - if(sscanf(client_message, "%*[^0123456789H\n]%i", &num) > 0) + if(type == SOCKET) { - // User wants a different number of requests - count = num; + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } } for(i=0; i < counters.clients; i++) diff --git a/socket.c b/socket.c index 8411dcd3..5d2d5a33 100644 --- a/socket.c +++ b/socket.c @@ -302,74 +302,94 @@ void *api_connection_handler_thread(void *socket_desc) char threadname[16]; sprintf(threadname,"api-%i",sockID); prctl(PR_SET_NAME,threadname,0,0,0); + //Receive from client - ssize_t n; - while((n = recv(sock,client_message,SOCKETBUFFERLEN-1, 0))) + if(recv(sock, client_message, SOCKETBUFFERLEN-1, 0) > 0) { - if (n > 0) + char *message = calloc(strlen(client_message)+1,sizeof(char)); + strcpy(message, client_message); + + // Clear client message receive buffer + memset(client_message, 0, sizeof client_message); + + if(debug) + logg("%s", message); + + if(strncmp(message, "GET ", 4) == 0) { - char *message = calloc(strlen(client_message)+1,sizeof(char)); - strcpy(message, client_message); - - // Clear client message receive buffer - memset(client_message, 0, sizeof client_message); - if(debug) - logg("%s", message); + logg("API GET request received"); + // HTTP requests can be simple or full. + // A simple request contains one line only, and looks like this: + // GET /index.html + // A full request can contain more than one line and may look like this: + // GET /index.html HTTP/1.1 + // User-Agent: Wget/1.16 (linux-gnueabihf) + // Accept: */* + // Host: 127.0.0.1:4747 + // Connection: Keep-Alive + bool header = false; - if(strncmp(message, "GET ", 4) == 0) + // Extract requested URL including arguments + const char *p2; + if(strstr(message, "HTTP/") != NULL) { - if(debug) - logg("API GET request received"); - // HTTP requests can be simple or full. - // A simple request contains one line only, and looks like this: - // GET /index.html - // A full request can contain more than one line and may look like this: - // GET /index.html HTTP/1.1 - // User-Agent: Wget/1.16 (linux-gnueabihf) - // Accept: */* - // Host: 127.0.0.1:4747 - // Connection: Keep-Alive - bool header = false; - if(strstr(message, "HTTP/") != NULL) - { - // Output HTTP response headers only if we have a full request - header = true; - } - - // Are we asked for a favicon? - if(strstr(message, "GET /favicon.ico") != NULL) - ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); - else - process_api_request(message, &sock, header); - - // Close connection to show that we reached the end of the transmission - close(sock); - sock = 0; - } - else if(strncmp(message, "HEAD ", 5) == 0) - { - // HEAD request: We do not send any content at all - if(debug) - logg("API HEAD request received"); - - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); - - // Close connection to show that we reached the end of the transmission - close(sock); - sock = 0; + // Output HTTP response headers only if we have a full request + header = true; + // End of request = "HTTP/" + p2 = strstr(message, " HTTP/"); } else { - if(debug) - logg("API received something strange"); + // End of requst = end of first line + p2 = strstr(message, "\n"); + } + if(p2 != NULL) + { + size_t len = p2 - message; + char *request = calloc(len+1, sizeof(char)); + strncpy(request, message, len); + request[len] = '\0'; + logg("request: \"%s\" (%i)", request, (int)len); + + // Are we asked for a favicon? + if(strstr(request, "/favicon.ico") != NULL) + ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); + else + process_api_request(request, &sock, header); + + // Free allocated memory + free(request); + } + else + { + logg("API received malformated request: \"%s\"", message); } - free(message); - - // Disconnect client - break; + // Close connection to show that we reached the end of the transmission + close(sock); + sock = 0; } + else if(strncmp(message, "HEAD ", 5) == 0) + { + // HEAD request: We do not send any content at all + if(debug) + logg("API HEAD request received"); + + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); + + // Close connection to show that we reached the end of the transmission + close(sock); + sock = 0; + } + else + { + if(debug) + logg("API received something strange"); + } + + // Free allocated memory + free(message); } //Free the socket pointer From f2f9601fb5cf5590e6233045d914f1fb3fc47780 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 13:27:47 +0200 Subject: [PATCH 027/168] Greatly simplify code by not sending Content-Length (optional according to RFC 2616) --- request.c | 53 ++++++++++++----------------------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/request.c b/request.c index 654a2839..d8b3b0bc 100644 --- a/request.c +++ b/request.c @@ -195,28 +195,14 @@ bool command(char *client_message, const char* cmd) return false; } -void sendAPIResponse(int sock, char *content, char type) { - if(type == APIH && strlen(content) > 0) +void sendAPIResponse(int sock, char type) { + if(type == APIH) { - // Send header and payload - ssend(sock, - "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" - "Content-Type: application/json\nContent-Length: %i\n\n%s", - strlen(content), - content); - } - else if(type == APIH) - { - // Send only header (length of content is not yet known and will be sent out in smaller packets) + // Send header only for full HTTP requests ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" "Content-Type: application/json\n\n"); } - else - { - // Simple request: Don't send header, only payload - ssend(sock,"%s",content); - } } // void formatNumber(bool raw, int n, char* buffer) @@ -299,13 +285,8 @@ void getStats(int *sock, char type) } else { - char *sendbuffer; - int ret = asprintf(&sendbuffer, "{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); - if(ret > 0) - sendAPIResponse(*sock, sendbuffer, type); - else - logg("Error allocating memory for API response (getStats)"); - free(sendbuffer); + sendAPIResponse(*sock, type); + ssend(*sock,"{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); } if(debugclients) @@ -338,7 +319,7 @@ void getOverTime(int *sock, char type) else { // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, "", type); + sendAPIResponse(*sock, type); ssend(*sock,"{\"domains_over_time\":{"); // Send "domains_over_time" data @@ -478,7 +459,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(type != SOCKET) {// First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, "", type); + sendAPIResponse(*sock, type); if(blocked) ssend(*sock, "{\"top_ads\":{"); else @@ -604,7 +585,7 @@ void getTopClients(char *client_message, int *sock, char type) if(type != SOCKET) {// First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, "", type); + sendAPIResponse(*sock, type); ssend(*sock, "{\"top_clients\":{"); } @@ -674,7 +655,7 @@ void getForwardDestinations(int *sock, char type) qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); // Send HTTP headers with unknown content length - sendAPIResponse(*sock, "", type); + sendAPIResponse(*sock, type); // Send initial JSON output if(type != SOCKET) @@ -768,22 +749,12 @@ void getQueryTypes(int *sock, char type) ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); else { - char * response; - int ret = asprintf( - &response, - "{\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", + sendAPIResponse(*sock, type); + ssend(*sock,"{\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", counters.IPv4, counters.IPv6, counters.PTR, - counters.SRV - ); - - if(ret > 0) - sendAPIResponse(*sock, response, type); - else - logg("Error allocating memory for API response (getQueryTypes)"); - - free(response); + counters.SRV); } if(debugclients) From 48d355bab0e48b09029c62a0ccd199eb046d90ed Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 14:09:11 +0200 Subject: [PATCH 028/168] Add "/history" --- request.c | 174 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 129 insertions(+), 45 deletions(-) diff --git a/request.c b/request.c index d8b3b0bc..40c8e987 100644 --- a/request.c +++ b/request.c @@ -179,10 +179,14 @@ void process_api_request(char *client_message, int *sock, bool header) { getQueryTypes(sock, type); } + else if(command(client_message, "GET /history")) + { + getAllQueries(client_message, sock, type); + } else if(header) { ssend(*sock, - "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\n" + "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\"}"); } } @@ -200,7 +204,7 @@ void sendAPIResponse(int sock, char type) { { // Send header only for full HTTP requests ssend(sock, - "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\n" + "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" "Content-Type: application/json\n\n"); } } @@ -772,53 +776,102 @@ void getAllQueries(char *client_message, int *sock, char type) // Do we want a more specific version of this command (domain/client/time interval filtered)? int from = 0, until = 0; bool filtertime = false; - if(command(client_message, ">getallqueries-time")) - { - // Get from to until boundaries - sscanf(client_message, ">getallqueries-time %i %i",&from, &until); - if(debugclients) - { - logg("Showing only limited time interval starting at ",from); - logg("Showing only limited time interval ending at ",until); - } - filtertime = true; - } - char *domainname; + char *domainname = NULL; bool filterdomainname = false; - if(command(client_message, ">getallqueries-domain")) - { - domainname = calloc(128, sizeof(char)); - // Get domain name we want to see only (limit length to 127 chars) - sscanf(client_message, ">getallqueries-domain %127s", domainname); - if(debugclients) - logg("Showing only queries with domain %s", domainname); - filterdomainname = true; - } - char *clientname; + char *clientname = NULL; bool filterclientname = false; - if(command(client_message, ">getallqueries-client")) + + if(type == SOCKET) { - clientname = calloc(128, sizeof(char)); - // Get client name we want to see only (limit length to 127 chars) - sscanf(client_message, ">getallqueries-client %127s", clientname); - if(debugclients) - logg("Showing only queries with client %s", clientname); - filterclientname = true; + // Time filtering? + if(command(client_message, ">getallqueries-time")) + { + sscanf(client_message, ">getallqueries-time %i %i",&from, &until); + filtertime = true; + } + // Domain filtering? + if(command(client_message, ">getallqueries-domain")) + { + sscanf(client_message, ">getallqueries-domain %ms", &domainname); + filterdomainname = true; + } + // Client filtering? + if(command(client_message, ">getallqueries-client")) + { + sscanf(client_message, ">getallqueries-client %ms", &clientname); + filterclientname = true; + } + } + else + { + // Time filtering? + const char * temp = strstr(client_message, "from="); + if(temp != NULL) + { + int num; + if(sscanf(temp, "from=%i", &num) > 0) + { + // User wants a different number of requests + from = num; + } + } + temp = strstr(client_message, "until="); + if(temp != NULL) + { + int num; + if(sscanf(temp, "until=%i", &num) > 0) + { + // User wants a different number of requests + until = num; + } + } + + // Domain filtering? + temp = strstr(client_message, "domain="); + if(temp != NULL) + { + sscanf(temp, "domain=%ms", &domainname); + filterdomainname = true; + } + temp = strstr(client_message, "client="); + + // Client filtering? + if(temp != NULL) + { + sscanf(temp, "client=%ms", &clientname); + filterclientname = true; + } } int ibeg = 0, num; // Test for integer that specifies number of entries to be shown - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) + if(type == SOCKET) { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - if(debugclients) - logg("Showing only limited amount of queries: ",num); + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; + } + } } // Get potentially existing filtering flags @@ -866,7 +919,13 @@ void getAllQueries(char *client_message, int *sock, char type) logg("Privacy mode enabled"); } - int i; + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock, "{\"history\":["); + } + + int i; bool first = true; for(i=ibeg; i < counters.queries; i++) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); @@ -913,22 +972,47 @@ void getAllQueries(char *client_message, int *sock, char type) continue; } - if(!privacymode) + if(type == SOCKET) { - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + if(!privacymode) + { + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + else + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + } else - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + { + ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + } } else { - ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], + if(!first) ssend(*sock, ","); + first = false; + + if(!privacymode) + { + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + else + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + } + else + { + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + } } } + if(type != SOCKET) + ssend(*sock, "]}"); + // Free allocated memory if(filterclientname) free(clientname); + if(filterdomainname) free(domainname); From b5c500fdcffdef068ce5115bda8fa669b5a2488a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 14:22:44 +0200 Subject: [PATCH 029/168] Fix all queries time filtering and SOCKET output determination --- request.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/request.c b/request.c index 40c8e987..55f308ea 100644 --- a/request.c +++ b/request.c @@ -775,7 +775,6 @@ void getAllQueries(char *client_message, int *sock, char type) // Do we want a more specific version of this command (domain/client/time interval filtered)? int from = 0, until = 0; - bool filtertime = false; char *domainname = NULL; bool filterdomainname = false; @@ -789,7 +788,6 @@ void getAllQueries(char *client_message, int *sock, char type) if(command(client_message, ">getallqueries-time")) { sscanf(client_message, ">getallqueries-time %i %i",&from, &until); - filtertime = true; } // Domain filtering? if(command(client_message, ">getallqueries-domain")) @@ -935,14 +933,14 @@ void getAllQueries(char *client_message, int *sock, char type) validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); - char type[5]; + char qtype[5]; if(queries[i].type == 1) { - strcpy(type,"IPv4"); + strcpy(qtype,"IPv4"); } else { - strcpy(type,"IPv6"); + strcpy(qtype,"IPv6"); } if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) @@ -950,12 +948,9 @@ void getAllQueries(char *client_message, int *sock, char type) if((queries[i].status == 2 || queries[i].status == 3) && !showpermitted) continue; - if(filtertime) - { // Skip those entries which so not meet the requested timeframe - if(from > queries[i].timestamp || queries[i].timestamp > until) + if((from > queries[i].timestamp && from != 0) || (queries[i].timestamp > until && until != 0)) continue; - } if(filterdomainname) { @@ -977,13 +972,13 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else { - ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } } else @@ -995,13 +990,13 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"%s\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"%s\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else { - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"hidden\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } } } From 2179f5d5caed2ce61c5fa7f713a968858288af36 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 14:33:05 +0200 Subject: [PATCH 030/168] Don't put integer values in quotes --- request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/request.c b/request.c index 55f308ea..9f880eef 100644 --- a/request.c +++ b/request.c @@ -990,13 +990,13 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"%s\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"%s\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else { - ssend(*sock,"[\"%i\",\"%s\",\"%s\",\"hidden\",\"%i\"]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } } } From b868c568eb5dffab84af4962c526df61ef30b2a0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 14:39:20 +0200 Subject: [PATCH 031/168] Add "/dns/recent_blocked" (can take argument ?limit=123) --- request.c | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/request.c b/request.c index 9f880eef..cb52d4db 100644 --- a/request.c +++ b/request.c @@ -183,6 +183,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getAllQueries(client_message, sock, type); } + else if(command(client_message, "GET /dns/recent_blocked")) + { + getRecentBlocked(client_message, sock, type); + } else if(header) { ssend(*sock, @@ -1024,18 +1028,38 @@ void getRecentBlocked(char *client_message, int *sock, char type) return; // Test for integer that specifies number of entries to be shown - if(sscanf(client_message, ">%*[^(](%i)", &num) > 0) + if(type == SOCKET) { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - - if(debugclients) - logg("Showing several blocked domains ",num); + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; + } } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; + } + } + } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock, "{\"recent_blocked\":["); + } + // Find most recent query with either status 1 (blocked) // or status 4 (wildcard blocked) - int found = 0; + int found = 0; bool first = true; for(i = counters.queries - 1; i > 0 ; i--) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); @@ -1045,7 +1069,16 @@ void getRecentBlocked(char *client_message, int *sock, char type) if(queries[i].status == 1 || queries[i].status == 4) { found++; - ssend(*sock,"%s\n",domains[queries[i].domainID].domain); + if(type == SOCKET) + { + ssend(*sock,"%s\n", domains[queries[i].domainID].domain); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); + } } if(found >= num) @@ -1053,6 +1086,9 @@ void getRecentBlocked(char *client_message, int *sock, char type) break; } } + + if(type != SOCKET) + ssend(*sock, "]}"); } void getMemoryUsage(int *sock, char type) From 2aa2ff1af22ffbb8365b3c405fa74c6a62faa5f0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:16:25 +0200 Subject: [PATCH 032/168] Added "/overTime/forward_dest", renamed "/stats/overTime" -> "/overTime/graphs" --- request.c | 106 +++++++++++++++++++++++++++++++++++++++++------------- socket.c | 3 -- 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/request.c b/request.c index cb52d4db..15654808 100644 --- a/request.c +++ b/request.c @@ -159,7 +159,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getStats(sock, type); } - else if(command(client_message, "GET /stats/overTime")) + else if(command(client_message, "GET /overTime/graphs")) { getOverTime(sock, type); } @@ -187,12 +187,18 @@ void process_api_request(char *client_message, int *sock, bool header) { getRecentBlocked(client_message, sock, type); } + else if(command(client_message, "GET /overTime/forward_dest")) + { + getForwardDestinationsOverTime(sock, type); + } else if(header) { ssend(*sock, "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\"}"); + "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\""); } + + ssend(*sock, "}"); } bool command(char *client_message, const char* cmd) @@ -209,7 +215,7 @@ void sendAPIResponse(int sock, char type) { // Send header only for full HTTP requests ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\n\n"); + "Content-Type: application/json\n\n{"); } } @@ -294,7 +300,7 @@ void getStats(int *sock, char type) else { sendAPIResponse(*sock, type); - ssend(*sock,"{\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i}",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); + ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached); } if(debugclients) @@ -328,7 +334,7 @@ void getOverTime(int *sock, char type) { // First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, type); - ssend(*sock,"{\"domains_over_time\":{"); + ssend(*sock,"\"domains_over_time\":{"); // Send "domains_over_time" data for(i = j; i < counters.overTime; i++) @@ -344,7 +350,7 @@ void getOverTime(int *sock, char type) if(i != j) ssend(*sock, ","); ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); } - ssend(*sock,"}}"); + ssend(*sock,"}"); } if(debugclients) @@ -469,9 +475,9 @@ void getTopDomains(char *client_message, int *sock, char type) {// First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, type); if(blocked) - ssend(*sock, "{\"top_ads\":{"); + ssend(*sock, "\"top_ads\":{"); else - ssend(*sock, "{\"top_queries\":{"); + ssend(*sock, "\"top_queries\":{"); } int skip = 0; bool first = true; @@ -530,7 +536,7 @@ void getTopDomains(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"}}"); + ssend(*sock,"}"); if(excludedomains != NULL) clearSetupVarsArray(); @@ -594,7 +600,7 @@ void getTopClients(char *client_message, int *sock, char type) if(type != SOCKET) {// First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, type); - ssend(*sock, "{\"top_clients\":{"); + ssend(*sock, "\"top_clients\":{"); } int skip = 0; bool first = true; @@ -634,7 +640,7 @@ void getTopClients(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"}}"); + ssend(*sock,"}"); if(excludeclients != NULL) clearSetupVarsArray(); @@ -667,7 +673,7 @@ void getForwardDestinations(int *sock, char type) // Send initial JSON output if(type != SOCKET) - ssend(*sock, "{\"forward_destinations\":{"); + ssend(*sock, "\"forward_destinations\":{"); // Loop over available forward destinations for(i=0; i < min(counters.forwarded+1, 10); i++) @@ -725,7 +731,7 @@ void getForwardDestinations(int *sock, char type) } if(type != SOCKET) - ssend(*sock, "}}"); + ssend(*sock, "}"); if(debugclients) logg("Sent forward destination data to client, ID: %i", *sock); @@ -736,15 +742,35 @@ void getForwardNames(int *sock, char type) { int i; + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"forward_destinations\":{"); + } + for(i=0; i < counters.forwarded; i++) { validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); - // Get sorted indices - ssend(*sock,"%i %i %s %s\n",i,forwarded[i].count,forwarded[i].ip,forwarded[i].name); + if(type == SOCKET) + { + ssend(*sock, "%i %i %s %s\n", i, forwarded[i].count, forwarded[i].ip, forwarded[i].name); + } + else + { + if(strlen(forwarded[i].name) > 0) + ssend(*sock, "\"%s|%s\":%i,", forwarded[i].name, forwarded[i].ip, forwarded[i].count); + else + ssend(*sock, "\"%s\":%i,", forwarded[i].ip, forwarded[i].count); + } + //{"2001:1608:10:25::9249:d69b":2799,"2001:1608:10:25::1c04:b12f":6382,"resolver2.ipv6-sandbox.opendns.com|2620:0:ccd::2":2478,"resolver1.ipv6-sandbox.opendns.com|2620:0:ccc::2":2219,"local|::1":2755}} + } // Add "local" forward destination - ssend(*sock,"%i %i ::1 local\n",counters.forwarded,counters.cached); + if(type == SOCKET) + ssend(*sock,"%i %i ::1 local\n",counters.forwarded,counters.cached); + else + ssend(*sock, "\"local|::1\":%i}", counters.cached); if(debugclients) logg("Sent forward destination names to client, ID: %i", *sock); @@ -758,7 +784,7 @@ void getQueryTypes(int *sock, char type) else { sendAPIResponse(*sock, type); - ssend(*sock,"{\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}}", + ssend(*sock,"\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", counters.IPv4, counters.IPv6, counters.PTR, @@ -924,7 +950,7 @@ void getAllQueries(char *client_message, int *sock, char type) if(type != SOCKET) { sendAPIResponse(*sock, type); - ssend(*sock, "{\"history\":["); + ssend(*sock, "\"history\":["); } int i; bool first = true; @@ -1006,7 +1032,7 @@ void getAllQueries(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock, "]}"); + ssend(*sock, "]"); // Free allocated memory if(filterclientname) @@ -1054,7 +1080,7 @@ void getRecentBlocked(char *client_message, int *sock, char type) if(type != SOCKET) { sendAPIResponse(*sock, type); - ssend(*sock, "{\"recent_blocked\":["); + ssend(*sock, "\"recent_blocked\":["); } // Find most recent query with either status 1 (blocked) @@ -1088,9 +1114,10 @@ void getRecentBlocked(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock, "]}"); + ssend(*sock, "]"); } +// only available via SOCKET void getMemoryUsage(int *sock, char type) { unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); @@ -1130,10 +1157,26 @@ void getForwardDestinationsOverTime(int *sock, char type) } if(sendit > -1) { + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"forward_dest\":{"); + } + + bool first = true; for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - ssend(*sock, "%i", overTime[i].timestamp); + if(type == SOCKET) + { + ssend(*sock, "%i", overTime[i].timestamp); + } + else + { + if(!first) ssend(*sock, ","); + ssend(*sock, "\"%i\":[", overTime[i].timestamp); + first = false; + } int j; @@ -1144,13 +1187,26 @@ void getForwardDestinationsOverTime(int *sock, char type) k = overTime[i].forwarddata[j]; else k = 0; - - ssend(*sock, " %i", k); + if(type == SOCKET) + ssend(*sock, " %i", k); + else + ssend(*sock, "%i,", k); } - ssend(*sock, " %i\n", overTime[i].cached + overTime[i].blocked); + if(type == SOCKET) + ssend(*sock, " %i\n", overTime[i].cached + overTime[i].blocked); + else + ssend(*sock, "%i]", overTime[i].cached + overTime[i].blocked); } } + + if(type != SOCKET) + { + ssend(*sock,"},"); + // Manually set API -> Don't send header a second time + getForwardNames(sock, API); + } + if(debugclients) logg("Sent overTime forwarded data to client, ID: %i", *sock); } diff --git a/socket.c b/socket.c index 5d2d5a33..f9ac184d 100644 --- a/socket.c +++ b/socket.c @@ -312,9 +312,6 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); - if(debug) - logg("%s", message); - if(strncmp(message, "GET ", 4) == 0) { if(debug) From 9d891d5f89d5b38fc5f632ce2efc663562dc8a6e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:30:06 +0200 Subject: [PATCH 033/168] Added "/overTime/query_types" --- request.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index 15654808..28e7e8a4 100644 --- a/request.c +++ b/request.c @@ -191,6 +191,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getForwardDestinationsOverTime(sock, type); } + else if(command(client_message, "GET /overTime/query_types")) + { + getQueryTypesOverTime(sock, type); + } else if(header) { ssend(*sock, @@ -1174,8 +1178,8 @@ void getForwardDestinationsOverTime(int *sock, char type) else { if(!first) ssend(*sock, ","); - ssend(*sock, "\"%i\":[", overTime[i].timestamp); first = false; + ssend(*sock, "\"%i\":[", overTime[i].timestamp); } int j; @@ -1234,12 +1238,32 @@ void getQueryTypesOverTime(int *sock, char type) } if(sendit > -1) { + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"query_types\":{"); + } + + bool first = true; for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - ssend(*sock, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); + if(type == SOCKET) + { + ssend(*sock, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + ssend(*sock, "\"%i\":[%i,%i]", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); + } } } + + if(type != SOCKET) + ssend(*sock,"}"); + if(debugclients) logg("Sent overTime query types data to client, ID: %i", *sock); } From 70e3b5d9f4059ddfa99f172e2bef9d1b4f33724e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:31:43 +0200 Subject: [PATCH 034/168] Move "/overTime/*" -> "/stats/overTime/*" --- request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/request.c b/request.c index 28e7e8a4..d3dba7f5 100644 --- a/request.c +++ b/request.c @@ -159,7 +159,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getStats(sock, type); } - else if(command(client_message, "GET /overTime/graphs")) + else if(command(client_message, "GET /stats/overTime/graphs")) { getOverTime(sock, type); } @@ -187,11 +187,11 @@ void process_api_request(char *client_message, int *sock, bool header) { getRecentBlocked(client_message, sock, type); } - else if(command(client_message, "GET /overTime/forward_dest")) + else if(command(client_message, "GET /stats/overTime/forward_dest")) { getForwardDestinationsOverTime(sock, type); } - else if(command(client_message, "GET /overTime/query_types")) + else if(command(client_message, "GET /stats/overTime/query_types")) { getQueryTypesOverTime(sock, type); } From 38ea0a7b889b88eb669945c9ccafd7700120715d Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:43:15 +0200 Subject: [PATCH 035/168] Use thread locks for API responses --- socket.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/socket.c b/socket.c index f9ac184d..179495c1 100644 --- a/socket.c +++ b/socket.c @@ -353,7 +353,11 @@ void *api_connection_handler_thread(void *socket_desc) if(strstr(request, "/favicon.ico") != NULL) ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); else + { + enable_thread_lock(threadname); process_api_request(request, &sock, header); + disable_thread_lock(threadname); + } // Free allocated memory free(request); From 942250e936d4720aba40e3574cb37dab6c86a075 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:52:00 +0200 Subject: [PATCH 036/168] Also send "dns_queries_today" in Top Clients request --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index d3dba7f5..6d14a316 100644 --- a/request.c +++ b/request.c @@ -644,7 +644,7 @@ void getTopClients(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"}"); + ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); if(excludeclients != NULL) clearSetupVarsArray(); From 30a3f7e90f1ea38fbee22bec0bd4bd983ed0b050 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:53:14 +0200 Subject: [PATCH 037/168] Also send "dns_queries_today" in Top Domains / Ads requests --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index 6d14a316..032c14b6 100644 --- a/request.c +++ b/request.c @@ -540,7 +540,7 @@ void getTopDomains(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"}"); + ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); if(excludedomains != NULL) clearSetupVarsArray(); From 59fe8732bbb5fbd5f3a5a5f1fbf5ee91d125e951 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 16:54:05 +0200 Subject: [PATCH 038/168] Return what is aksed for (top_domains) --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index 032c14b6..65af2f38 100644 --- a/request.c +++ b/request.c @@ -481,7 +481,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(blocked) ssend(*sock, "\"top_ads\":{"); else - ssend(*sock, "\"top_queries\":{"); + ssend(*sock, "\"top_domains\":{"); } int skip = 0; bool first = true; From 5df32339a81e4463dc4d6a7227ff3c5983ebfe80 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 14 Jun 2017 17:39:57 +0200 Subject: [PATCH 039/168] Send two additional counter values along with the top lists --- request.c | 2 +- socket.c | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/request.c b/request.c index 65af2f38..6c07e545 100644 --- a/request.c +++ b/request.c @@ -540,7 +540,7 @@ void getTopDomains(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); + ssend(*sock,"},\"dns_queries_today\":%i,\"ads_blocked_today\":%i", (counters.queries - counters.invalidqueries), counters.blocked); if(excludedomains != NULL) clearSetupVarsArray(); diff --git a/socket.c b/socket.c index 179495c1..04e9a8bd 100644 --- a/socket.c +++ b/socket.c @@ -314,8 +314,6 @@ void *api_connection_handler_thread(void *socket_desc) if(strncmp(message, "GET ", 4) == 0) { - if(debug) - logg("API GET request received"); // HTTP requests can be simple or full. // A simple request contains one line only, and looks like this: // GET /index.html @@ -347,7 +345,6 @@ void *api_connection_handler_thread(void *socket_desc) char *request = calloc(len+1, sizeof(char)); strncpy(request, message, len); request[len] = '\0'; - logg("request: \"%s\" (%i)", request, (int)len); // Are we asked for a favicon? if(strstr(request, "/favicon.ico") != NULL) @@ -374,8 +371,6 @@ void *api_connection_handler_thread(void *socket_desc) else if(strncmp(message, "HEAD ", 5) == 0) { // HEAD request: We do not send any content at all - if(debug) - logg("API HEAD request received"); ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); From ecc2ef3a1397043988fdb75a412c0de7aaa3a863 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 14 Jun 2017 20:47:04 -0400 Subject: [PATCH 040/168] Do some formatting --- request.c | 48 ++++++++++++------------------------------------ 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/request.c b/request.c index 6c07e545..9f970abf 100644 --- a/request.c +++ b/request.c @@ -207,10 +207,7 @@ void process_api_request(char *client_message, int *sock, bool header) bool command(char *client_message, const char* cmd) { - if(strstr(client_message,cmd) != NULL) - return true; - else - return false; + return strstr(client_message,cmd) != NULL; } void sendAPIResponse(int sock, char type) { @@ -291,9 +288,7 @@ void getStats(int *sock, char type) float percentage = 0.0; // Avoid 1/0 condition if(total > 0) - { percentage = 1e2*blocked/total; - } if(type == SOCKET) { ssend(*sock, "domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ @@ -403,25 +398,17 @@ void getTopDomains(char *client_message, int *sock, char type) // SOCKET: >top-domains for audit // API: /stats/top_domains?audit if(type == SOCKET && command(client_message, " for audit")) - { audit = true; - } else if(type != SOCKET && command(client_message, "audit")) - { audit = true; - } // Sort in descending order? // SOCKET: >top-domains desc // API: /stats/top_domains?order=desc if(type == SOCKET && command(client_message, " desc")) - { desc = true; - } else if(type != SOCKET && command(client_message, "order=desc")) - { desc = true; - } for(i=0; i < counters.domains; i++) { @@ -447,13 +434,9 @@ void getTopDomains(char *client_message, int *sock, char type) if(filter != NULL) { if((strcmp(filter, "permittedonly")) == 0) - { showblocked = false; - } else if((strcmp(filter, "blockedonly")) == 0) - { showpermitted = false; - } else if((strcmp(filter, "nothing")) == 0) { showpermitted = false; @@ -470,14 +453,17 @@ void getTopDomains(char *client_message, int *sock, char type) if(excludedomains != NULL) { getSetupVarsArray(excludedomains); + if(debugclients) logg("Excluding %i domains from being displayed", setupVarsElements); } } if(type != SOCKET) - {// First send header with unspecified content-length outside of the for-loop + { + // First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, type); + if(blocked) ssend(*sock, "\"top_ads\":{"); else @@ -597,12 +583,14 @@ void getTopClients(char *client_message, int *sock, char type) if(excludeclients != NULL) { getSetupVarsArray(excludeclients); + if(debugclients) logg("Excluding %i clients from being displayed", setupVarsElements); } if(type != SOCKET) - {// First send header with unspecified content-length outside of the for-loop + { + // First send header with unspecified content-length outside of the for-loop sendAPIResponse(*sock, type); ssend(*sock, "\"top_clients\":{"); } @@ -788,7 +776,7 @@ void getQueryTypes(int *sock, char type) else { sendAPIResponse(*sock, type); - ssend(*sock,"\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", + ssend(*sock, "\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", counters.IPv4, counters.IPv6, counters.PTR, @@ -912,13 +900,9 @@ void getAllQueries(char *client_message, int *sock, char type) if(filter != NULL) { if((strcmp(filter, "permittedonly")) == 0) - { showblocked = false; - } else if((strcmp(filter, "blockedonly")) == 0) - { showpermitted = false; - } else if((strcmp(filter, "nothing")) == 0) { showpermitted = false; @@ -930,9 +914,11 @@ void getAllQueries(char *client_message, int *sock, char type) // Get privacy mode flag char * privacy = read_setupVarsconf("API_PRIVACY_MODE"); bool privacymode = false; + if(privacy != NULL) if(getSetupVarsBool(privacy)) privacymode = true; + clearSetupVarsArray(); if(debugclients) @@ -969,13 +955,9 @@ void getAllQueries(char *client_message, int *sock, char type) char qtype[5]; if(queries[i].type == 1) - { strcpy(qtype,"IPv4"); - } else - { strcpy(qtype,"IPv6"); - } if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) continue; @@ -1011,9 +993,7 @@ void getAllQueries(char *client_message, int *sock, char type) ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else - { ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); - } } else { @@ -1029,9 +1009,7 @@ void getAllQueries(char *client_message, int *sock, char type) ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else - { - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); - } + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } } @@ -1112,9 +1090,7 @@ void getRecentBlocked(char *client_message, int *sock, char type) } if(found >= num) - { break; - } } if(type != SOCKET) From 446a1b97073c36b27c4ec951a5fb33b4b242e93f Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 14 Jun 2017 21:31:01 -0400 Subject: [PATCH 041/168] Add GET /dns/whitelist and /dns/blacklist --- FTL.h | 1 + request.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/FTL.h b/FTL.h index c664da79..0bd1b0d3 100644 --- a/FTL.h +++ b/FTL.h @@ -183,6 +183,7 @@ typedef struct { enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; enum { SOCKET, API, APIH }; +enum { WHITELIST, BLACKLIST }; logFileNamesStruct files; FTLFileNamesStruct FTLfiles; diff --git a/request.c b/request.c index 9f970abf..6f13ce1f 100644 --- a/request.c +++ b/request.c @@ -31,6 +31,7 @@ void getClientID(int *sock, char type); void getQueryTypesOverTime(int *sock, char type); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); +void getList(int *sock, char type, char list_type); void process_socket_request(char *client_message, int *sock) { @@ -195,6 +196,14 @@ void process_api_request(char *client_message, int *sock, bool header) { getQueryTypesOverTime(sock, type); } + else if(command(client_message, "GET /dns/whitelist")) + { + getList(sock, type, WHITELIST); + } + else if(command(client_message, "GET /dns/blacklist")) + { + getList(sock, type, BLACKLIST); + } else if(header) { ssend(*sock, @@ -1272,3 +1281,38 @@ void getDBstats(int *sock, char type) if(debugclients) logg("Sent DB info to client, ID: %i", *sock); } + +void getList(int *sock, char type, char list_type) +{ + FILE *fp; + char line[255]; + + if((fp = fopen(list_type == WHITELIST ? "/etc/pihole/whitelist.txt" : "/etc/pihole/blacklist.txt", "r")) != NULL) + { + bool first = true; + sendAPIResponse(*sock, type); + ssend(*sock, "\"data\":["); + + while(fgets(line, sizeof(line), fp)) { + // Skip empty lines + if(line[0] == '\n') + continue; + + if(!first) ssend(*sock, ","); + first = false; + + // Trim off the newline, if it exists + size_t len = strlen(line); + if(len > 0 && line[len-1] == '\n') + line[len-1] = 0; + + ssend(*sock, "\"%s\"", line); + } + + ssend(*sock, "]"); + + fclose(fp); + } + else + logg("ERROR: Unable to read %s"); +} From cce43dc36e086255d713b731ecaebed3db97ed4e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 10:07:54 +0200 Subject: [PATCH 042/168] It is no severe issue if the lists (e.g. blacklist) are not there - so we should just return an empty array. --- request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/request.c b/request.c index 6f13ce1f..c4fd1d31 100644 --- a/request.c +++ b/request.c @@ -1314,5 +1314,7 @@ void getList(int *sock, char type, char list_type) fclose(fp); } else - logg("ERROR: Unable to read %s"); + { + ssend(*sock, "\"data\":[]"); + } } From 1905f9cb71793462fd4fbe62a2d39ec85937302a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 10:12:14 +0200 Subject: [PATCH 043/168] Return data with key "blacklist" or "whitelist" instead of "data". Use predefined constants for the location of the white- and blacklist so they can easily be changed in one place, if needed. --- request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/request.c b/request.c index c4fd1d31..8e70f0f9 100644 --- a/request.c +++ b/request.c @@ -1287,11 +1287,11 @@ void getList(int *sock, char type, char list_type) FILE *fp; char line[255]; - if((fp = fopen(list_type == WHITELIST ? "/etc/pihole/whitelist.txt" : "/etc/pihole/blacklist.txt", "r")) != NULL) + if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) { bool first = true; sendAPIResponse(*sock, type); - ssend(*sock, "\"data\":["); + ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); while(fgets(line, sizeof(line), fp)) { // Skip empty lines @@ -1315,6 +1315,6 @@ void getList(int *sock, char type, char list_type) } else { - ssend(*sock, "\"data\":[]"); + ssend(*sock, "\"%s\":[]", list_type == WHITELIST ? "whitelist" : "blacklist"); } } From 0e3f8b5f067afab3e1ad869095f15cbcc745e06c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 10:15:28 +0200 Subject: [PATCH 044/168] Use getline() instead of the deprecated fgets() to avoid havng to used a fixed size buffer --- request.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index 8e70f0f9..2cd5ae76 100644 --- a/request.c +++ b/request.c @@ -1285,7 +1285,8 @@ void getDBstats(int *sock, char type) void getList(int *sock, char type, char list_type) { FILE *fp; - char line[255]; + char *line = NULL; + size_t size = 0; if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) { @@ -1293,7 +1294,7 @@ void getList(int *sock, char type, char list_type) sendAPIResponse(*sock, type); ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); - while(fgets(line, sizeof(line), fp)) { + while(getline(&line, &size, fp) != -1) { // Skip empty lines if(line[0] == '\n') continue; @@ -1308,6 +1309,12 @@ void getList(int *sock, char type, char list_type) ssend(*sock, "\"%s\"", line); } + // Free allocated memory + if(line != NULL) + { + free(line); + line = NULL; + } ssend(*sock, "]"); From 7cef4728761a3fea0479ac7fe822ac517a6403f2 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 10:27:11 +0200 Subject: [PATCH 045/168] Imporve triming of newline character when reading in the lists. The function strcspn() counts the number of characters until it hits a '\r' or a '\n' (in other words, it finds the first '\r' or '\n'). If it doesn't hit anything, it stops at the '\0' (returning the length of the string). Note that this works fine even if there is no newline, because strcspn stops at a '\0'. In that case, the entire line is simply replacing '\0' with '\0'. --- grep.c | 6 +++++- request.c | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/grep.c b/grep.c index 112f1827..65537c7d 100644 --- a/grep.c +++ b/grep.c @@ -101,8 +101,12 @@ void readWildcardsList() return; } + + // Trim off the newline (could even be CR-LF) + linebuffer[strcspn(linebuffer, "\r\n")] = 0; + // Try to read up to 511 characters - if(sscanf(linebuffer, "address=/%511[^/]/%*[^\n]\n", buffer) > 0) + if(sscanf(linebuffer, "address=/%511[^/]/", buffer) > 0) { unsigned long int addrbuffer = 0; // Skip leading '.' by incrementing memory location step by step until the first diff --git a/request.c b/request.c index 2cd5ae76..f1c1d027 100644 --- a/request.c +++ b/request.c @@ -1303,9 +1303,7 @@ void getList(int *sock, char type, char list_type) first = false; // Trim off the newline, if it exists - size_t len = strlen(line); - if(len > 0 && line[len-1] == '\n') - line[len-1] = 0; + line[strcspn(line, "\r\n")] = 0; ssend(*sock, "\"%s\"", line); } From c8f349d8db841d6b4fe6ff06ef827155122cc9c0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 11:16:19 +0200 Subject: [PATCH 046/168] Stop at "&" after domain/client filtering strings --- request.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index f1c1d027..0314ff71 100644 --- a/request.c +++ b/request.c @@ -861,7 +861,10 @@ void getAllQueries(char *client_message, int *sock, char type) temp = strstr(client_message, "domain="); if(temp != NULL) { - sscanf(temp, "domain=%ms", &domainname); + char *temp2 = strdup(temp); + temp2[strcspn(temp2, "&")] = 0; + sscanf(temp2, "domain=%ms", &domainname); + free(temp2); filterdomainname = true; } temp = strstr(client_message, "client="); @@ -869,7 +872,10 @@ void getAllQueries(char *client_message, int *sock, char type) // Client filtering? if(temp != NULL) { - sscanf(temp, "client=%ms", &clientname); + char *temp2 = strdup(temp); + temp2[strcspn(temp2, "&")] = 0; + sscanf(temp2, "client=%ms", &clientname); + free(temp2); filterclientname = true; } } From cb9d1cd61732aee8c1d17198d4988771009360db Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 15 Jun 2017 11:37:10 +0200 Subject: [PATCH 047/168] Add "/dns/status" --- FTL.h | 1 + request.c | 12 ++++++++++++ structs.c | 1 + 3 files changed, 14 insertions(+) diff --git a/FTL.h b/FTL.h index 0bd1b0d3..00290654 100644 --- a/FTL.h +++ b/FTL.h @@ -84,6 +84,7 @@ typedef struct { const char* whitelist; const char* blacklist; const char* setupVars; + const char* dnsmasqconf; const char* wildcards; const char* auditlist; } logFileNamesStruct; diff --git a/request.c b/request.c index 0314ff71..9259f0f0 100644 --- a/request.c +++ b/request.c @@ -32,6 +32,7 @@ void getQueryTypesOverTime(int *sock, char type); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); void getList(int *sock, char type, char list_type); +void getPiholeStatus(int *sock, char type); void process_socket_request(char *client_message, int *sock) { @@ -204,6 +205,10 @@ void process_api_request(char *client_message, int *sock, bool header) { getList(sock, type, BLACKLIST); } + else if(command(client_message, "GET /dns/status")) + { + getPiholeStatus(sock, type); + } else if(header) { ssend(*sock, @@ -1329,3 +1334,10 @@ void getList(int *sock, char type, char list_type) ssend(*sock, "\"%s\":[]", list_type == WHITELIST ? "whitelist" : "blacklist"); } } + +void getPiholeStatus(int *sock, char type) +{ + int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); + sendAPIResponse(*sock, type); + ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); +} diff --git a/structs.c b/structs.c index f7a46f7e..c51fe004 100644 --- a/structs.c +++ b/structs.c @@ -25,6 +25,7 @@ logFileNamesStruct files = { "/etc/pihole/whitelist.txt", "/etc/pihole/blacklist.txt", "/etc/pihole/setupVars.conf", + "/etc/dnsmasq.d/01-pihole.conf", "/etc/dnsmasq.d/03-pihole-wildcard.conf", "/etc/pihole/auditlog.list" }; From a06ecba711bcdea3db28f576b67fb46a1a105e2d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 15 Jun 2017 20:06:46 -0400 Subject: [PATCH 048/168] Make sure to send API headers even if there's no data --- request.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/request.c b/request.c index 9259f0f0..95524c9e 100644 --- a/request.c +++ b/request.c @@ -1155,14 +1155,15 @@ void getForwardDestinationsOverTime(int *sock, char type) break; } } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"forward_dest\":{"); + } + if(sendit > -1) { - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"forward_dest\":{"); - } - bool first = true; for(i = sendit; i < counters.overTime; i++) { @@ -1232,14 +1233,15 @@ void getQueryTypesOverTime(int *sock, char type) break; } } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"query_types\":{"); + } + if(sendit > -1) { - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"query_types\":{"); - } - bool first = true; for(i = sendit; i < counters.overTime; i++) { From 693e69befcaa097add4a5b909fe62508bc497c7b Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 15 Jun 2017 20:13:18 -0400 Subject: [PATCH 049/168] Send API headers every time & simplify getList --- request.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/request.c b/request.c index 95524c9e..2f873010 100644 --- a/request.c +++ b/request.c @@ -1301,11 +1301,12 @@ void getList(int *sock, char type, char list_type) char *line = NULL; size_t size = 0; + sendAPIResponse(*sock, type); + ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); + if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) { bool first = true; - sendAPIResponse(*sock, type); - ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); while(getline(&line, &size, fp) != -1) { // Skip empty lines @@ -1327,14 +1328,10 @@ void getList(int *sock, char type, char list_type) line = NULL; } - ssend(*sock, "]"); - fclose(fp); } - else - { - ssend(*sock, "\"%s\":[]", list_type == WHITELIST ? "whitelist" : "blacklist"); - } + + ssend(*sock, "]"); } void getPiholeStatus(int *sock, char type) From c29ce3b5facf0e1cb3f28e76c44e693fbb5af38b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Fri, 16 Jun 2017 19:32:43 +0200 Subject: [PATCH 050/168] Add first API test --- test/test_suite.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_suite.sh b/test/test_suite.sh index b8ddfc6b..d2e40449 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -179,3 +179,19 @@ load 'libs/bats-support/load' echo "curl exit code: ${status}" [[ "${status}" -eq 0 ]] } + + +@test "API: Correct answer to summary request (including header check)" { + run bash -c "curl -i 127.0.0.1:4747/stats/summary" + echo "output: ${lines[@]}" + echo "curl exit code: ${status}" + [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] + [[ ${lines[1]} == "Server: FTL" ]] + [[ ${lines[2]} == "Cache-Control: no-cache" ]] + [[ ${lines[3]} == "Server: FTL" ]] + [[ ${lines[4]} == "Access-Control-Allow-Origin: *" ]] + [[ ${lines[5]} == "Content-Type: application/json" ]] + [[ ${lines[6]} == "" ]] + [[ ${lines[7]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ "${status}" -eq 0 ]] +} From 3f330dd1266d2855c33b3f6ee8e75e0eb4844713 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 17 Jun 2017 08:28:07 +0200 Subject: [PATCH 051/168] Make silent --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index d2e40449..a6c24267 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -182,7 +182,7 @@ load 'libs/bats-support/load' @test "API: Correct answer to summary request (including header check)" { - run bash -c "curl -i 127.0.0.1:4747/stats/summary" + run bash -c "curl -si 127.0.0.1:4747/stats/summary" echo "output: ${lines[@]}" echo "curl exit code: ${status}" [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] From f8072a0c2475647af46875e846be0111c3a0b534 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 17 Jun 2017 08:36:47 +0200 Subject: [PATCH 052/168] Minor correction to API tests --- test/test_suite.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index a6c24267..9ddc4a13 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -180,7 +180,6 @@ load 'libs/bats-support/load' [[ "${status}" -eq 0 ]] } - @test "API: Correct answer to summary request (including header check)" { run bash -c "curl -si 127.0.0.1:4747/stats/summary" echo "output: ${lines[@]}" @@ -188,10 +187,25 @@ load 'libs/bats-support/load' [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] [[ ${lines[1]} == "Server: FTL" ]] [[ ${lines[2]} == "Cache-Control: no-cache" ]] - [[ ${lines[3]} == "Server: FTL" ]] - [[ ${lines[4]} == "Access-Control-Allow-Origin: *" ]] - [[ ${lines[5]} == "Content-Type: application/json" ]] - [[ ${lines[6]} == "" ]] - [[ ${lines[7]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] + [[ ${lines[4]} == "Content-Type: application/json" ]] + [[ ${lines[5]} == "" ]] + [[ ${lines[6]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ "${status}" -eq 0 ]] +} + +@test "API: Correct answer to top_domains request" { + run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" + echo "output: ${lines[@]}" + echo "curl exit code: ${status}" + [[ ${lines[0]} == "" ]] + [[ "${status}" -eq 0 ]] +} + +@test "API: Correct answer to top_ads request" { + run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" + echo "output: ${lines[@]}" + echo "curl exit code: ${status}" + [[ ${lines[0]} == "" ]] [[ "${status}" -eq 0 ]] } From ec47c81128025ab8f1d5b9f2431b006c9db75766 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 17 Jun 2017 17:45:26 +0200 Subject: [PATCH 053/168] Adjusted tests --- test/test_suite.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 9ddc4a13..41caf9df 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -189,8 +189,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "" ]] - [[ ${lines[6]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] [[ "${status}" -eq 0 ]] } @@ -198,7 +197,7 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "" ]] + [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":5,\"ads_blocked_today\":0}" ]] [[ "${status}" -eq 0 ]] } @@ -206,6 +205,6 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "" ]] + [[ ${lines[0]} == "{\"top_ads\":{},\"dns_queries_today\":5,\"ads_blocked_today\":0}" ]] [[ "${status}" -eq 0 ]] } From 9893ef6896da0b04a35a55ad2a6b9b26a6c3b30b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 18 Jun 2017 10:36:44 +0200 Subject: [PATCH 054/168] Adjust tests with new black.list interpretation --- test/test_suite.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 62a99f6e..f92b0316 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -194,7 +194,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":5,\"ads_blocked_today\":0,\"ads_percentage_today\":0.0000,\"unique_domains\":4,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2}" ]] [[ "${status}" -eq 0 ]] } @@ -202,7 +202,7 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":5,\"ads_blocked_today\":0}" ]] + [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7,\"ads_blocked_today\":2}" ]] [[ "${status}" -eq 0 ]] } @@ -210,6 +210,6 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_ads\":{},\"dns_queries_today\":5,\"ads_blocked_today\":0}" ]] + [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"dns_queries_today\":7,\"ads_blocked_today\":2}" ]] [[ "${status}" -eq 0 ]] } From 5df03958245f146998d0666ff170e0ada7012622 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 25 Jun 2017 20:00:08 -0400 Subject: [PATCH 055/168] Change key name of forwards over time JSON Previously, both `forward_dest` and `forward_destinations` were keys in the JSON output at the root level. Which contains the over time data, and which contains the forward destination hostnames + IP addresses? Because of this ambiguity, the over time section should be renamed to `over_time`. Signed-off-by: Mcat12 --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index bbf22874..0b01d91c 100644 --- a/request.c +++ b/request.c @@ -1164,7 +1164,7 @@ void getForwardDestinationsOverTime(int *sock, char type) if(type != SOCKET) { sendAPIResponse(*sock, type); - ssend(*sock,"\"forward_dest\":{"); + ssend(*sock,"\"over_time\":{"); } if(sendit > -1) From f566fd30dd8a5a6d7855926fc601ee9500e85cfb Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 26 Jun 2017 16:27:02 +0200 Subject: [PATCH 056/168] Fix tests --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index ddbca0cc..ed051878 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -195,7 +195,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3}" ]] [[ "${status}" -eq 0 ]] } From bbb3df2fe5a934cbd334cca31185c5395280ad59 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 26 Jun 2017 16:35:51 +0200 Subject: [PATCH 057/168] Add a missing space, our tests require 100% accuracy --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index ed051878..d862a24a 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -195,7 +195,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3 }" ]] [[ "${status}" -eq 0 ]] } From cfea5cfca329c8bb8d6f8c8dd8e31f136d405c41 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 26 Jun 2017 21:32:15 -0400 Subject: [PATCH 058/168] Only give relevant total in top domain JSON output If it's `top_ads`, give `ads_blocked_today`. If it's `top_domains`, give `dns_queries_tdoay`. Both are not needed at the same time. Signed-off-by: Mcat12 --- request.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/request.c b/request.c index 0b01d91c..e6b6b75c 100644 --- a/request.c +++ b/request.c @@ -545,7 +545,12 @@ void getTopDomains(char *client_message, int *sock, char type) } if(type != SOCKET) - ssend(*sock,"},\"dns_queries_today\":%i,\"ads_blocked_today\":%i", (counters.queries - counters.invalidqueries), counters.blocked); + { + if(blocked) + ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); + else + ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); + } if(excludedomains != NULL) clearSetupVarsArray(); From 5693b723a7dab0e9fb29213184becb9961fa5006 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 26 Jun 2017 22:17:17 -0400 Subject: [PATCH 059/168] Fix top domains and ads tests Signed-off-by: Mcat12 --- test/test_suite.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index d862a24a..57e53742 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -203,7 +203,7 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7,\"ads_blocked_today\":2}" ]] + [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] [[ "${status}" -eq 0 ]] } @@ -211,6 +211,6 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"dns_queries_today\":7,\"ads_blocked_today\":2}" ]] + [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"ads_blocked_today\":2}" ]] [[ "${status}" -eq 0 ]] } From e0f462e1bf28c2a7fd50684b86d0218849d72357 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 26 Jun 2017 22:22:16 -0400 Subject: [PATCH 060/168] The newline? Signed-off-by: Mcat12 --- request.c | 2 +- test/test_suite.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/request.c b/request.c index d48050fa..352f16a5 100644 --- a/request.c +++ b/request.c @@ -314,7 +314,7 @@ void getStats(int *sock, char type) else { sendAPIResponse(*sock, type); - ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i\n",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); + ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); } if(debugclients) diff --git a/test/test_suite.sh b/test/test_suite.sh index 57e53742..d46a124d 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -195,7 +195,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3 }" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3}" ]] [[ "${status}" -eq 0 ]] } From 08391f46fc931bf60e5ff83f9567e7ccbeab8c53 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 30 Jun 2017 14:40:26 -0400 Subject: [PATCH 061/168] Rename /stats/overTime/graphs to graph It's returning one graph, not multiple. Signed-off-by: Mcat12 --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index 352f16a5..db2f88cd 100644 --- a/request.c +++ b/request.c @@ -161,7 +161,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getStats(sock, type); } - else if(command(client_message, "GET /stats/overTime/graphs")) + else if(command(client_message, "GET /stats/overTime/graph")) { getOverTime(sock, type); } From 52d336496f07d56d980cdff6654fdac15ce3861e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 30 Jun 2017 17:28:58 -0400 Subject: [PATCH 062/168] Rename /history to /stats/history It provides stats more than DNS functionality. However, it is in more of a gray area than, say, queries over time. Signed-off-by: Mcat12 --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index db2f88cd..e15097a2 100644 --- a/request.c +++ b/request.c @@ -181,7 +181,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getQueryTypes(sock, type); } - else if(command(client_message, "GET /history")) + else if(command(client_message, "GET /stats/history")) { getAllQueries(client_message, sock, type); } From 7c88707fef849a2c4f03a7f9721899e2413d2b12 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 30 Jun 2017 17:30:28 -0400 Subject: [PATCH 063/168] Rename /dns/recent_blocked to /stats/recent_blocked As this is also shown on chronometer, it seems to be related to stats more than anything else. Signed-off-by: Mcat12 --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index e15097a2..c4d88c24 100644 --- a/request.c +++ b/request.c @@ -185,7 +185,7 @@ void process_api_request(char *client_message, int *sock, bool header) { getAllQueries(client_message, sock, type); } - else if(command(client_message, "GET /dns/recent_blocked")) + else if(command(client_message, "GET /stats/recent_blocked")) { getRecentBlocked(client_message, sock, type); } From ccbf0ee5522f50932bbde9efa02a58c2911a5c39 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 4 Jul 2017 20:16:14 +0200 Subject: [PATCH 064/168] Add "GET /stats/dashboard" --- request.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/request.c b/request.c index c4d88c24..afce6387 100644 --- a/request.c +++ b/request.c @@ -177,6 +177,14 @@ void process_api_request(char *client_message, int *sock, bool header) { getForwardDestinations(sock, type); } + else if(command(client_message, "GET /stats/dashboard")) + { + getStats(sock, type); + getOverTime(sock, type); + getTopDomains(client_message, sock, type); + getTopClients(client_message, sock, type); + getForwardDestinations(sock, type); + } else if(command(client_message, "GET /stats/query_types")) { getQueryTypes(sock, type); From 32c2a414c04d9ab98760ad817baaf86c3a9c08fd Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 4 Jul 2017 20:19:28 +0200 Subject: [PATCH 065/168] Don't send header multiple times --- request.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/request.c b/request.c index afce6387..f8a06796 100644 --- a/request.c +++ b/request.c @@ -180,9 +180,14 @@ void process_api_request(char *client_message, int *sock, bool header) else if(command(client_message, "GET /stats/dashboard")) { getStats(sock, type); + type = API; + ssend(*sock, ","); getOverTime(sock, type); + ssend(*sock, ","); getTopDomains(client_message, sock, type); + ssend(*sock, ","); getTopClients(client_message, sock, type); + ssend(*sock, ","); getForwardDestinations(sock, type); } else if(command(client_message, "GET /stats/query_types")) From 0a6f5a5ad1693b91df565250f4fa057d2202744c Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 28 Jul 2017 23:06:45 -0400 Subject: [PATCH 066/168] Sanitize domains when outputting `/stats/history` This will have to be extended to the other domain-emitting API calls in future commits. Signed-off-by: Mcat12 --- request.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 6 deletions(-) diff --git a/request.c b/request.c index 29f41595..79795897 100644 --- a/request.c +++ b/request.c @@ -308,6 +308,54 @@ int cmpdesc(const void *a, const void *b) return 0; } +// Current supports sanitizing " and \ from the input +void formatJSON(char *input, char *buffer, size_t buffer_size) +{ + size_t output_size = strlen(input); + int output_index = 0; + + if(output_size > buffer_size) { + logg("Unable to sanitize domain for JSON, input size > buffer size"); + exit(EXIT_FAILURE); + } + + // Do a straight copy if there's nothing to sanitize + if(strstr(input, "\"") == NULL && strstr(input, "\\") == NULL) { + strcpy(buffer, input); + return; + } + + for(size_t i = 0; i < strlen(input); i++) { + switch(input[i]) { + case '"': + case '\\': + // We're adding an extra character + output_size += 1; + + if(output_size > buffer_size) { + logg("Unable to sanitize domain for JSON, output size > buffer size"); + exit(EXIT_FAILURE); + } + + buffer[output_index] = '\\'; + buffer[output_index+1] = input[i]; + output_index += 2; + break; + default: + buffer[output_index] = input[i]; + output_index++; + break; + } + } + + if(output_size+1 > buffer_size) { + logg("Unable to sanitize domain for JSON, output size > buffer size (when adding null terminator)"); + exit(EXIT_FAILURE); + } + + buffer[output_index] = 0; +} + void getStats(int *sock, char type) { int blocked = counters.blocked + counters.wildcardblocked; @@ -1040,28 +1088,70 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend( + *sock, "%i %s %s %s %i\n", + queries[i].timestamp, + qtype, + domains[queries[i].domainID].domain, + clients[queries[i].clientID].name, + queries[i].status + ); else - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend( + *sock, "%i %s %s %s %i\n", + queries[i].timestamp, + qtype, + domains[queries[i].domainID].domain, + clients[queries[i].clientID].ip, + queries[i].status + ); } else - ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + ssend( + *sock, "%i %s %s hidden %i\n", + queries[i].timestamp, + qtype, + domains[queries[i].domainID].domain, + queries[i].status + ); } else { // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], if(!first) ssend(*sock, ","); first = false; + char domain[4096]; + formatJSON(domains[queries[i].domainID].domain, domain, 4096); if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + ssend( + *sock, "[%i,\"%s\",\"%s\",\"%s\",%i]", + queries[i].timestamp, + qtype, + domain, + clients[queries[i].clientID].name, + queries[i].status + ); else - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + ssend( + *sock, "[%i,\"%s\",\"%s\",\"%s\",%i]", + queries[i].timestamp, + qtype, + domain, + clients[queries[i].clientID].ip, + queries[i].status + ); } else - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + ssend( + *sock, "[%i,\"%s\",\"%s\",\"hidden\",%i]", + queries[i].timestamp, + qtype, + domain, + queries[i].status + ); } } From 345ac16558367818bdd5ff2299c0f4737c0218b9 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 29 Jul 2017 12:36:38 -0400 Subject: [PATCH 067/168] Revert "Sanitize domains when outputting `/stats/history`" This reverts commit 0a6f5a5 The sanitizing will instead be implemented off of dev in the log parsing section. Signed-off-by: Mcat12 --- request.c | 102 ++++-------------------------------------------------- 1 file changed, 6 insertions(+), 96 deletions(-) diff --git a/request.c b/request.c index 79795897..29f41595 100644 --- a/request.c +++ b/request.c @@ -308,54 +308,6 @@ int cmpdesc(const void *a, const void *b) return 0; } -// Current supports sanitizing " and \ from the input -void formatJSON(char *input, char *buffer, size_t buffer_size) -{ - size_t output_size = strlen(input); - int output_index = 0; - - if(output_size > buffer_size) { - logg("Unable to sanitize domain for JSON, input size > buffer size"); - exit(EXIT_FAILURE); - } - - // Do a straight copy if there's nothing to sanitize - if(strstr(input, "\"") == NULL && strstr(input, "\\") == NULL) { - strcpy(buffer, input); - return; - } - - for(size_t i = 0; i < strlen(input); i++) { - switch(input[i]) { - case '"': - case '\\': - // We're adding an extra character - output_size += 1; - - if(output_size > buffer_size) { - logg("Unable to sanitize domain for JSON, output size > buffer size"); - exit(EXIT_FAILURE); - } - - buffer[output_index] = '\\'; - buffer[output_index+1] = input[i]; - output_index += 2; - break; - default: - buffer[output_index] = input[i]; - output_index++; - break; - } - } - - if(output_size+1 > buffer_size) { - logg("Unable to sanitize domain for JSON, output size > buffer size (when adding null terminator)"); - exit(EXIT_FAILURE); - } - - buffer[output_index] = 0; -} - void getStats(int *sock, char type) { int blocked = counters.blocked + counters.wildcardblocked; @@ -1088,70 +1040,28 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend( - *sock, "%i %s %s %s %i\n", - queries[i].timestamp, - qtype, - domains[queries[i].domainID].domain, - clients[queries[i].clientID].name, - queries[i].status - ); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - ssend( - *sock, "%i %s %s %s %i\n", - queries[i].timestamp, - qtype, - domains[queries[i].domainID].domain, - clients[queries[i].clientID].ip, - queries[i].status - ); + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else - ssend( - *sock, "%i %s %s hidden %i\n", - queries[i].timestamp, - qtype, - domains[queries[i].domainID].domain, - queries[i].status - ); + ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } else { // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], if(!first) ssend(*sock, ","); first = false; - char domain[4096]; - formatJSON(domains[queries[i].domainID].domain, domain, 4096); if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend( - *sock, "[%i,\"%s\",\"%s\",\"%s\",%i]", - queries[i].timestamp, - qtype, - domain, - clients[queries[i].clientID].name, - queries[i].status - ); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); else - ssend( - *sock, "[%i,\"%s\",\"%s\",\"%s\",%i]", - queries[i].timestamp, - qtype, - domain, - clients[queries[i].clientID].ip, - queries[i].status - ); + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); } else - ssend( - *sock, "[%i,\"%s\",\"%s\",\"hidden\",%i]", - queries[i].timestamp, - qtype, - domain, - queries[i].status - ); + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); } } From 4762b8a55c5e2435ef7e6108d252c6718f2b2e57 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 6 Aug 2017 19:06:12 -0400 Subject: [PATCH 068/168] Reorganize to split up API functions Signed-off-by: Mcat12 --- Makefile | 4 +- api.c | 22 + api.h | 33 ++ api_dns.c | 58 +++ api_stats.c | 1062 ++++++++++++++++++++++++++++++++++++++++++++++ request.c | 1161 +-------------------------------------------------- 6 files changed, 1178 insertions(+), 1162 deletions(-) create mode 100644 api.c create mode 100644 api.h create mode 100644 api_dns.c create mode 100644 api_stats.c diff --git a/Makefile b/Makefile index e619e81f..2d0b095d 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -DEPS = FTL.h routines.h version.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o +DEPS = FTL.h routines.h api.h version.h +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') diff --git a/api.c b/api.c new file mode 100644 index 00000000..ca7ec8b6 --- /dev/null +++ b/api.c @@ -0,0 +1,22 @@ +/* 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" + +void sendAPIResponse(int sock, char type) { + if(type == APIH) + { + // Send header only for full HTTP requests + ssend(sock, + "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" + "Content-Type: application/json\n\n{"); + } +} diff --git a/api.h b/api.h new file mode 100644 index 00000000..2cd69861 --- /dev/null +++ b/api.h @@ -0,0 +1,33 @@ +/* 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 +* API commands +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +// Endpoints under /stats/ +void getStats(int *sock, char type); +void getOverTime(int *sock, char type); +void getTopDomains (char *client_message, int *sock, char type); +void getTopClients(char *client_message, int *sock, char type); +void getForwardDestinations(char *client_message, int *sock, char type); +void getForwardNames(int *sock, char type); +void getQueryTypes(int *sock, char type); +void getAllQueries(char *client_message, int *sock, char type); +void getRecentBlocked(char *client_message, int *sock, char type); +void getMemoryUsage(int *sock, char type); +void getForwardDestinationsOverTime(int *sock, char type); +void getClientID(int *sock, char type); +void getQueryTypesOverTime(int *sock, char type); +void getVersion(int *sock, char type); +void getDBstats(int *sock, char type); + +// Endpoints under /dns/ +void getList(int *sock, char type, char list_type); +void getPiholeStatus(int *sock, char type); + +// General API commands +void sendAPIResponse(int sock, char type); diff --git a/api_dns.c b/api_dns.c new file mode 100644 index 00000000..2449a0ac --- /dev/null +++ b/api_dns.c @@ -0,0 +1,58 @@ +/* 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 +* API /dns/ +* +* 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" + +void getList(int *sock, char type, char list_type) +{ + FILE *fp; + char *line = NULL; + size_t size = 0; + + sendAPIResponse(*sock, type); + ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); + + if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) + { + bool first = true; + + while(getline(&line, &size, fp) != -1) { + // Skip empty lines + if(line[0] == '\n') + continue; + + if(!first) ssend(*sock, ","); + first = false; + + // Trim off the newline, if it exists + line[strcspn(line, "\r\n")] = 0; + + ssend(*sock, "\"%s\"", line); + } + // Free allocated memory + if(line != NULL) + { + free(line); + line = NULL; + } + + fclose(fp); + } + + ssend(*sock, "]"); +} + +void getPiholeStatus(int *sock, char type) +{ + int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); + sendAPIResponse(*sock, type); + ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); +} diff --git a/api_stats.c b/api_stats.c new file mode 100644 index 00000000..c314b52e --- /dev/null +++ b/api_stats.c @@ -0,0 +1,1062 @@ +/* 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 +* API /stats/ +* +* 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 "version.h" + +#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + +/* qsort comparision function (count field), sort ASC */ +int cmpasc(const void *a, const void *b) +{ + int *elem1 = (int*)a; + int *elem2 = (int*)b; + + if (elem1[1] < elem2[1]) + return -1; + else if (elem1[1] > elem2[1]) + return 1; + else + return 0; +} + +// qsort subroutine, sort DESC +int cmpdesc(const void *a, const void *b) +{ + int *elem1 = (int*)a; + int *elem2 = (int*)b; + + if (elem1[1] > elem2[1]) + return -1; + else if (elem1[1] < elem2[1]) + return 1; + else + return 0; +} + +void getStats(int *sock, char type) +{ + int blocked = counters.blocked + counters.wildcardblocked; + int total = counters.queries - counters.invalidqueries; + float percentage = 0.0; + // Avoid 1/0 condition + if(total > 0) + percentage = 1e2*blocked/total; + + if(type == SOCKET) { + ssend(*sock, "domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ + counters.gravity, total, blocked, percentage); + ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ + counters.domains, counters.forwardedqueries, counters.cached); + ssend(*sock, "unique_clients %i\n", counters.clients); + } + else + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); + } + + if(debugclients) + logg("Sent stats data to client, ID: %i", *sock); +} + +void getOverTime(int *sock, char type) +{ + int i, j = 9999999; + + // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) + for(i=0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(overTime[i].total > 0 || overTime[i].blocked > 0) + { + j = i; + break; + } + } + + // Send data in socket format if requested + if(type == SOCKET) + { + for(i = j; i < counters.overTime; i++) + { + ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); + } + } + else + { + // First send header with unspecified content-length outside of the for-loop + sendAPIResponse(*sock, type); + ssend(*sock,"\"domains_over_time\":{"); + + // Send "domains_over_time" data + for(i = j; i < counters.overTime; i++) + { + if(i != j) ssend(*sock, ","); + ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); + } + ssend(*sock,"},\"ads_over_time\":{"); + + // Send "ads_over_time" data + for(i = j; i < counters.overTime; i++) + { + if(i != j) ssend(*sock, ","); + ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); + } + ssend(*sock,"}"); + } + + if(debugclients) + logg("Sent overTime data to client, ID: %i", *sock); +} + +void getTopDomains(char *client_message, int *sock, char type) +{ + int i, temparray[counters.domains][2], count=10, num; + bool blocked, audit = false, desc = false; + + if(type == SOCKET) + blocked = command(client_message, ">top-ads"); + else + blocked = command(client_message, "/top_ads"); + + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Match both top-domains and top-ads + // SOCKET: >top-domains (15) + // API: /stats/top_domains?limit=15 + if(type == SOCKET) + { + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + } + + // Apply Audit Log filtering? + // SOCKET: >top-domains for audit + // API: /stats/top_domains?audit + if(type == SOCKET && command(client_message, " for audit")) + audit = true; + else if(type != SOCKET && command(client_message, "audit")) + audit = true; + + // Sort in descending order? + // SOCKET: >top-domains desc + // API: /stats/top_domains?order=desc + if(type == SOCKET && command(client_message, " desc")) + desc = true; + else if(type != SOCKET && command(client_message, "order=desc")) + desc = true; + + for(i=0; i < counters.domains; i++) + { + validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); + temparray[i][0] = i; + if(blocked) + temparray[i][1] = domains[i].blockedcount; + else + // Count only permitted queries + temparray[i][1] = (domains[i].count - domains[i].blockedcount); + } + + // Sort temporary array + if(desc) + qsort(temparray, counters.domains, sizeof(int[2]), cmpdesc); + else + qsort(temparray, counters.domains, sizeof(int[2]), cmpasc); + + + // Get filter + char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); + bool showpermitted = true, showblocked = true; + if(filter != NULL) + { + if((strcmp(filter, "permittedonly")) == 0) + showblocked = false; + else if((strcmp(filter, "blockedonly")) == 0) + showpermitted = false; + else if((strcmp(filter, "nothing")) == 0) + { + showpermitted = false; + showblocked = false; + } + } + clearSetupVarsArray(); + + // Get domains which the user doesn't want to see + char * excludedomains = NULL; + if(!audit) + { + excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); + if(excludedomains != NULL) + { + getSetupVarsArray(excludedomains); + + if(debugclients) + logg("Excluding %i domains from being displayed", setupVarsElements); + } + } + + if(type != SOCKET) + { + // First send header with unspecified content-length outside of the for-loop + sendAPIResponse(*sock, type); + + if(blocked) + ssend(*sock, "\"top_ads\":{"); + else + ssend(*sock, "\"top_domains\":{"); + } + + int skip = 0; bool first = true; + for(i=0; i < min(counters.domains, count+skip); i++) + { + // Get sorted indices + int j = temparray[counters.domains-i-1][0]; + validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); + + // Skip this domain if there is a filter on it + if(excludedomains != NULL) + { + if(insetupVarsArray(domains[j].domain)) + { + skip++; + continue; + } + } + + // Skip this domain if already included in audit + if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) + { + skip++; + continue; + } + + if(blocked && showblocked && domains[j].blockedcount > 0) + { + if(type == SOCKET) + { + if(audit && domains[j].wildcard) + ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); + else + ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); + } + else + { + if(!first) ssend(*sock,","); + first = false; + ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); + } + } + else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) + { + if(type == SOCKET) + { + ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); + } + else + { + if(!first) ssend(*sock,","); + first = false; + ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); + } + } + } + + if(type != SOCKET) + { + if(blocked) + ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); + else + ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); + } + + if(excludedomains != NULL) + clearSetupVarsArray(); + + if(debugclients) + { + if(blocked) + logg("Sent top ads list data to client, ID: %i", *sock); + else + logg("Sent top domains list data to client, ID: %i", *sock); + } +} + +void getTopClients(char *client_message, int *sock, char type) +{ + int i, temparray[counters.clients][2], count=10, num; + + // Match both top-domains and top-ads + // SOCKET: >top-clients (15) + // API: /stats/top_clients?limit=15 + if(type == SOCKET) + { + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + count = num; + } + } + } + + for(i=0; i < counters.clients; i++) + { + validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); + temparray[i][0] = i; + temparray[i][1] = clients[i].count; + } + + // Sort temporary array + qsort(temparray, counters.clients, sizeof(int[2]), cmpasc); + + // Get domains which the user doesn't want to see + char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); + if(excludeclients != NULL) + { + getSetupVarsArray(excludeclients); + + if(debugclients) + logg("Excluding %i clients from being displayed", setupVarsElements); + } + + if(type != SOCKET) + { + // First send header with unspecified content-length outside of the for-loop + sendAPIResponse(*sock, type); + ssend(*sock, "\"top_clients\":{"); + } + + int skip = 0; bool first = true; + for(i=0; i < min(counters.clients, count+skip); i++) + { + // Get sorted indices + int j = temparray[counters.clients-i-1][0]; + validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); + + // Skip this client if there is a filter on it + if(excludeclients != NULL) + { + if(insetupVarsArray(clients[j].ip) || + insetupVarsArray(clients[j].name)) + { + skip++; + continue; + } + } + + if(clients[j].count > 0) + { + if(type == SOCKET) + { + ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); + } + else + { + if(!first) ssend(*sock,","); + first = false; + if(strlen(clients[j].name) > 0) + ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); + else + ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); + } + } + } + + if(type != SOCKET) + ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); + + if(excludeclients != NULL) + clearSetupVarsArray(); + + if(debugclients) + logg("Sent top clients data to client, ID: %i", *sock); +} + + +void getForwardDestinations(char *client_message, int *sock, char type) +{ + bool allocated = false, first = true, sort = true; + int i, temparray[counters.forwarded+1][2]; + + if(type == SOCKET && command(client_message, "unsorted")) + sort = false; + else if(strstr(client_message, "unsorted")) + sort = false; + + if(sort) + { + for(i=0; i < counters.forwarded; i++) + { + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + temparray[i][0] = i; + temparray[i][1] = forwarded[i].count; + } + + // Add "local " forward destination + temparray[counters.forwarded][0] = counters.forwarded; + temparray[counters.forwarded][1] = counters.cached + counters.blocked; + + // Sort temporary array in descending order + qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); + } + + // Send HTTP headers with unknown content length + sendAPIResponse(*sock, type); + + // Send initial JSON output + if(type != SOCKET) + ssend(*sock, "\"forward_destinations\":{"); + + // Loop over available forward destinations + for(i=0; i < min(counters.forwarded+1, 10); i++) + { + char *name, *ip; + int count; + + // Get sorted indices + int j; + if(sort) + j = temparray[i][0]; + else + j = i; + + // Is this the "local" forward destination? + if(j == counters.forwarded) + { + ip = calloc(4,1); + strcpy(ip, "::1"); + name = calloc(6,1); + strcpy(name, "local"); + count = counters.cached + counters.blocked; + allocated = true; + } + else + { + validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); + ip = forwarded[j].ip; + name = forwarded[j].name; + count = forwarded[j].count; + allocated = false; + } + + // Send data if count > 0 + if(count > 0) + { + if(type == SOCKET) + { + ssend(*sock,"%i %i %s %s\n",i,count,ip,name); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + + if(strlen(name) > 0) + ssend(*sock, "\"%s|%s\":%i", name, ip, count); + else + ssend(*sock, "\"%s\":%i", ip, count); + } + } + + // Free previously allocated memory only if we allocated it + if(allocated) + { + free(ip); + free(name); + } + } + + if(type != SOCKET) + ssend(*sock, "}"); + + if(debugclients) + logg("Sent forward destination data to client, ID: %i", *sock); +} + + +void getForwardNames(int *sock, char type) +{ + int i; + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"forward_destinations\":{"); + } + + for(i=0; i < counters.forwarded; i++) + { + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + if(type == SOCKET) + { + ssend(*sock, "%i %i %s %s\n", i, forwarded[i].count, forwarded[i].ip, forwarded[i].name); + } + else + { + if(strlen(forwarded[i].name) > 0) + ssend(*sock, "\"%s|%s\":%i,", forwarded[i].name, forwarded[i].ip, forwarded[i].count); + else + ssend(*sock, "\"%s\":%i,", forwarded[i].ip, forwarded[i].count); + } + //{"2001:1608:10:25::9249:d69b":2799,"2001:1608:10:25::1c04:b12f":6382,"resolver2.ipv6-sandbox.opendns.com|2620:0:ccd::2":2478,"resolver1.ipv6-sandbox.opendns.com|2620:0:ccc::2":2219,"local|::1":2755}} + + } + + // Add "local" forward destination + if(type == SOCKET) + ssend(*sock,"%i %i ::1 local\n",counters.forwarded,counters.cached); + else + ssend(*sock, "\"local|::1\":%i}", counters.cached); + + if(debugclients) + logg("Sent forward destination names to client, ID: %i", *sock); +} + + +void getQueryTypes(int *sock, char type) +{ + if(type == SOCKET) + ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); + else + { + sendAPIResponse(*sock, type); + ssend(*sock, "\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", + counters.IPv4, + counters.IPv6, + counters.PTR, + counters.SRV); + } + + if(debugclients) + logg("Sent query type data to client, ID: %i", *sock); +} + + +void getAllQueries(char *client_message, int *sock, char type) +{ + + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Do we want a more specific version of this command (domain/client/time interval filtered)? + int from = 0, until = 0; + + char *domainname = NULL; + bool filterdomainname = false; + + char *clientname = NULL; + bool filterclientname = false; + + if(type == SOCKET) + { + // Time filtering? + if(command(client_message, ">getallqueries-time")) + { + sscanf(client_message, ">getallqueries-time %i %i",&from, &until); + } + // Domain filtering? + if(command(client_message, ">getallqueries-domain")) + { + sscanf(client_message, ">getallqueries-domain %ms", &domainname); + filterdomainname = true; + } + // Client filtering? + if(command(client_message, ">getallqueries-client")) + { + sscanf(client_message, ">getallqueries-client %ms", &clientname); + filterclientname = true; + } + } + else + { + // Time filtering? + const char * temp = strstr(client_message, "from="); + if(temp != NULL) + { + int num; + if(sscanf(temp, "from=%i", &num) > 0) + { + // User wants a different number of requests + from = num; + } + } + temp = strstr(client_message, "until="); + if(temp != NULL) + { + int num; + if(sscanf(temp, "until=%i", &num) > 0) + { + // User wants a different number of requests + until = num; + } + } + + // Domain filtering? + temp = strstr(client_message, "domain="); + if(temp != NULL) + { + char *temp2 = strdup(temp); + temp2[strcspn(temp2, "&")] = 0; + sscanf(temp2, "domain=%ms", &domainname); + free(temp2); + filterdomainname = true; + } + temp = strstr(client_message, "client="); + + // Client filtering? + if(temp != NULL) + { + char *temp2 = strdup(temp); + temp2[strcspn(temp2, "&")] = 0; + sscanf(temp2, "client=%ms", &clientname); + free(temp2); + filterclientname = true; + } + } + + int ibeg = 0, num; + // Test for integer that specifies number of entries to be shown + if(type == SOCKET) + { + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; + } + } + } + + // Get potentially existing filtering flags + char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); + bool showpermitted = true, showblocked = true; + if(filter != NULL) + { + if((strcmp(filter, "permittedonly")) == 0) + showblocked = false; + else if((strcmp(filter, "blockedonly")) == 0) + showpermitted = false; + else if((strcmp(filter, "nothing")) == 0) + { + showpermitted = false; + showblocked = false; + } + } + clearSetupVarsArray(); + + // Get privacy mode flag + char * privacy = read_setupVarsconf("API_PRIVACY_MODE"); + bool privacymode = false; + + if(privacy != NULL) + if(getSetupVarsBool(privacy)) + privacymode = true; + + clearSetupVarsArray(); + + if(debugclients) + { + if(showpermitted) + logg("Showing permitted queries"); + else + logg("Hiding permitted queries"); + + if(showblocked) + logg("Showing blocked queries"); + else + logg("Hiding blocked queries"); + + if(privacymode) + logg("Privacy mode enabled"); + } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock, "\"history\":["); + } + + int i; bool first = true; + for(i=ibeg; i < counters.queries; i++) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(!queries[i].valid) continue; + + validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); + validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); + + char qtype[5]; + if(queries[i].type == 1) + strcpy(qtype,"IPv4"); + else + strcpy(qtype,"IPv6"); + + if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) + continue; + if((queries[i].status == 2 || queries[i].status == 3) && !showpermitted) + continue; + + // Skip those entries which so not meet the requested timeframe + if((from > queries[i].timestamp && from != 0) || (queries[i].timestamp > until && until != 0)) + continue; + + if(filterdomainname) + { + // Skip if domain name is not identical with what the user wants to see + if(strcmp(domains[queries[i].domainID].domain, domainname) != 0) + continue; + } + + if(filterclientname) + { + // Skip if client name and IP are not identical with what the user wants to see + if((strcmp(clients[queries[i].clientID].ip, clientname) != 0) && + (strcmp(clients[queries[i].clientID].name, clientname) != 0)) + continue; + } + + if(type == SOCKET) + { + if(!privacymode) + { + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + else + ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + } + else + ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + } + else + { + // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], + if(!first) ssend(*sock, ","); + first = false; + + if(!privacymode) + { + if(strlen(clients[queries[i].clientID].name) > 0) + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); + else + ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); + } + else + ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); + } + } + + if(type != SOCKET) + ssend(*sock, "]"); + + // Free allocated memory + if(filterclientname) + free(clientname); + + if(filterdomainname) + free(domainname); + + if(debugclients) + logg("Sent all queries data to client, ID: %i", *sock); +} + +void getRecentBlocked(char *client_message, int *sock, char type) +{ + int i, num=1; + + // Exit before processing any data if requested via config setting + if(!config.query_display) + return; + + // Test for integer that specifies number of entries to be shown + if(type == SOCKET) + { + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) + { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; + } + } + else + { + const char * limit = strstr(client_message, "limit="); + if(limit != NULL) + { + if(sscanf(limit, "limit=%i", &num) > 0) + { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; + } + } + } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock, "\"recent_blocked\":["); + } + + // Find most recent query with either status 1 (blocked) + // or status 4 (wildcard blocked) + int found = 0; bool first = true; + for(i = counters.queries - 1; i > 0 ; i--) + { + validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); + // Check if this query has been removed due to garbage collection + if(!queries[i].valid) continue; + + if(queries[i].status == 1 || queries[i].status == 4) + { + found++; + if(type == SOCKET) + { + ssend(*sock,"%s\n", domains[queries[i].domainID].domain); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); + } + } + + if(found >= num) + break; + } + + if(type != SOCKET) + ssend(*sock, "]"); +} + +// only available via SOCKET +void getMemoryUsage(int *sock, char type) +{ + unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); + char *structprefix = calloc(2, sizeof(char)); + double formated = 0.0; + format_memory_size(structprefix, structbytes, &formated); + ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); + free(structprefix); + + unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; + char *dynamicprefix = calloc(2, sizeof(char)); + format_memory_size(dynamicprefix, dynamicbytes, &formated); + ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); + free(dynamicprefix); + + unsigned long int totalbytes = structbytes + dynamicbytes; + char *totalprefix = calloc(2, sizeof(char)); + format_memory_size(totalprefix, totalbytes, &formated); + ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); + free(totalprefix); + + if(debugclients) + logg("Sent memory data to client, ID: %i", *sock); +} + +void getForwardDestinationsOverTime(int *sock, char type) +{ + int i, sendit = -1; + for(i = 0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if((overTime[i].total > 0 || overTime[i].blocked > 0)) + { + sendit = i; + break; + } + } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"over_time\":{"); + } + + if(sendit > -1) + { + bool first = true; + for(i = sendit; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(type == SOCKET) + { + ssend(*sock, "%i", overTime[i].timestamp); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + ssend(*sock, "\"%i\":[", overTime[i].timestamp); + } + + int j; + + for(j = 0; j < counters.forwarded; j++) + { + int k; + if(j < overTime[i].forwardnum) + k = overTime[i].forwarddata[j]; + else + k = 0; + if(type == SOCKET) + ssend(*sock, " %i", k); + else + ssend(*sock, "%i,", k); + } + + if(type == SOCKET) + ssend(*sock, " %i\n", overTime[i].cached + overTime[i].blocked); + else + ssend(*sock, "%i]", overTime[i].cached + overTime[i].blocked); + } + } + + if(type != SOCKET) + { + ssend(*sock,"},"); + // Manually set API -> Don't send header a second time + getForwardNames(sock, API); + } + + if(debugclients) + logg("Sent overTime forwarded data to client, ID: %i", *sock); +} + +void getClientID(int *sock, char type) +{ + + ssend(*sock,"%i\n", *sock); + + if(debugclients) + logg("Sent client ID to client, ID: %i", *sock); +} + +void getQueryTypesOverTime(int *sock, char type) +{ + int i, sendit = -1; + for(i = 0; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if((overTime[i].total > 0 || overTime[i].blocked > 0)) + { + sendit = i; + break; + } + } + + if(type != SOCKET) + { + sendAPIResponse(*sock, type); + ssend(*sock,"\"query_types\":{"); + } + + if(sendit > -1) + { + bool first = true; + for(i = sendit; i < counters.overTime; i++) + { + validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); + if(type == SOCKET) + { + ssend(*sock, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); + } + else + { + if(!first) ssend(*sock, ","); + first = false; + ssend(*sock, "\"%i\":[%i,%i]", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); + } + } + } + + if(type != SOCKET) + ssend(*sock,"}"); + + if(debugclients) + logg("Sent overTime query types data to client, ID: %i", *sock); +} + +void getVersion(int *sock, char type) +{ + ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); + + if(debugclients) + logg("Sent version info to client, ID: %i", *sock); +} + +void getDBstats(int *sock, char type) +{ + // Get file details + struct stat st; + long int filesize = 0; + if(stat(FTLfiles.db, &st) != 0) + // stat() failed (maybe the file does not exist?) + filesize = -1; + else + filesize = st.st_size; + + char *prefix = calloc(2, sizeof(char)); + double formated = 0.0; + format_memory_size(prefix, filesize, &formated); + + ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); + + if(debugclients) + logg("Sent DB info to client, ID: %i", *sock); +} diff --git a/request.c b/request.c index 29f41595..17dcbd1e 100644 --- a/request.c +++ b/request.c @@ -9,30 +9,7 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "version.h" - -// Private -#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) -#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) - -// Local prototypes -void getStats(int *sock, char type); -void getOverTime(int *sock, char type); -void getTopDomains (char *client_message, int *sock, char type); -void getTopClients(char *client_message, int *sock, char type); -void getForwardDestinations(char *client_message, int *sock, char type); -void getForwardNames(int *sock, char type); -void getQueryTypes(int *sock, char type); -void getAllQueries(char *client_message, int *sock, char type); -void getRecentBlocked(char *client_message, int *sock, char type); -void getMemoryUsage(int *sock, char type); -void getForwardDestinationsOverTime(int *sock, char type); -void getClientID(int *sock, char type); -void getQueryTypesOverTime(int *sock, char type); -void getVersion(int *sock, char type); -void getDBstats(int *sock, char type); -void getList(int *sock, char type, char list_type); -void getPiholeStatus(int *sock, char type); +#include "api.h" void process_socket_request(char *client_message, int *sock) { @@ -236,1139 +213,3 @@ bool command(char *client_message, const char* cmd) { return strstr(client_message,cmd) != NULL; } - -void sendAPIResponse(int sock, char type) { - if(type == APIH) - { - // Send header only for full HTTP requests - ssend(sock, - "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\n\n{"); - } -} - -// void formatNumber(bool raw, int n, char* buffer) -// { -// if(raw) -// { -// // Don't change number, echo string -// sprintf(buffer, "%d", n); -// } -// else -// { -// // Insert thousand separator -// if(n < 0) { -// sprintf(buffer, "-"); -// n = -n; -// } -// else -// { -// // Empty buffer -// buffer[0] = '\0'; -// } - -// int a[20] = { 0 }; -// int *pa = a; -// while(n > 0) { -// *++pa = n % 1000; -// n /= 1000; -// } -// sprintf(buffer, "%s%d", buffer, *pa); -// while(pa > a + 1) { -// sprintf(buffer, "%s,%03d", buffer, *--pa); -// } -// } -// } - -/* qsort comparision function (count field), sort ASC */ -int cmpasc(const void *a, const void *b) -{ - int *elem1 = (int*)a; - int *elem2 = (int*)b; - - if (elem1[1] < elem2[1]) - return -1; - else if (elem1[1] > elem2[1]) - return 1; - else - return 0; -} - -// qsort subroutine, sort DESC -int cmpdesc(const void *a, const void *b) -{ - int *elem1 = (int*)a; - int *elem2 = (int*)b; - - if (elem1[1] > elem2[1]) - return -1; - else if (elem1[1] < elem2[1]) - return 1; - else - return 0; -} - -void getStats(int *sock, char type) -{ - int blocked = counters.blocked + counters.wildcardblocked; - int total = counters.queries - counters.invalidqueries; - float percentage = 0.0; - // Avoid 1/0 condition - if(total > 0) - percentage = 1e2*blocked/total; - - if(type == SOCKET) { - ssend(*sock, "domains_being_blocked %i\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", \ - counters.gravity, total, blocked, percentage); - ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", \ - counters.domains, counters.forwardedqueries, counters.cached); - ssend(*sock, "unique_clients %i\n", counters.clients); - } - else - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); - } - - if(debugclients) - logg("Sent stats data to client, ID: %i", *sock); -} - -void getOverTime(int *sock, char type) -{ - int i, j = 9999999; - - // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) - for(i=0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(overTime[i].total > 0 || overTime[i].blocked > 0) - { - j = i; - break; - } - } - - // Send data in socket format if requested - if(type == SOCKET) - { - for(i = j; i < counters.overTime; i++) - { - ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); - } - } - else - { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); - ssend(*sock,"\"domains_over_time\":{"); - - // Send "domains_over_time" data - for(i = j; i < counters.overTime; i++) - { - if(i != j) ssend(*sock, ","); - ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); - } - ssend(*sock,"},\"ads_over_time\":{"); - - // Send "ads_over_time" data - for(i = j; i < counters.overTime; i++) - { - if(i != j) ssend(*sock, ","); - ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); - } - ssend(*sock,"}"); - } - - if(debugclients) - logg("Sent overTime data to client, ID: %i", *sock); -} - -void getTopDomains(char *client_message, int *sock, char type) -{ - int i, temparray[counters.domains][2], count=10, num; - bool blocked, audit = false, desc = false; - - if(type == SOCKET) - blocked = command(client_message, ">top-ads"); - else - blocked = command(client_message, "/top_ads"); - - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - // Match both top-domains and top-ads - // SOCKET: >top-domains (15) - // API: /stats/top_domains?limit=15 - if(type == SOCKET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - } - - // Apply Audit Log filtering? - // SOCKET: >top-domains for audit - // API: /stats/top_domains?audit - if(type == SOCKET && command(client_message, " for audit")) - audit = true; - else if(type != SOCKET && command(client_message, "audit")) - audit = true; - - // Sort in descending order? - // SOCKET: >top-domains desc - // API: /stats/top_domains?order=desc - if(type == SOCKET && command(client_message, " desc")) - desc = true; - else if(type != SOCKET && command(client_message, "order=desc")) - desc = true; - - for(i=0; i < counters.domains; i++) - { - validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); - temparray[i][0] = i; - if(blocked) - temparray[i][1] = domains[i].blockedcount; - else - // Count only permitted queries - temparray[i][1] = (domains[i].count - domains[i].blockedcount); - } - - // Sort temporary array - if(desc) - qsort(temparray, counters.domains, sizeof(int[2]), cmpdesc); - else - qsort(temparray, counters.domains, sizeof(int[2]), cmpasc); - - - // Get filter - char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); - bool showpermitted = true, showblocked = true; - if(filter != NULL) - { - if((strcmp(filter, "permittedonly")) == 0) - showblocked = false; - else if((strcmp(filter, "blockedonly")) == 0) - showpermitted = false; - else if((strcmp(filter, "nothing")) == 0) - { - showpermitted = false; - showblocked = false; - } - } - clearSetupVarsArray(); - - // Get domains which the user doesn't want to see - char * excludedomains = NULL; - if(!audit) - { - excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); - if(excludedomains != NULL) - { - getSetupVarsArray(excludedomains); - - if(debugclients) - logg("Excluding %i domains from being displayed", setupVarsElements); - } - } - - if(type != SOCKET) - { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); - - if(blocked) - ssend(*sock, "\"top_ads\":{"); - else - ssend(*sock, "\"top_domains\":{"); - } - - int skip = 0; bool first = true; - for(i=0; i < min(counters.domains, count+skip); i++) - { - // Get sorted indices - int j = temparray[counters.domains-i-1][0]; - validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); - - // Skip this domain if there is a filter on it - if(excludedomains != NULL) - { - if(insetupVarsArray(domains[j].domain)) - { - skip++; - continue; - } - } - - // Skip this domain if already included in audit - if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) - { - skip++; - continue; - } - - if(blocked && showblocked && domains[j].blockedcount > 0) - { - if(type == SOCKET) - { - if(audit && domains[j].wildcard) - ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); - else - ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); - } - else - { - if(!first) ssend(*sock,","); - first = false; - ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); - } - } - else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) - { - if(type == SOCKET) - { - ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); - } - else - { - if(!first) ssend(*sock,","); - first = false; - ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); - } - } - } - - if(type != SOCKET) - { - if(blocked) - ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); - else - ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); - } - - if(excludedomains != NULL) - clearSetupVarsArray(); - - if(debugclients) - { - if(blocked) - logg("Sent top ads list data to client, ID: %i", *sock); - else - logg("Sent top domains list data to client, ID: %i", *sock); - } -} - -void getTopClients(char *client_message, int *sock, char type) -{ - int i, temparray[counters.clients][2], count=10, num; - - // Match both top-domains and top-ads - // SOCKET: >top-clients (15) - // API: /stats/top_clients?limit=15 - if(type == SOCKET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - } - - for(i=0; i < counters.clients; i++) - { - validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); - temparray[i][0] = i; - temparray[i][1] = clients[i].count; - } - - // Sort temporary array - qsort(temparray, counters.clients, sizeof(int[2]), cmpasc); - - // Get domains which the user doesn't want to see - char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - if(excludeclients != NULL) - { - getSetupVarsArray(excludeclients); - - if(debugclients) - logg("Excluding %i clients from being displayed", setupVarsElements); - } - - if(type != SOCKET) - { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); - ssend(*sock, "\"top_clients\":{"); - } - - int skip = 0; bool first = true; - for(i=0; i < min(counters.clients, count+skip); i++) - { - // Get sorted indices - int j = temparray[counters.clients-i-1][0]; - validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); - - // Skip this client if there is a filter on it - if(excludeclients != NULL) - { - if(insetupVarsArray(clients[j].ip) || - insetupVarsArray(clients[j].name)) - { - skip++; - continue; - } - } - - if(clients[j].count > 0) - { - if(type == SOCKET) - { - ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); - } - else - { - if(!first) ssend(*sock,","); - first = false; - if(strlen(clients[j].name) > 0) - ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); - else - ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); - } - } - } - - if(type != SOCKET) - ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); - - if(excludeclients != NULL) - clearSetupVarsArray(); - - if(debugclients) - logg("Sent top clients data to client, ID: %i", *sock); -} - - -void getForwardDestinations(char *client_message, int *sock, char type) -{ - bool allocated = false, first = true, sort = true; - int i, temparray[counters.forwarded+1][2]; - - if(type == SOCKET && command(client_message, "unsorted")) - sort = false; - else if(strstr(client_message, "unsorted")) - sort = false; - - if(sort) - { - for(i=0; i < counters.forwarded; i++) - { - validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); - temparray[i][0] = i; - temparray[i][1] = forwarded[i].count; - } - - // Add "local " forward destination - temparray[counters.forwarded][0] = counters.forwarded; - temparray[counters.forwarded][1] = counters.cached + counters.blocked; - - // Sort temporary array in descending order - qsort(temparray, counters.forwarded+1, sizeof(int[2]), cmpdesc); - } - - // Send HTTP headers with unknown content length - sendAPIResponse(*sock, type); - - // Send initial JSON output - if(type != SOCKET) - ssend(*sock, "\"forward_destinations\":{"); - - // Loop over available forward destinations - for(i=0; i < min(counters.forwarded+1, 10); i++) - { - char *name, *ip; - int count; - - // Get sorted indices - int j; - if(sort) - j = temparray[i][0]; - else - j = i; - - // Is this the "local" forward destination? - if(j == counters.forwarded) - { - ip = calloc(4,1); - strcpy(ip, "::1"); - name = calloc(6,1); - strcpy(name, "local"); - count = counters.cached + counters.blocked; - allocated = true; - } - else - { - validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); - ip = forwarded[j].ip; - name = forwarded[j].name; - count = forwarded[j].count; - allocated = false; - } - - // Send data if count > 0 - if(count > 0) - { - if(type == SOCKET) - { - ssend(*sock,"%i %i %s %s\n",i,count,ip,name); - } - else - { - if(!first) ssend(*sock, ","); - first = false; - - if(strlen(name) > 0) - ssend(*sock, "\"%s|%s\":%i", name, ip, count); - else - ssend(*sock, "\"%s\":%i", ip, count); - } - } - - // Free previously allocated memory only if we allocated it - if(allocated) - { - free(ip); - free(name); - } - } - - if(type != SOCKET) - ssend(*sock, "}"); - - if(debugclients) - logg("Sent forward destination data to client, ID: %i", *sock); -} - - -void getForwardNames(int *sock, char type) -{ - int i; - - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"forward_destinations\":{"); - } - - for(i=0; i < counters.forwarded; i++) - { - validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); - if(type == SOCKET) - { - ssend(*sock, "%i %i %s %s\n", i, forwarded[i].count, forwarded[i].ip, forwarded[i].name); - } - else - { - if(strlen(forwarded[i].name) > 0) - ssend(*sock, "\"%s|%s\":%i,", forwarded[i].name, forwarded[i].ip, forwarded[i].count); - else - ssend(*sock, "\"%s\":%i,", forwarded[i].ip, forwarded[i].count); - } - //{"2001:1608:10:25::9249:d69b":2799,"2001:1608:10:25::1c04:b12f":6382,"resolver2.ipv6-sandbox.opendns.com|2620:0:ccd::2":2478,"resolver1.ipv6-sandbox.opendns.com|2620:0:ccc::2":2219,"local|::1":2755}} - - } - - // Add "local" forward destination - if(type == SOCKET) - ssend(*sock,"%i %i ::1 local\n",counters.forwarded,counters.cached); - else - ssend(*sock, "\"local|::1\":%i}", counters.cached); - - if(debugclients) - logg("Sent forward destination names to client, ID: %i", *sock); -} - - -void getQueryTypes(int *sock, char type) -{ - if(type == SOCKET) - ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); - else - { - sendAPIResponse(*sock, type); - ssend(*sock, "\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", - counters.IPv4, - counters.IPv6, - counters.PTR, - counters.SRV); - } - - if(debugclients) - logg("Sent query type data to client, ID: %i", *sock); -} - - -void getAllQueries(char *client_message, int *sock, char type) -{ - - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - // Do we want a more specific version of this command (domain/client/time interval filtered)? - int from = 0, until = 0; - - char *domainname = NULL; - bool filterdomainname = false; - - char *clientname = NULL; - bool filterclientname = false; - - if(type == SOCKET) - { - // Time filtering? - if(command(client_message, ">getallqueries-time")) - { - sscanf(client_message, ">getallqueries-time %i %i",&from, &until); - } - // Domain filtering? - if(command(client_message, ">getallqueries-domain")) - { - sscanf(client_message, ">getallqueries-domain %ms", &domainname); - filterdomainname = true; - } - // Client filtering? - if(command(client_message, ">getallqueries-client")) - { - sscanf(client_message, ">getallqueries-client %ms", &clientname); - filterclientname = true; - } - } - else - { - // Time filtering? - const char * temp = strstr(client_message, "from="); - if(temp != NULL) - { - int num; - if(sscanf(temp, "from=%i", &num) > 0) - { - // User wants a different number of requests - from = num; - } - } - temp = strstr(client_message, "until="); - if(temp != NULL) - { - int num; - if(sscanf(temp, "until=%i", &num) > 0) - { - // User wants a different number of requests - until = num; - } - } - - // Domain filtering? - temp = strstr(client_message, "domain="); - if(temp != NULL) - { - char *temp2 = strdup(temp); - temp2[strcspn(temp2, "&")] = 0; - sscanf(temp2, "domain=%ms", &domainname); - free(temp2); - filterdomainname = true; - } - temp = strstr(client_message, "client="); - - // Client filtering? - if(temp != NULL) - { - char *temp2 = strdup(temp); - temp2[strcspn(temp2, "&")] = 0; - sscanf(temp2, "client=%ms", &clientname); - free(temp2); - filterclientname = true; - } - } - - int ibeg = 0, num; - // Test for integer that specifies number of entries to be shown - if(type == SOCKET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - } - } - } - - // Get potentially existing filtering flags - char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); - bool showpermitted = true, showblocked = true; - if(filter != NULL) - { - if((strcmp(filter, "permittedonly")) == 0) - showblocked = false; - else if((strcmp(filter, "blockedonly")) == 0) - showpermitted = false; - else if((strcmp(filter, "nothing")) == 0) - { - showpermitted = false; - showblocked = false; - } - } - clearSetupVarsArray(); - - // Get privacy mode flag - char * privacy = read_setupVarsconf("API_PRIVACY_MODE"); - bool privacymode = false; - - if(privacy != NULL) - if(getSetupVarsBool(privacy)) - privacymode = true; - - clearSetupVarsArray(); - - if(debugclients) - { - if(showpermitted) - logg("Showing permitted queries"); - else - logg("Hiding permitted queries"); - - if(showblocked) - logg("Showing blocked queries"); - else - logg("Hiding blocked queries"); - - if(privacymode) - logg("Privacy mode enabled"); - } - - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock, "\"history\":["); - } - - int i; bool first = true; - for(i=ibeg; i < counters.queries; i++) - { - validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this query has been removed due to garbage collection - if(!queries[i].valid) continue; - - validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); - validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); - - char qtype[5]; - if(queries[i].type == 1) - strcpy(qtype,"IPv4"); - else - strcpy(qtype,"IPv6"); - - if((queries[i].status == 1 || queries[i].status == 4) && !showblocked) - continue; - if((queries[i].status == 2 || queries[i].status == 3) && !showpermitted) - continue; - - // Skip those entries which so not meet the requested timeframe - if((from > queries[i].timestamp && from != 0) || (queries[i].timestamp > until && until != 0)) - continue; - - if(filterdomainname) - { - // Skip if domain name is not identical with what the user wants to see - if(strcmp(domains[queries[i].domainID].domain, domainname) != 0) - continue; - } - - if(filterclientname) - { - // Skip if client name and IP are not identical with what the user wants to see - if((strcmp(clients[queries[i].clientID].ip, clientname) != 0) && - (strcmp(clients[queries[i].clientID].name, clientname) != 0)) - continue; - } - - if(type == SOCKET) - { - if(!privacymode) - { - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); - else - ssend(*sock,"%i %s %s %s %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); - } - else - ssend(*sock,"%i %s %s hidden %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); - } - else - { - // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2","2"], - if(!first) ssend(*sock, ","); - first = false; - - if(!privacymode) - { - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status); - else - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status); - } - else - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status); - } - } - - if(type != SOCKET) - ssend(*sock, "]"); - - // Free allocated memory - if(filterclientname) - free(clientname); - - if(filterdomainname) - free(domainname); - - if(debugclients) - logg("Sent all queries data to client, ID: %i", *sock); -} - -void getRecentBlocked(char *client_message, int *sock, char type) -{ - int i, num=1; - - // Exit before processing any data if requested via config setting - if(!config.query_display) - return; - - // Test for integer that specifies number of entries to be shown - if(type == SOCKET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - } - } - } - - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock, "\"recent_blocked\":["); - } - - // Find most recent query with either status 1 (blocked) - // or status 4 (wildcard blocked) - int found = 0; bool first = true; - for(i = counters.queries - 1; i > 0 ; i--) - { - validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); - // Check if this query has been removed due to garbage collection - if(!queries[i].valid) continue; - - if(queries[i].status == 1 || queries[i].status == 4) - { - found++; - if(type == SOCKET) - { - ssend(*sock,"%s\n", domains[queries[i].domainID].domain); - } - else - { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); - } - } - - if(found >= num) - break; - } - - if(type != SOCKET) - ssend(*sock, "]"); -} - -// only available via SOCKET -void getMemoryUsage(int *sock, char type) -{ - unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); - char *structprefix = calloc(2, sizeof(char)); - double formated = 0.0; - format_memory_size(structprefix, structbytes, &formated); - ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); - free(structprefix); - - unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; - char *dynamicprefix = calloc(2, sizeof(char)); - format_memory_size(dynamicprefix, dynamicbytes, &formated); - ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); - free(dynamicprefix); - - unsigned long int totalbytes = structbytes + dynamicbytes; - char *totalprefix = calloc(2, sizeof(char)); - format_memory_size(totalprefix, totalbytes, &formated); - ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); - free(totalprefix); - - if(debugclients) - logg("Sent memory data to client, ID: %i", *sock); -} - -void getForwardDestinationsOverTime(int *sock, char type) -{ - int i, sendit = -1; - for(i = 0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0)) - { - sendit = i; - break; - } - } - - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"over_time\":{"); - } - - if(sendit > -1) - { - bool first = true; - for(i = sendit; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(type == SOCKET) - { - ssend(*sock, "%i", overTime[i].timestamp); - } - else - { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%i\":[", overTime[i].timestamp); - } - - int j; - - for(j = 0; j < counters.forwarded; j++) - { - int k; - if(j < overTime[i].forwardnum) - k = overTime[i].forwarddata[j]; - else - k = 0; - if(type == SOCKET) - ssend(*sock, " %i", k); - else - ssend(*sock, "%i,", k); - } - - if(type == SOCKET) - ssend(*sock, " %i\n", overTime[i].cached + overTime[i].blocked); - else - ssend(*sock, "%i]", overTime[i].cached + overTime[i].blocked); - } - } - - if(type != SOCKET) - { - ssend(*sock,"},"); - // Manually set API -> Don't send header a second time - getForwardNames(sock, API); - } - - if(debugclients) - logg("Sent overTime forwarded data to client, ID: %i", *sock); -} - -void getClientID(int *sock, char type) -{ - - ssend(*sock,"%i\n", *sock); - - if(debugclients) - logg("Sent client ID to client, ID: %i", *sock); -} - -void getQueryTypesOverTime(int *sock, char type) -{ - int i, sendit = -1; - for(i = 0; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if((overTime[i].total > 0 || overTime[i].blocked > 0)) - { - sendit = i; - break; - } - } - - if(type != SOCKET) - { - sendAPIResponse(*sock, type); - ssend(*sock,"\"query_types\":{"); - } - - if(sendit > -1) - { - bool first = true; - for(i = sendit; i < counters.overTime; i++) - { - validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(type == SOCKET) - { - ssend(*sock, "%i %i %i\n", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); - } - else - { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%i\":[%i,%i]", overTime[i].timestamp,overTime[i].querytypedata[0],overTime[i].querytypedata[1]); - } - } - } - - if(type != SOCKET) - ssend(*sock,"}"); - - if(debugclients) - logg("Sent overTime query types data to client, ID: %i", *sock); -} - -void getVersion(int *sock, char type) -{ - ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); - - if(debugclients) - logg("Sent version info to client, ID: %i", *sock); -} - -void getDBstats(int *sock, char type) -{ - // Get file details - struct stat st; - long int filesize = 0; - if(stat(FTLfiles.db, &st) != 0) - // stat() failed (maybe the file does not exist?) - filesize = -1; - else - filesize = st.st_size; - - char *prefix = calloc(2, sizeof(char)); - double formated = 0.0; - format_memory_size(prefix, filesize, &formated); - - ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); - - if(debugclients) - logg("Sent DB info to client, ID: %i", *sock); -} - -void getList(int *sock, char type, char list_type) -{ - FILE *fp; - char *line = NULL; - size_t size = 0; - - sendAPIResponse(*sock, type); - ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); - - if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) - { - bool first = true; - - while(getline(&line, &size, fp) != -1) { - // Skip empty lines - if(line[0] == '\n') - continue; - - if(!first) ssend(*sock, ","); - first = false; - - // Trim off the newline, if it exists - line[strcspn(line, "\r\n")] = 0; - - ssend(*sock, "\"%s\"", line); - } - // Free allocated memory - if(line != NULL) - { - free(line); - line = NULL; - } - - fclose(fp); - } - - ssend(*sock, "]"); -} - -void getPiholeStatus(int *sock, char type) -{ - int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); - sendAPIResponse(*sock, type); - ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); -} From 551351b178f79901e7816ebc35a6da2f99b0d195 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 7 Aug 2017 12:43:25 +0200 Subject: [PATCH 069/168] We don't show "pi.hole" anymore in the results. Adjust tests (also for the API component) accordingly. Signed-off-by: DL6ER --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 6ae52b3d..86325a9c 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -215,7 +215,7 @@ load 'libs/bats-support/load' run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" echo "output: ${lines[@]}" echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"pi.hole\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] + [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"example.com\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] [[ "${status}" -eq 0 ]] } From ebe0631e5cab2a0f1f91398aaa1bc0694cd3019c Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 8 Aug 2017 23:02:59 -0400 Subject: [PATCH 070/168] Add dummy POST handling for whitelist and blacklist Signed-off-by: Mcat12 --- api.h | 1 + api_dns.c | 6 ++++++ request.c | 34 +++++++++++++++++++++++++++++++++- routines.h | 3 +-- socket.c | 4 ++-- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/api.h b/api.h index 2cd69861..a5468780 100644 --- a/api.h +++ b/api.h @@ -27,6 +27,7 @@ void getDBstats(int *sock, char type); // Endpoints under /dns/ void getList(int *sock, char type, char list_type); +void addList(int *sock, char type, char list_type, char *data); void getPiholeStatus(int *sock, char type); // General API commands diff --git a/api_dns.c b/api_dns.c index 2449a0ac..f1135d58 100644 --- a/api_dns.c +++ b/api_dns.c @@ -56,3 +56,9 @@ void getPiholeStatus(int *sock, char type) sendAPIResponse(*sock, type); ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); } + +void addList(int *sock, char type, char list_type, char *data) +{ + sendAPIResponse(*sock, type); + ssend(*sock, "\"status\": \"success\""); +} diff --git a/request.c b/request.c index 17dcbd1e..5d3875e0 100644 --- a/request.c +++ b/request.c @@ -126,7 +126,26 @@ void process_socket_request(char *client_message, int *sock) } } -void process_api_request(char *client_message, int *sock, bool header) +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; +} + +void process_api_request(char *client_message, char *full_message, int *sock, bool header) { char type; if(header) @@ -134,6 +153,11 @@ void process_api_request(char *client_message, int *sock, bool header) else type = API; + if(debug) + logg("Received API request: %s", full_message); + + char *data = getPayload(full_message); + if(command(client_message, "GET /stats/summary")) { getStats(sock, type); @@ -191,10 +215,18 @@ void process_api_request(char *client_message, int *sock, bool header) { getList(sock, type, WHITELIST); } + else if(command(client_message, "POST /dns/whitelist")) + { + addList(sock, type, WHITELIST, data); + } else if(command(client_message, "GET /dns/blacklist")) { getList(sock, type, BLACKLIST); } + else if(command(client_message, "POST /dns/blacklist")) + { + addList(sock, type, BLACKLIST, data); + } else if(command(client_message, "GET /dns/status")) { getPiholeStatus(sock, type); diff --git a/routines.h b/routines.h index 38007f9e..d6baa5f5 100644 --- a/routines.h +++ b/routines.h @@ -43,9 +43,8 @@ void *socket_listening_thread(void *args); void *api_listening_thread(void *args); void process_socket_request(char *client_message, int *sock); -void process_api_request(char *client_message, int *sock, bool header); +void process_api_request(char *client_message, char *full_message, int *sock, bool header); bool command(char *client_message, const char* cmd); -void formatNumber(bool raw, int n, char* buffer); void read_gravity_files(void); int countlines(const char* fname); diff --git a/socket.c b/socket.c index 04e9a8bd..7b63cacb 100644 --- a/socket.c +++ b/socket.c @@ -312,7 +312,7 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); - if(strncmp(message, "GET ", 4) == 0) + if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0) { // HTTP requests can be simple or full. // A simple request contains one line only, and looks like this: @@ -352,7 +352,7 @@ void *api_connection_handler_thread(void *socket_desc) else { enable_thread_lock(threadname); - process_api_request(request, &sock, header); + process_api_request(request, message, &sock, header); disable_thread_lock(threadname); } From 9b9810117723d6150b13202869ac077575539d8e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 8 Aug 2017 23:08:35 -0400 Subject: [PATCH 071/168] Fix JSON "not_found" message Signed-off-by: Mcat12 --- request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.c b/request.c index 5d3875e0..e722c82e 100644 --- a/request.c +++ b/request.c @@ -235,7 +235,7 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo { ssend(*sock, "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\nContent-Length: 21\n\n{status: \"not_found\""); + "Content-Type: application/json\nContent-Length: 23\n\n{\"status\": \"not_found\""); } ssend(*sock, "}"); From 9ec7bd872264748bb473c7337ef02af8065fd032 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 9 Aug 2017 21:28:38 -0400 Subject: [PATCH 072/168] Implement simple JSON parsing for addList Uses cJSON to parse, but output will still be handled directly. I implemented different API response methods to allow for different HTTP response codes to be sent. The addList code is still dumb and doesn't do anything, but it's getting there. Signed-off-by: Mcat12 --- Makefile | 4 +- api.c | 14 +- api.h | 4 +- api_dns.c | 25 +- api_stats.c | 22 +- cJSON.c | 2701 +++++++++++++++++++++++++++++++++++++++++++++++++++ cJSON.h | 263 +++++ 7 files changed, 3012 insertions(+), 21 deletions(-) create mode 100644 cJSON.c create mode 100644 cJSON.h diff --git a/Makefile b/Makefile index 2d0b095d..1217f48c 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -DEPS = FTL.h routines.h api.h version.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o +DEPS = FTL.h routines.h api.h version.h cJSON.h +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o cJSON.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') diff --git a/api.c b/api.c index ca7ec8b6..bdf15004 100644 --- a/api.c +++ b/api.c @@ -11,12 +11,20 @@ #include "FTL.h" #include "api.h" -void sendAPIResponse(int sock, char type) { +void sendAPIResponse(int sock, char type, char *http_status) { if(type == APIH) { // Send header only for full HTTP requests ssend(sock, - "HTTP/1.0 200 OK\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\n\n{"); + "HTTP/1.0 %s\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" + "Content-Type: application/json\n\n{", http_status); } } + +void sendAPIResponseOK(int sock, char type) { + sendAPIResponse(sock, type, "200 OK"); +} + +void sendAPIResponseBadRequest(int sock, char type) { + sendAPIResponse(sock, type, "400 Bad Request"); +} diff --git a/api.h b/api.h index a5468780..393f5f2c 100644 --- a/api.h +++ b/api.h @@ -31,4 +31,6 @@ void addList(int *sock, char type, char list_type, char *data); void getPiholeStatus(int *sock, char type); // General API commands -void sendAPIResponse(int sock, char type); +void sendAPIResponse(int sock, char type, char *http_status); +void sendAPIResponseOK(int sock, char type); +void sendAPIResponseBadRequest(int sock, char type); diff --git a/api_dns.c b/api_dns.c index f1135d58..723884ea 100644 --- a/api_dns.c +++ b/api_dns.c @@ -10,6 +10,7 @@ #include "FTL.h" #include "api.h" +#include "cJSON.h" void getList(int *sock, char type, char list_type) { @@ -17,7 +18,7 @@ void getList(int *sock, char type, char list_type) char *line = NULL; size_t size = 0; - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) @@ -53,12 +54,28 @@ void getList(int *sock, char type, char list_type) void getPiholeStatus(int *sock, char type) { int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); } void addList(int *sock, char type, char list_type, char *data) { - sendAPIResponse(*sock, type); - ssend(*sock, "\"status\": \"success\""); + cJSON *input_root = cJSON_Parse(data); + cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); + char *domain; + + // Validate domain + if(cJSON_IsString(domain_json)) { + domain = domain_json->valuestring; + + // Valid domain + sendAPIResponseOK(*sock, type); + ssend(*sock, "\"status\":\"success\",\"domain\":\"%s\"", domain); + } + else { + sendAPIResponseBadRequest(*sock, type); + ssend(*sock, "\"status\":\"no_domain\""); + } + + cJSON_Delete(input_root); } diff --git a/api_stats.c b/api_stats.c index c314b52e..ac8a4ff2 100644 --- a/api_stats.c +++ b/api_stats.c @@ -60,7 +60,7 @@ void getStats(int *sock, char type) } else { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); } @@ -94,7 +94,7 @@ void getOverTime(int *sock, char type) else { // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock,"\"domains_over_time\":{"); // Send "domains_over_time" data @@ -224,7 +224,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(type != SOCKET) { // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); if(blocked) ssend(*sock, "\"top_ads\":{"); @@ -358,7 +358,7 @@ void getTopClients(char *client_message, int *sock, char type) if(type != SOCKET) { // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"top_clients\":{"); } @@ -437,7 +437,7 @@ void getForwardDestinations(char *client_message, int *sock, char type) } // Send HTTP headers with unknown content length - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); // Send initial JSON output if(type != SOCKET) @@ -516,7 +516,7 @@ void getForwardNames(int *sock, char type) if(type != SOCKET) { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock,"\"forward_destinations\":{"); } @@ -555,7 +555,7 @@ void getQueryTypes(int *sock, char type) ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); else { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", counters.IPv4, counters.IPv6, @@ -725,7 +725,7 @@ void getAllQueries(char *client_message, int *sock, char type) if(type != SOCKET) { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"history\":["); } @@ -847,7 +847,7 @@ void getRecentBlocked(char *client_message, int *sock, char type) if(type != SOCKET) { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock, "\"recent_blocked\":["); } @@ -924,7 +924,7 @@ void getForwardDestinationsOverTime(int *sock, char type) if(type != SOCKET) { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock,"\"over_time\":{"); } @@ -1002,7 +1002,7 @@ void getQueryTypesOverTime(int *sock, char type) if(type != SOCKET) { - sendAPIResponse(*sock, type); + sendAPIResponseOK(*sock, type); ssend(*sock,"\"query_types\":{"); } diff --git a/cJSON.c b/cJSON.c new file mode 100644 index 00000000..93798ac5 --- /dev/null +++ b/cJSON.c @@ -0,0 +1,2701 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#define true ((cJSON_bool)1) +#define false ((cJSON_bool)0) + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 5) || (CJSON_VERSION_PATCH != 7) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(*allocate)(size_t size); + void (*deallocate)(void *pointer); + void *(*reallocate)(void *pointer, size_t size); +} internal_hooks; + +static internal_hooks global_hooks = { malloc, free, realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + if (!(copy = (unsigned char*)hooks->allocate(length))) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +#define cannot_read(buffer, size) (!can_read(buffer, size)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + memcpy(newbuffer, p->buffer, p->offset + 1); + } + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26]; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if ((d * 0) != 0) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occured */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = strlen((const char*)value) + sizeof(""); + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(&buffer))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + else + { + global_error = local_error; + } + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +#define cjson_min(a, b) ((a < b) ? a : b) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(256); + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->length); + buffer->buffer = NULL; + if (printed == NULL) { + goto fail; + } + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((len < 0) || (buf == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buf; + p.length = (size_t)len; + p.offset = 0; + p.noalloc = true; + p.format = fmt; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + if (!output_buffer->noalloc) + { + output_buffer->hooks.deallocate(output_buffer->buffer); + } + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* faile to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL)) + { + return; + } + + child = array->child; + + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + } + else + { + /* append to the end */ + while (child->next) + { + child = child->next; + } + suffix_object(child, item); + } +} + +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + if (item == NULL) + { + return; + } + + /* call cJSON_AddItemToObjectCS for code reuse */ + cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); + /* remove cJSON_StringIsConst flag */ + item->type &= ~cJSON_StringIsConst; +} + +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + if ((item == NULL) || (string == NULL)) + { + return; + } + if (!(item->type & cJSON_StringIsConst) && item->string) + { + global_hooks.deallocate(item->string); + } + item->string = (char*)string; + item->type |= cJSON_StringIsConst; + cJSON_AddItemToArray(object, item); +} +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return; + } + + cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return; + } + + cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item->prev != NULL) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + cJSON_AddItemToArray(array, newitem); + return; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (parent->child == item) + { + parent->child = replacement; + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return; + } + + cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); + + return true; +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = b ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + unsigned char *into = (unsigned char*)json; + + if (json == NULL) + { + return; + } + + while (*json) + { + if (*json == ' ') + { + json++; + } + else if (*json == '\t') + { + /* Whitespace characters. */ + json++; + } + else if (*json == '\r') + { + json++; + } + else if (*json=='\n') + { + json++; + } + else if ((*json == '/') && (json[1] == '/')) + { + /* double-slash comments, to end of line. */ + while (*json && (*json != '\n')) + { + json++; + } + } + else if ((*json == '/') && (json[1] == '*')) + { + /* multiline comments. */ + while (*json && !((*json == '*') && (json[1] == '/'))) + { + json++; + } + json += 2; + } + else if (*json == '\"') + { + /* string literals, which are \" sensitive. */ + *into++ = (unsigned char)*json++; + while (*json && (*json != '\"')) + { + if (*json == '\\') + { + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + *into++ = (unsigned char)*json++; + } + else + { + /* All other characters. */ + *into++ = (unsigned char)*json++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (a->valuedouble == b->valuedouble) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/cJSON.h b/cJSON.h new file mode 100644 index 00000000..0878fd0f --- /dev/null +++ b/cJSON.h @@ -0,0 +1,263 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 5 +#define CJSON_VERSION_PATCH 7 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + void *(*malloc_fn)(size_t sz); + void (*free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type __stdcall +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall +#endif +#else /* !WIN32 */ +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* These utilities create an Array of count items. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detatch items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will +need to be released. With recurse!=0, it will duplicate any children connected to the item. +The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + + +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Macros for creating things quickly. */ +#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) +#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) +#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) +#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) +#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) +#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) +#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s)) + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif From 2f8e5409eb34fd544cc04415db747dfc9a19e221 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 9 Aug 2017 22:05:51 -0400 Subject: [PATCH 073/168] Check if domain is invalid Signed-off-by: Mcat12 --- FTL.h | 1 + api.c | 27 +++++++++++++++++++++++++++ api.h | 2 ++ api_dns.c | 14 +++++++++++--- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/FTL.h b/FTL.h index c2a3d595..b361f005 100644 --- a/FTL.h +++ b/FTL.h @@ -23,6 +23,7 @@ #include #include #include +#include // struct sockaddr_in #include // char* inet_ntoa(struct in_addr in) diff --git a/api.c b/api.c index bdf15004..7ab5c455 100644 --- a/api.c +++ b/api.c @@ -28,3 +28,30 @@ void sendAPIResponseOK(int sock, char type) { void sendAPIResponseBadRequest(int sock, char type) { sendAPIResponse(sock, type, "400 Bad Request"); } + +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-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$"; + 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); +} diff --git a/api.h b/api.h index 393f5f2c..4252084e 100644 --- a/api.h +++ b/api.h @@ -34,3 +34,5 @@ void getPiholeStatus(int *sock, char type); void sendAPIResponse(int sock, char type, char *http_status); void sendAPIResponseOK(int sock, char type); void sendAPIResponseBadRequest(int sock, char type); +bool matchesRegex(char *regex_expression, char *input); +bool isValidDomain(char *domain); diff --git a/api_dns.c b/api_dns.c index 723884ea..af112431 100644 --- a/api_dns.c +++ b/api_dns.c @@ -68,11 +68,19 @@ void addList(int *sock, char type, char list_type, char *data) if(cJSON_IsString(domain_json)) { domain = domain_json->valuestring; - // Valid domain - sendAPIResponseOK(*sock, type); - ssend(*sock, "\"status\":\"success\",\"domain\":\"%s\"", domain); + if(isValidDomain(domain)) { + // Valid domain + sendAPIResponseOK(*sock, type); + ssend(*sock, "\"status\":\"success\""); + } + else { + // Invalid domain + sendAPIResponseBadRequest(*sock, type); + ssend(*sock, "\"status\":\"invalid_domain\""); + } } else { + // No domain sendAPIResponseBadRequest(*sock, type); ssend(*sock, "\"status\":\"no_domain\""); } From 3a5752d9089f8ebd2e3c93ce6e0d5eac9dcda7fb Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 9 Aug 2017 22:40:33 -0400 Subject: [PATCH 074/168] Actually add the domain to the list This requires the `pihole` user to have sudo access to the `pihole` command (will require a core update). Many future API commands will have this requirement, as they too will be using the `pihole` command. Signed-off-by: Mcat12 --- api.c | 4 ++++ api.h | 1 + api_dns.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/api.c b/api.c index 7ab5c455..3545876a 100644 --- a/api.c +++ b/api.c @@ -29,6 +29,10 @@ void sendAPIResponseBadRequest(int sock, char type) { sendAPIResponse(sock, type, "400 Bad Request"); } +void sendAPIResponseInternalServerError(int sock, char type) { + sendAPIResponse(sock, type, "500 Internal Server Error"); +} + bool matchesRegex(char *regex_expression, char *input) { regex_t regex; int result; diff --git a/api.h b/api.h index 4252084e..7aab675a 100644 --- a/api.h +++ b/api.h @@ -34,5 +34,6 @@ void getPiholeStatus(int *sock, char type); void sendAPIResponse(int sock, char type, char *http_status); void sendAPIResponseOK(int sock, char type); void sendAPIResponseBadRequest(int sock, char type); +void sendAPIResponseInternalServerError(int sock, char type); bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); diff --git a/api_dns.c b/api_dns.c index af112431..3068260e 100644 --- a/api_dns.c +++ b/api_dns.c @@ -70,8 +70,32 @@ void addList(int *sock, char type, char list_type, char *data) if(isValidDomain(domain)) { // Valid domain - sendAPIResponseOK(*sock, type); - ssend(*sock, "\"status\":\"success\""); + char *partial_command; + + if(list_type == WHITELIST) + partial_command = "sudo pihole -w -q %s"; + else if(list_type == BLACKLIST) + partial_command = "sudo pihole -b -q %s"; + else { + logg("Invalid list type in addList"); + exit(EXIT_FAILURE); + } + + char *command = malloc((strlen(domain) + strlen(partial_command)) * sizeof(char)); + sprintf(command, partial_command, domain); + int return_code = system(command); + free(command); + + if(return_code == 0) { + // Successfully added to list + sendAPIResponseOK(*sock, type); + ssend(*sock, "\"status\":\"success\""); + } + else { + // Failed to add to list + sendAPIResponseInternalServerError(*sock, type); + ssend(*sock, "\"status\":\"unknown_error\""); + } } else { // Invalid domain From a743931d150a1ff9b7d2e3aa30a35814b6634180 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 10 Aug 2017 22:00:13 -0400 Subject: [PATCH 075/168] Improve API response method to use enums --- api.c | 32 +++++++++++++++++++------------- api.h | 8 ++++---- api_dns.c | 21 +++++++++------------ api_stats.c | 22 +++++++++++----------- 4 files changed, 43 insertions(+), 40 deletions(-) diff --git a/api.c b/api.c index 3545876a..0fa9ed69 100644 --- a/api.c +++ b/api.c @@ -11,7 +11,25 @@ #include "FTL.h" #include "api.h" -void sendAPIResponse(int sock, char type, char *http_status) { +void sendAPIResponse(int sock, char type, char http_code) { + 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; + } + if(type == APIH) { // Send header only for full HTTP requests @@ -21,18 +39,6 @@ void sendAPIResponse(int sock, char type, char *http_status) { } } -void sendAPIResponseOK(int sock, char type) { - sendAPIResponse(sock, type, "200 OK"); -} - -void sendAPIResponseBadRequest(int sock, char type) { - sendAPIResponse(sock, type, "400 Bad Request"); -} - -void sendAPIResponseInternalServerError(int sock, char type) { - sendAPIResponse(sock, type, "500 Internal Server Error"); -} - bool matchesRegex(char *regex_expression, char *input) { regex_t regex; int result; diff --git a/api.h b/api.h index 7aab675a..d2b64032 100644 --- a/api.h +++ b/api.h @@ -30,10 +30,10 @@ void getList(int *sock, char type, char list_type); void addList(int *sock, char type, char list_type, char *data); void getPiholeStatus(int *sock, char type); +// HTTP Response Codes +enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND }; + // General API commands -void sendAPIResponse(int sock, char type, char *http_status); -void sendAPIResponseOK(int sock, char type); -void sendAPIResponseBadRequest(int sock, char type); -void sendAPIResponseInternalServerError(int sock, char type); +void sendAPIResponse(int sock, char type, char http_code); bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); diff --git a/api_dns.c b/api_dns.c index 3068260e..4bd95906 100644 --- a/api_dns.c +++ b/api_dns.c @@ -12,13 +12,12 @@ #include "api.h" #include "cJSON.h" -void getList(int *sock, char type, char list_type) -{ +void getList(int *sock, char type, char list_type) { FILE *fp; char *line = NULL; size_t size = 0; - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) @@ -51,15 +50,13 @@ void getList(int *sock, char type, char list_type) ssend(*sock, "]"); } -void getPiholeStatus(int *sock, char type) -{ +void getPiholeStatus(int *sock, char type) { int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); } -void addList(int *sock, char type, char list_type, char *data) -{ +void addList(int *sock, char type, char list_type, char *data) { cJSON *input_root = cJSON_Parse(data); cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); char *domain; @@ -88,24 +85,24 @@ void addList(int *sock, char type, char list_type, char *data) if(return_code == 0) { // Successfully added to list - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":\"success\""); } else { // Failed to add to list - sendAPIResponseInternalServerError(*sock, type); + sendAPIResponse(*sock, type, INTERNAL_ERROR); ssend(*sock, "\"status\":\"unknown_error\""); } } else { // Invalid domain - sendAPIResponseBadRequest(*sock, type); + sendAPIResponse(*sock, type, BAD_REQUEST); ssend(*sock, "\"status\":\"invalid_domain\""); } } else { // No domain - sendAPIResponseBadRequest(*sock, type); + sendAPIResponse(*sock, type, BAD_REQUEST); ssend(*sock, "\"status\":\"no_domain\""); } diff --git a/api_stats.c b/api_stats.c index ac8a4ff2..a8f60169 100644 --- a/api_stats.c +++ b/api_stats.c @@ -60,7 +60,7 @@ void getStats(int *sock, char type) } else { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock,"\"domains_being_blocked\":%i,\"dns_queries_today\":%i,\"ads_blocked_today\":%i,\"ads_percentage_today\":%.4f,\"unique_domains\":%i,\"queries_forwarded\":%i,\"queries_cached\":%i,\"unique_clients\":%i",counters.gravity,total, blocked, percentage,counters.domains,counters.forwardedqueries,counters.cached,counters.clients); } @@ -94,7 +94,7 @@ void getOverTime(int *sock, char type) else { // First send header with unspecified content-length outside of the for-loop - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock,"\"domains_over_time\":{"); // Send "domains_over_time" data @@ -224,7 +224,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(type != SOCKET) { // First send header with unspecified content-length outside of the for-loop - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); if(blocked) ssend(*sock, "\"top_ads\":{"); @@ -358,7 +358,7 @@ void getTopClients(char *client_message, int *sock, char type) if(type != SOCKET) { // First send header with unspecified content-length outside of the for-loop - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"top_clients\":{"); } @@ -437,7 +437,7 @@ void getForwardDestinations(char *client_message, int *sock, char type) } // Send HTTP headers with unknown content length - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); // Send initial JSON output if(type != SOCKET) @@ -516,7 +516,7 @@ void getForwardNames(int *sock, char type) if(type != SOCKET) { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock,"\"forward_destinations\":{"); } @@ -555,7 +555,7 @@ void getQueryTypes(int *sock, char type) ssend(*sock,"A (IPv4): %i\nAAAA (IPv6): %i\n",counters.IPv4,counters.IPv6); else { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"query_types\":{\"A (IPv4)\":%i,\"AAAA (IPv6)\":%i,\"PTR\":%i,\"SRV\":%i}", counters.IPv4, counters.IPv6, @@ -725,7 +725,7 @@ void getAllQueries(char *client_message, int *sock, char type) if(type != SOCKET) { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"history\":["); } @@ -847,7 +847,7 @@ void getRecentBlocked(char *client_message, int *sock, char type) if(type != SOCKET) { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock, "\"recent_blocked\":["); } @@ -924,7 +924,7 @@ void getForwardDestinationsOverTime(int *sock, char type) if(type != SOCKET) { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock,"\"over_time\":{"); } @@ -1002,7 +1002,7 @@ void getQueryTypesOverTime(int *sock, char type) if(type != SOCKET) { - sendAPIResponseOK(*sock, type); + sendAPIResponse(*sock, type, OK); ssend(*sock,"\"query_types\":{"); } From 87dc82c09d20fdd128fdf9e61f0aa9b75e52cbcf Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 10 Aug 2017 22:51:22 -0400 Subject: [PATCH 076/168] Add extra character for null terminator --- api_dns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_dns.c b/api_dns.c index 4bd95906..55634ee6 100644 --- a/api_dns.c +++ b/api_dns.c @@ -78,7 +78,7 @@ void addList(int *sock, char type, char list_type, char *data) { exit(EXIT_FAILURE); } - char *command = malloc((strlen(domain) + strlen(partial_command)) * sizeof(char)); + char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); sprintf(command, partial_command, domain); int return_code = system(command); free(command); From 1893aa5c252d55cff5bf8ed90006b94c343904b7 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 10 Aug 2017 23:10:44 -0400 Subject: [PATCH 077/168] Add whitelist and blacklist deletion API endpoints Signed-off-by: Mcat12 --- api.h | 1 + api_dns.c | 158 ++++++++++++++++++++++++++++++++++++++++-------------- request.c | 13 +++-- socket.c | 2 +- 4 files changed, 130 insertions(+), 44 deletions(-) diff --git a/api.h b/api.h index d2b64032..096ab18c 100644 --- a/api.h +++ b/api.h @@ -28,6 +28,7 @@ void getDBstats(int *sock, char type); // Endpoints under /dns/ void getList(int *sock, char type, char list_type); void addList(int *sock, char type, char list_type, char *data); +void removeList(int *sock, char type, char list_type, char *client_message); void getPiholeStatus(int *sock, char type); // HTTP Response Codes diff --git a/api_dns.c b/api_dns.c index 55634ee6..b64ad187 100644 --- a/api_dns.c +++ b/api_dns.c @@ -62,49 +62,127 @@ void addList(int *sock, char type, char list_type, char *data) { char *domain; // Validate domain - if(cJSON_IsString(domain_json)) { - domain = domain_json->valuestring; - - if(isValidDomain(domain)) { - // Valid domain - char *partial_command; - - if(list_type == WHITELIST) - partial_command = "sudo pihole -w -q %s"; - else if(list_type == BLACKLIST) - partial_command = "sudo pihole -b -q %s"; - else { - logg("Invalid list type in addList"); - exit(EXIT_FAILURE); - } - - char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); - sprintf(command, partial_command, domain); - int return_code = system(command); - free(command); - - if(return_code == 0) { - // Successfully added to list - sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"status\":\"success\""); - } - else { - // Failed to add to list - sendAPIResponse(*sock, type, INTERNAL_ERROR); - ssend(*sock, "\"status\":\"unknown_error\""); - } - } - else { - // Invalid domain - sendAPIResponse(*sock, type, BAD_REQUEST); - ssend(*sock, "\"status\":\"invalid_domain\""); - } - } - else { - // No domain + if(!cJSON_IsString(domain_json)) { + // No domain found sendAPIResponse(*sock, type, BAD_REQUEST); ssend(*sock, "\"status\":\"no_domain\""); + return; + } + + domain = domain_json->valuestring; + + if(!isValidDomain(domain)) { + // Invalid domain + sendAPIResponse(*sock, type, BAD_REQUEST); + ssend(*sock, "\"status\":\"invalid_domain\""); + return; + } + + // Get command + char *partial_command; + + if(list_type == WHITELIST) + partial_command = "sudo pihole -w -q "; + else if(list_type == BLACKLIST) + partial_command = "sudo pihole -b -q "; + else { + logg("Invalid list type in addList"); + exit(EXIT_FAILURE); + } + + // Run command + char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); + strcpy(command, partial_command); + strcat(command, domain); + int return_code = system(command); + free(command); + + if(return_code == 0) { + // Successfully added to list + sendAPIResponse(*sock, type, OK); + ssend(*sock, "\"status\":\"success\""); + } + else { + // Failed to add to list + sendAPIResponse(*sock, type, INTERNAL_ERROR); + ssend(*sock, "\"status\":\"unknown_error\""); } cJSON_Delete(input_root); } + +void removeList(int *sock, char type, char list_type, char *client_message) { + char *domain = strrchr(client_message, '/'); + + // Remove leading '/' + domain++; + + // Validate route + char *expected_route_start; + char *expected_route; + + if(list_type == WHITELIST) + expected_route_start = "/dns/whitelist/"; + else if(list_type == BLACKLIST) + expected_route_start = "/dns/blacklist/"; + else { + logg("Invalid list type in removeList"); + exit(EXIT_FAILURE); + } + + expected_route = malloc((strlen(expected_route_start) + strlen(domain) + 1) * sizeof(char)); + strcpy(expected_route, expected_route_start); + strcat(expected_route, domain); + + if(!strstr(client_message, expected_route)) { + // Invalid route + free(expected_route); + sendAPIResponse(*sock, type, NOT_FOUND); + ssend(*sock, "\"status\":\"not_found\""); + return; + } + + free(expected_route); + + // Validate domain + + if(domain == NULL) { + // No domain found + sendAPIResponse(*sock, type, NOT_FOUND); + ssend(*sock, "\"status\":\"not_found\""); + return; + } + + if(!isValidDomain(domain)) { + // Invalid domain + sendAPIResponse(*sock, type, BAD_REQUEST); + ssend(*sock, "\"status\":\"invalid_domain\""); + return; + } + + // Get command + char *partial_command; + + if(list_type == WHITELIST) + partial_command = "sudo pihole -w -q -d "; + else + partial_command = "sudo pihole -b -q -d "; + + // Run command + char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); + strcpy(command, partial_command); + strcat(command, domain); + int return_code = system(command); + free(command); + + if(return_code == 0) { + // Successfully removed from list + sendAPIResponse(*sock, type, OK); + ssend(*sock, "\"status\":\"success\""); + } + else { + // Failed to remove from list + sendAPIResponse(*sock, type, INTERNAL_ERROR); + ssend(*sock, "\"status\":\"unknown_error\""); + } +} diff --git a/request.c b/request.c index e722c82e..eae41838 100644 --- a/request.c +++ b/request.c @@ -219,6 +219,10 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo { addList(sock, type, WHITELIST, data); } + else if(command(client_message, "DELETE /dns/whitelist/")) + { + removeList(sock, type, WHITELIST, client_message); + } else if(command(client_message, "GET /dns/blacklist")) { getList(sock, type, BLACKLIST); @@ -227,15 +231,18 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo { addList(sock, type, BLACKLIST, data); } + else if(command(client_message, "DELETE /dns/blacklist/")) + { + removeList(sock, type, BLACKLIST, client_message); + } else if(command(client_message, "GET /dns/status")) { getPiholeStatus(sock, type); } else if(header) { - ssend(*sock, - "HTTP/1.0 404 Not Found\nServer: FTL\nCache-Control: no-cache\nAccess-Control-Allow-Origin: *\n" - "Content-Type: application/json\nContent-Length: 23\n\n{\"status\": \"not_found\""); + sendAPIResponse(*sock, type, NOT_FOUND); + ssend(*sock, "\"status\":\"not_found\""); } ssend(*sock, "}"); diff --git a/socket.c b/socket.c index 7b63cacb..64521625 100644 --- a/socket.c +++ b/socket.c @@ -312,7 +312,7 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); - if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0) + if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0 || strncmp(message, "DELETE ", 7) == 0) { // HTTP requests can be simple or full. // A simple request contains one line only, and looks like this: From 06be1c48068899de25bdf29ac6176c4d0b60dea8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 11 Aug 2017 21:05:07 -0400 Subject: [PATCH 078/168] Move debug API request logging to see all requests --- request.c | 3 --- socket.c | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/request.c b/request.c index eae41838..9c6f9a72 100644 --- a/request.c +++ b/request.c @@ -153,9 +153,6 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo else type = API; - if(debug) - logg("Received API request: %s", full_message); - char *data = getPayload(full_message); if(command(client_message, "GET /stats/summary")) diff --git a/socket.c b/socket.c index 64521625..1741a95c 100644 --- a/socket.c +++ b/socket.c @@ -312,6 +312,9 @@ void *api_connection_handler_thread(void *socket_desc) // Clear client message receive buffer memset(client_message, 0, sizeof client_message); + if(debug) + logg("Received API request: \n%s", message); + if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0 || strncmp(message, "DELETE ", 7) == 0) { // HTTP requests can be simple or full. From 729d5d4ea91963fae975e361ef85aecf44cdd63d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 11 Aug 2017 21:07:08 -0400 Subject: [PATCH 079/168] Respond to CORS preflight (OPTIONS) --- socket.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/socket.c b/socket.c index 1741a95c..398fd441 100644 --- a/socket.c +++ b/socket.c @@ -366,20 +366,18 @@ void *api_connection_handler_thread(void *socket_desc) { logg("API received malformated request: \"%s\"", message); } - - // Close connection to show that we reached the end of the transmission - close(sock); - sock = 0; + } + else if(strncmp(message, "OPTIONS ", 8) == 0) + { + // OPTIONS request: CORS preflight + ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nAccess-Control-Allow-Origin: *\n" + "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\n" + "Access-Control-Allow-Headers: Content-Type\n\n"); } else if(strncmp(message, "HEAD ", 5) == 0) { // HEAD request: We do not send any content at all - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); - - // Close connection to show that we reached the end of the transmission - close(sock); - sock = 0; } else { @@ -387,6 +385,10 @@ void *api_connection_handler_thread(void *socket_desc) logg("API received something strange"); } + // Close connection to show that we reached the end of the transmission + close(sock); + sock = 0; + // Free allocated memory free(message); } From fb574f7485d200d2feae8d929920759bd8586b54 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 16 Aug 2017 21:04:58 -0400 Subject: [PATCH 080/168] Perform better request endpoint checking Signed-off-by: Mcat12 --- request.c | 66 +++++++++++++++++++++++++++++++++++++----------------- routines.h | 1 + 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/request.c b/request.c index 9c6f9a72..b8f7cdb6 100644 --- a/request.c +++ b/request.c @@ -155,27 +155,27 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo char *data = getPayload(full_message); - if(command(client_message, "GET /stats/summary")) + if(matchesEndpoint(client_message, "GET /stats/summary")) { getStats(sock, type); } - else if(command(client_message, "GET /stats/overTime/graph")) + else if(matchesEndpoint(client_message, "GET /stats/overTime/graph")) { getOverTime(sock, type); } - else if(command(client_message, "GET /stats/top_domains") || command(client_message, "GET /stats/top_ads")) + else if(matchesEndpoint(client_message, "GET /stats/top_domains") || matchesEndpoint(client_message, "GET /stats/top_ads")) { getTopDomains(client_message, sock, type); } - else if(command(client_message, "GET /stats/top_clients")) + else if(matchesEndpoint(client_message, "GET /stats/top_clients")) { getTopClients(client_message, sock, type); } - else if(command(client_message, "GET /stats/forward_dest") || command(client_message, "GET /stats/forward_destinations")) + else if(matchesEndpoint(client_message, "GET /stats/forward_dest") || matchesEndpoint(client_message, "GET /stats/forward_destinations")) { getForwardDestinations(client_message, sock, type); } - else if(command(client_message, "GET /stats/dashboard")) + else if(matchesEndpoint(client_message, "GET /stats/dashboard")) { getStats(sock, type); type = API; @@ -188,51 +188,51 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo ssend(*sock, ","); getForwardDestinations(client_message, sock, type); } - else if(command(client_message, "GET /stats/query_types")) + else if(matchesEndpoint(client_message, "GET /stats/query_types")) { getQueryTypes(sock, type); } - else if(command(client_message, "GET /stats/history")) + else if(matchesEndpoint(client_message, "GET /stats/history")) { getAllQueries(client_message, sock, type); } - else if(command(client_message, "GET /stats/recent_blocked")) + else if(matchesEndpoint(client_message, "GET /stats/recent_blocked")) { getRecentBlocked(client_message, sock, type); } - else if(command(client_message, "GET /stats/overTime/forward_dest")) + else if(matchesEndpoint(client_message, "GET /stats/overTime/forward_dest")) { getForwardDestinationsOverTime(sock, type); } - else if(command(client_message, "GET /stats/overTime/query_types")) + else if(matchesEndpoint(client_message, "GET /stats/overTime/query_types")) { getQueryTypesOverTime(sock, type); } - else if(command(client_message, "GET /dns/whitelist")) + else if(matchesEndpoint(client_message, "GET /dns/whitelist")) { getList(sock, type, WHITELIST); } - else if(command(client_message, "POST /dns/whitelist")) + else if(matchesEndpoint(client_message, "POST /dns/whitelist")) { addList(sock, type, WHITELIST, data); } - else if(command(client_message, "DELETE /dns/whitelist/")) + else if(matchesRegex("DELETE \\/dns\\/whitelist\\/[^\\/]*$", client_message)) { removeList(sock, type, WHITELIST, client_message); } - else if(command(client_message, "GET /dns/blacklist")) + else if(matchesEndpoint(client_message, "GET /dns/blacklist")) { getList(sock, type, BLACKLIST); } - else if(command(client_message, "POST /dns/blacklist")) + else if(matchesEndpoint(client_message, "POST /dns/blacklist")) { addList(sock, type, BLACKLIST, data); } - else if(command(client_message, "DELETE /dns/blacklist/")) + else if(matchesRegex("DELETE \\/dns\\/blacklist\\/[^\\/]*$", client_message)) { removeList(sock, type, BLACKLIST, client_message); } - else if(command(client_message, "GET /dns/status")) + else if(matchesEndpoint(client_message, "GET /dns/status")) { getPiholeStatus(sock, type); } @@ -245,7 +245,31 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo ssend(*sock, "}"); } -bool command(char *client_message, const char* cmd) -{ - return strstr(client_message,cmd) != NULL; +bool command(char *client_message, const char* cmd) { + return strstr(client_message, cmd) != NULL; +} + +bool matchesEndpoint(char *client_message, const char *cmd) { + char *get_params_start = strstr(client_message, "?"); + bool result; + + // Check if there are GET parameters to ignore + if(get_params_start != NULL) { + char without_get_params[256]; + + // Check to make sure we don't overflow the buffer + if(strlen(cmd)+1 > sizeof(without_get_params) / sizeof(char)) + return false; + + size_t msg_len = get_params_start - client_message; + + strncpy(without_get_params, client_message, msg_len); + without_get_params[msg_len] = 0; + + result = strcmp(without_get_params, cmd) == 0; + } + else + result = strcmp(client_message, cmd) == 0; + + return result; } diff --git a/routines.h b/routines.h index d6baa5f5..11b08a6a 100644 --- a/routines.h +++ b/routines.h @@ -45,6 +45,7 @@ void *api_listening_thread(void *args); void process_socket_request(char *client_message, int *sock); void process_api_request(char *client_message, char *full_message, int *sock, bool header); bool command(char *client_message, const char* cmd); +bool matchesEndpoint(char *client_message, const char *cmd); void read_gravity_files(void); int countlines(const char* fname); From 4842c16e3426a0cc33401942710e0be8889a1b6d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 4 Sep 2017 16:16:07 -0400 Subject: [PATCH 081/168] Fix broken domain verification if the domain includes numbers Signed-off-by: Mcat12 --- api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.c b/api.c index 0fa9ed69..5f7cf121 100644 --- a/api.c +++ b/api.c @@ -57,7 +57,7 @@ bool matchesRegex(char *regex_expression, char *input) { } bool isValidDomain(char *domain) { - char *valid_chars_regex = "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$"; + 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})*$"; From 834a9598ebd7b523b6c69fb513736ab3311d5875 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 4 Sep 2017 16:25:55 -0400 Subject: [PATCH 082/168] Add Wildlist support Signed-off-by: Mcat12 --- FTL.h | 2 +- api_dns.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- request.c | 12 +++++++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/FTL.h b/FTL.h index b361f005..921f3ef3 100644 --- a/FTL.h +++ b/FTL.h @@ -185,7 +185,7 @@ typedef struct { enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; enum { SOCKET, API, APIH }; -enum { WHITELIST, BLACKLIST }; +enum { WHITELIST, BLACKLIST, WILDLIST }; logFileNamesStruct files; FTLFileNamesStruct FTLfiles; diff --git a/api_dns.c b/api_dns.c index b64ad187..f442326b 100644 --- a/api_dns.c +++ b/api_dns.c @@ -16,26 +16,85 @@ void getList(int *sock, char type, char list_type) { FILE *fp; char *line = NULL; size_t size = 0; + const char *file; + const char *name; + + if(list_type == WHITELIST) { + file = files.whitelist; + name = "whitelist"; + } + else if(list_type == BLACKLIST) { + file = files.blacklist; + name = "blacklist"; + } + else { + file = files.wildcards; + name = "wildlist"; + } sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"%s\":[", list_type == WHITELIST ? "whitelist" : "blacklist"); + ssend(*sock, "\"%s\":[", name); - if((fp = fopen(list_type == WHITELIST ? files.whitelist : files.blacklist, "r")) != NULL) + if((fp = fopen(file, "r")) != NULL) { bool first = true; + bool skipEveryOther = false; + bool skip = true; + char *parsedLine; + + // Check if both IPv4 and IPv6 are used. If so, skip every other line in if we're getting wildcard domains + if(list_type == WILDLIST) { + char *ipv4 = read_setupVarsconf("IPV4_ADDRESS"); + size_t ipv4_len = strlen(ipv4); + char *ipv6 = read_setupVarsconf("IPV6_ADDRESS"); + size_t ipv6_len = strlen(ipv6); + + if(ipv4_len > 0 && ipv6_len > 0) + skipEveryOther = true; + } while(getline(&line, &size, fp) != -1) { // Skip empty lines if(line[0] == '\n') continue; + // If applicable, skip every other line + if(skipEveryOther) { + skip = !skip; + + if(skip) + continue; + } + if(!first) ssend(*sock, ","); first = false; // Trim off the newline, if it exists line[strcspn(line, "\r\n")] = 0; - ssend(*sock, "\"%s\"", line); + // Do more parsing if it's the wildcard list + if(list_type == WILDLIST) { + char *firstSlash = strstr(line, "/"); + + if(firstSlash == NULL) { + logg("Failed to parse wildcard line: %s", line); + continue; + } + + char *secondSlash = strstr(firstSlash+1, "/"); + + if(secondSlash == NULL) { + logg("Failed to parse wildcard line: %s", line); + continue; + } + + secondSlash[0] = 0; + parsedLine = firstSlash+1; + } + else + parsedLine = line; + + ssend(*sock, "\"%s\"", parsedLine); } // Free allocated memory if(line != NULL) @@ -85,6 +144,8 @@ void addList(int *sock, char type, char list_type, char *data) { partial_command = "sudo pihole -w -q "; else if(list_type == BLACKLIST) partial_command = "sudo pihole -b -q "; + else if(list_type == WILDLIST) + partial_command = "sudo pihole -wild -q "; else { logg("Invalid list type in addList"); exit(EXIT_FAILURE); @@ -125,6 +186,8 @@ void removeList(int *sock, char type, char list_type, char *client_message) { expected_route_start = "/dns/whitelist/"; else if(list_type == BLACKLIST) expected_route_start = "/dns/blacklist/"; + else if(list_type == WILDLIST) + expected_route_start = "/dns/wildlist/"; else { logg("Invalid list type in removeList"); exit(EXIT_FAILURE); @@ -165,8 +228,10 @@ void removeList(int *sock, char type, char list_type, char *client_message) { if(list_type == WHITELIST) partial_command = "sudo pihole -w -q -d "; - else + else if(list_type == BLACKLIST) partial_command = "sudo pihole -b -q -d "; + else + partial_command = "sudo pihole -wild -q -d "; // Run command char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); diff --git a/request.c b/request.c index b8f7cdb6..555fa439 100644 --- a/request.c +++ b/request.c @@ -232,6 +232,18 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo { removeList(sock, type, BLACKLIST, client_message); } + else if(matchesEndpoint(client_message, "GET /dns/wildlist")) + { + getList(sock, type, WILDLIST); + } + else if(matchesEndpoint(client_message, "POST /dns/wildlist")) + { + addList(sock, type, WILDLIST, data); + } + else if(matchesRegex("DELETE \\/dns\\/wildlist\\/[^\\/]*$", client_message)) + { + removeList(sock, type, WILDLIST, client_message); + } else if(matchesEndpoint(client_message, "GET /dns/status")) { getPiholeStatus(sock, type); From e2f2f0d2b7a94092e9b06ec675a939d7ebbc8ed0 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 27 Sep 2017 00:40:50 -0400 Subject: [PATCH 083/168] Fix tests Signed-off-by: Mcat12 --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 188894dd..a4a9ebb5 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -208,7 +208,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"unique_clients\":3}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"clients_ever_seen\":3,\"unique_clients\":3}" ]] [[ "${status}" -eq 0 ]] } From 3331c429718df0cbddcd4a8e2356da11f5c135ed Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 26 Nov 2017 22:59:24 -0500 Subject: [PATCH 084/168] Move getPayload to api.c Signed-off-by: Mcat12 --- api.c | 18 ++++++++++++++++++ api.h | 1 + request.c | 19 ------------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/api.c b/api.c index 5f7cf121..31443487 100644 --- a/api.c +++ b/api.c @@ -39,6 +39,24 @@ void sendAPIResponse(int sock, char type, char http_code) { } } +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; diff --git a/api.h b/api.h index 7156d430..dd45be53 100644 --- a/api.h +++ b/api.h @@ -35,6 +35,7 @@ void getPiholeStatus(int *sock, char type); enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND }; // General API commands +char* getPayload(char *http_message); void sendAPIResponse(int sock, char type, char http_code); bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); diff --git a/request.c b/request.c index 8a69e1b6..43470106 100644 --- a/request.c +++ b/request.c @@ -126,25 +126,6 @@ void process_socket_request(char *client_message, int *sock) } } -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; -} - void process_api_request(char *client_message, char *full_message, int *sock, bool header) { char type; From 3573f54ed58d69f87191bd320b50dfe93d32c9a8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 22 Dec 2017 23:11:14 -0500 Subject: [PATCH 085/168] Fix getVersion after merge Signed-off-by: Mcat12 --- api_stats.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api_stats.c b/api_stats.c index dd1f1797..6ef0e55e 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1159,10 +1159,9 @@ void getVersion(int *sock, char type) char version[] = GIT_VERSION; if(strstr(version, ".") != NULL) - sprintf(server_message,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); + ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); else - sprintf(server_message,"version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, GIT_BRANCH, GIT_DATE); - swrite(server_message, *sock); + ssend(*sock,"version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, GIT_BRANCH, GIT_DATE); if(debugclients) logg("Sent version info to client, ID: %i", *sock); From 894bd398193fd80bfd5096fd7a84bf00d854c81e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 22 Dec 2017 23:14:19 -0500 Subject: [PATCH 086/168] Fix newly merged in API functions They were using the missing `swrite` function. Signed-off-by: Mcat12 --- api_stats.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api_stats.c b/api_stats.c index 6ef0e55e..4b18f995 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1155,8 +1155,6 @@ void getQueryTypesOverTime(int *sock, char type) void getVersion(int *sock, char type) { - char server_message[SOCKETBUFFERLEN]; - char version[] = GIT_VERSION; if(strstr(version, ".") != NULL) ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, GIT_TAG, GIT_BRANCH, GIT_DATE); @@ -1254,7 +1252,7 @@ void getClientsOverTime(int *sock) } sprintf(server_message + strlen(server_message), "\n"); - swrite(server_message, *sock); + ssend(*sock, server_message); } if(excludeclients != NULL) @@ -1294,7 +1292,7 @@ void getClientNames(int *sock) continue; sprintf(server_message,"%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); - swrite(server_message, *sock); + ssend(*sock, server_message); } if(excludeclients != NULL) From 1f5ca043f17871df49a34ebe0ba9699932e1f5ed Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sat, 23 Dec 2017 00:07:26 -0500 Subject: [PATCH 087/168] Add status to test summary data --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 9200ae5b..36b92498 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -209,7 +209,7 @@ load 'libs/bats-support/load' [[ ${lines[2]} == "Cache-Control: no-cache" ]] [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"clients_ever_seen\":3,\"unique_clients\":3}" ]] + [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"clients_ever_seen\":3,\"unique_clients\":3,\"status\":\"unknown\"}" ]] [[ "${status}" -eq 0 ]] } From 4e90a6e6abfbe0aac557931ccd1831112cf1bf30 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sat, 23 Dec 2017 00:09:04 -0500 Subject: [PATCH 088/168] Add quotation marks around new summary status --- api_stats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_stats.c b/api_stats.c index 4b18f995..c8b24494 100644 --- a/api_stats.c +++ b/api_stats.c @@ -104,7 +104,7 @@ void getStats(int *sock, char type) "\"queries_cached\":%i," "\"clients_ever_seen\":%i," "\"unique_clients\":%i," - "\"status\":%s", + "\"status\":\"%s\"", domains_blocked,total, blocked, percentage, From 2212e23e27845a11e70e2a2998f528e873952f64 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 23 Dec 2017 14:34:07 -0500 Subject: [PATCH 089/168] Fix domains_being_blocked output Signed-off-by: Mcat12 --- api_stats.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api_stats.c b/api_stats.c index c8b24494..d27fa805 100644 --- a/api_stats.c +++ b/api_stats.c @@ -59,7 +59,11 @@ void getStats(int *sock, char type) switch(blockingstatus) { case 0: // Blocking disabled - strncpy(domains_blocked, "N/A", 4); + if(type == SOCKET) + strncpy(domains_blocked, "N/A", 4); + else + strncpy(domains_blocked, "\"N/A\"", 6); + strncpy(status, "disabled", 9); break; case 1: // Blocking Enabled @@ -105,7 +109,8 @@ void getStats(int *sock, char type) "\"clients_ever_seen\":%i," "\"unique_clients\":%i," "\"status\":\"%s\"", - domains_blocked,total, + domains_blocked, + total, blocked, percentage, counters.domains, From 5005f285ec36b9988fc53c8e066581ce337db197 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 24 Dec 2017 16:11:46 -0500 Subject: [PATCH 090/168] Implement simple password-protection to the HTTP API Currently has the hardcoded password of "password". You gain a session by sending JSON to the API with the key of `password` and the value set to the password you are trying to use. Then it will return a success response with the session and will add a Set-Cookie header to set the FTL_SESSION cookie to the session number. The summary, queries graph, and status are accessible without authentication. The internal data structure used to store the sessions is valid for up to 24 minutes after the last query. If it becomes invalid then it is eligible to be reused for a new session. The garbage collection can be optimized further, but it is not a problem at the moment. Signed-off-by: Mcat12 --- FTL.h | 2 +- api.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- api.h | 16 ++++++- request.c | 16 +++++++ structs.c | 10 +++++ 5 files changed, 168 insertions(+), 7 deletions(-) diff --git a/FTL.h b/FTL.h index ff9acffa..f5c9cc98 100644 --- a/FTL.h +++ b/FTL.h @@ -188,7 +188,7 @@ typedef struct { int querytypedata; } memoryStruct; -enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; +enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD, AUTHDATA }; enum { SOCKET, API, APIH }; enum { WHITELIST, BLACKLIST, WILDLIST }; diff --git a/api.c b/api.c index 31443487..2b00ceb2 100644 --- a/api.c +++ b/api.c @@ -10,8 +10,13 @@ #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) { @@ -28,17 +33,135 @@ void sendAPIResponse(int sock, char type, char http_code) { 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) { - // Send header only for full HTTP requests - 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); + 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) { + // 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; + } + + for(int i = 0; i < authLength; i++) { + if(authData[i].valid && authData[i].session == *session) { + 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); + + // Find a unique session number + while(true) { + auth->session = random(); + + bool unique = true; + for(int 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; + for(int i = 0; i < authLength; i++) { + if(!authData[i].valid) { + // Found an invalid auth we can reuse + found = true; + 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"); diff --git a/api.h b/api.h index 4cc0fe87..dfad1af1 100644 --- a/api.h +++ b/api.h @@ -3,7 +3,7 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API commands +* API commands and structures * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ @@ -34,10 +34,22 @@ void removeList(int *sock, char type, char list_type, char *client_message); void getPiholeStatus(int *sock, char type); // HTTP Response Codes -enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND }; +enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND, UNAUTHORIZED }; + +// Authentication +typedef struct { + time_t lastQueryTime; + long session; + bool valid; +} AuthData; +AuthData *authData; +int authLength; +enum Auth { AUTH_UNAUTHORIZED, AUTH_PREVIOUS, AUTH_NEW }; // General API commands +enum Auth authenticate(char *with_headers, char *payload, long *session); char* getPayload(char *http_message); void sendAPIResponse(int sock, char type, char http_code); +void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session); bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); diff --git a/request.c b/request.c index 709b1ab3..c229109b 100644 --- a/request.c +++ b/request.c @@ -146,6 +146,22 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo type = API; char *data = getPayload(full_message); + long session; + + char authResult = authenticate(full_message, data, &session); + if(authResult == AUTH_UNAUTHORIZED && !matchesEndpoint(client_message, "GET /stats/summary") + && !matchesEndpoint(client_message, "GET /stats/overTime/graph") + && !matchesEndpoint(client_message, "GET /dns/status")) { + sendAPIResponse(*sock, type, UNAUTHORIZED); + ssend(*sock, "\"status\":\"unauthorized\"}"); + return; + } + + if(authResult == AUTH_NEW) { + sendAPIResponseWithCookie(*sock, type, OK, &session); + ssend(*sock, "\"status\":\"authorized\",\"session\":%ld}", session); + return; + } if(matchesEndpoint(client_message, "GET /stats/summary")) { diff --git a/structs.c b/structs.c index 9e3f6c1f..ab9b84da 100644 --- a/structs.c +++ b/structs.c @@ -9,6 +9,7 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" +#include "api.h" FTLFileNamesStruct FTLfiles = { "/etc/pihole/pihole-FTL.conf", @@ -118,6 +119,15 @@ void memory_check(int which) exit(EXIT_FAILURE); } break; + case AUTHDATA: + // Always called when we need one more entry, like wildcard + logg_struct_resize("authdata", authLength+1, 1); + authData = realloc(authData, (authLength+1) * sizeof(AuthData)); + + if(authData == NULL) { + logg("FATAL: Memory allocation failed! Exiting"); + exit(EXIT_FAILURE); + } default: /* That cannot happen */ break; From ae81270817d5a357a48911eef1cea690b63c1652 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 24 Dec 2017 17:04:55 -0500 Subject: [PATCH 091/168] Fix invalid for loop error Signed-off-by: Mcat12 --- api.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api.c b/api.c index 2b00ceb2..e015c46b 100644 --- a/api.c +++ b/api.c @@ -75,7 +75,8 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { return AUTH_UNAUTHORIZED; } - for(int i = 0; i < authLength; i++) { + int i; + for(i = 0; i < authLength; i++) { if(authData[i].valid && authData[i].session == *session) { auth = &authData[i]; time_t currentTime = time(NULL); @@ -118,7 +119,8 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { auth->session = random(); bool unique = true; - for(int i = 0; i < authLength; i++) { + int i; + for(i = 0; i < authLength; i++) { if(authData[i].session == auth->session) { unique = false; break; @@ -134,7 +136,8 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { // Add to auth storage bool found = false; - for(int i = 0; i < authLength; i++) { + int i; + for(i = 0; i < authLength; i++) { if(!authData[i].valid) { // Found an invalid auth we can reuse found = true; From cd2fd1180654df25088975539a7ddb36502fb4fd Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 25 Dec 2017 12:24:28 +0100 Subject: [PATCH 092/168] Make client IPs globally available in FTL (array of strings clientip[sockedidentifier]) for possible use in authentication Signed-off-by: DL6ER --- FTL.h | 4 ++++ request.c | 7 +++++++ socket.c | 20 +++++++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/FTL.h b/FTL.h index f5c9cc98..ac1b7f39 100644 --- a/FTL.h +++ b/FTL.h @@ -67,6 +67,9 @@ // Default -60 (one minute before a full hour) #define GCdelay (-60) +// How many client connection do we accept at once? +#define MAXCONNS 20 + // Static structs typedef struct { const char* conf; @@ -234,3 +237,4 @@ long int lastdbindex; bool travis; bool DBdeleteoldqueries; bool rereadgravity; +char * clientip[MAXCONNS]; diff --git a/request.c b/request.c index c229109b..36c4bd70 100644 --- a/request.c +++ b/request.c @@ -113,6 +113,13 @@ void process_socket_request(char *client_message, int *sock) if(debugclients) logg("Client wants to disconnect, ID: %i",*sock); + // Free memory containing IP address of this client + if(clientip[*sock] != NULL) + { + free(clientip[*sock]); + clientip[*sock] = NULL; + } + close(*sock); *sock = 0; } diff --git a/socket.c b/socket.c index 398fd441..5fded2c3 100644 --- a/socket.c +++ b/socket.c @@ -170,9 +170,16 @@ int listener(int sockfd) socklen_t clilen = sizeof(cli_addr); int clientsocket = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); - if(debugclients) - logg("Client connected: %s, ID: %i", inet_ntoa (cli_addr.sin_addr), clientsocket); - + if(clientsocket < MAXCONNS) + { + clientip[clientsocket] = strdup(inet_ntoa (cli_addr.sin_addr)); + if(debugclients) + logg("Client connected: %s, ID: %i", clientip[clientsocket], clientsocket); + } + else + { + return -1; + } return clientsocket; } @@ -249,6 +256,12 @@ void *socket_connection_handler_thread(void *socket_desc) close(sock); free(socket_desc); + if(clientip[sock] != NULL) + { + free(clientip[sock]); + clientip[sock] = NULL; + } + return 0; } @@ -274,6 +287,7 @@ void *socket_listening_thread(void *args) { // Look for new clients that want to connect int csck = listener(socketfd); + if(csck < 0) continue; // Allocate memory used to transport client socket ID to client listening thread newsock = calloc(1,sizeof(int)); From f15b9ed8acaa8c77d006ce3602d6e685209e2087 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 25 Dec 2017 17:41:04 -0500 Subject: [PATCH 093/168] Make sure the API handler ignores clients when over the limit Signed-off-by: Mcat12 --- socket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/socket.c b/socket.c index 5fded2c3..77f69ecf 100644 --- a/socket.c +++ b/socket.c @@ -172,7 +172,7 @@ int listener(int sockfd) if(clientsocket < MAXCONNS) { - clientip[clientsocket] = strdup(inet_ntoa (cli_addr.sin_addr)); + clientip[clientsocket] = strdup(inet_ntoa(cli_addr.sin_addr)); if(debugclients) logg("Client connected: %s, ID: %i", clientip[clientsocket], clientsocket); } @@ -437,6 +437,7 @@ void *api_listening_thread(void *args) { // Look for new clients that want to connect int csck = listener(apifd); + if(csck < 0) continue; // Allocate memory used to transport client socket ID to client listening thread newsock = calloc(1,sizeof(int)); From 636183b1b051a6a493ef75821ecd0bfc0b359e01 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 25 Dec 2017 18:05:53 -0500 Subject: [PATCH 094/168] Validate domain earlier in removeList Signed-off-by: Mcat12 --- api_dns.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/api_dns.c b/api_dns.c index f442326b..f7e93f65 100644 --- a/api_dns.c +++ b/api_dns.c @@ -175,6 +175,14 @@ void addList(int *sock, char type, char list_type, char *data) { void removeList(int *sock, char type, char list_type, char *client_message) { char *domain = strrchr(client_message, '/'); + // Validate domain + if(domain == NULL) { + // No domain found + sendAPIResponse(*sock, type, NOT_FOUND); + ssend(*sock, "\"status\":\"not_found\""); + return; + } + // Remove leading '/' domain++; @@ -207,15 +215,6 @@ void removeList(int *sock, char type, char list_type, char *client_message) { free(expected_route); - // Validate domain - - if(domain == NULL) { - // No domain found - sendAPIResponse(*sock, type, NOT_FOUND); - ssend(*sock, "\"status\":\"not_found\""); - return; - } - if(!isValidDomain(domain)) { // Invalid domain sendAPIResponse(*sock, type, BAD_REQUEST); From c9e40a4b417c52c8ea8f79b61c71c0a52d450f96 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 25 Dec 2017 23:33:49 -0500 Subject: [PATCH 095/168] Free client IP data after API handler runs and close rejected sockets Also added more debugclients logging. Signed-off-by: Mcat12 --- socket.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/socket.c b/socket.c index 77f69ecf..2679f5ac 100644 --- a/socket.c +++ b/socket.c @@ -170,16 +170,24 @@ int listener(int sockfd) socklen_t clilen = sizeof(cli_addr); int clientsocket = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); + char *ipAddr = inet_ntoa(cli_addr.sin_addr); + if(clientsocket < MAXCONNS) { - clientip[clientsocket] = strdup(inet_ntoa(cli_addr.sin_addr)); + clientip[clientsocket] = strdup(ipAddr); + if(debugclients) logg("Client connected: %s, ID: %i", clientip[clientsocket], clientsocket); } else { + if(debugclients) + logg("Client denied (at max capacity): %s, ID: %i", ipAddr, clientsocket); + + close(clientsocket); return -1; } + return clientsocket; } @@ -256,8 +264,7 @@ void *socket_connection_handler_thread(void *socket_desc) close(sock); free(socket_desc); - if(clientip[sock] != NULL) - { + if(clientip[sock] != NULL) { free(clientip[sock]); clientip[sock] = NULL; } @@ -412,6 +419,11 @@ void *api_connection_handler_thread(void *socket_desc) close(sock); free(socket_desc); + if(clientip[sock] != NULL) { + free(clientip[sock]); + clientip[sock] = NULL; + } + return 0; } From 6050aa5ad795f5812e22f3a946e0a0e4351eac01 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 25 Dec 2017 23:48:36 -0500 Subject: [PATCH 096/168] Add IP address into the authentication check Signed-off-by: Mcat12 --- api.c | 7 +++++-- api.h | 3 ++- request.c | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/api.c b/api.c index e015c46b..f9bd4cc0 100644 --- a/api.c +++ b/api.c @@ -57,7 +57,7 @@ void sendAPIResponseWithCookie(int sock, char type, char http_code, const long * } // 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) { +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; @@ -77,7 +77,8 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { int i; for(i = 0; i < authLength; i++) { - if(authData[i].valid && authData[i].session == *session) { + // 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); @@ -113,6 +114,7 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { auth = malloc(sizeof(AuthData)); auth->lastQueryTime = time(NULL); + auth->ip = strdup(clientip[sock]); // Find a unique session number while(true) { @@ -141,6 +143,7 @@ enum Auth authenticate(char *with_headers, char *payload, long *session) { if(!authData[i].valid) { // Found an invalid auth we can reuse found = true; + free(authData[i].ip); authData[i] = *auth; break; } diff --git a/api.h b/api.h index dfad1af1..5086830d 100644 --- a/api.h +++ b/api.h @@ -40,6 +40,7 @@ enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND, UNAUTHORIZED }; typedef struct { time_t lastQueryTime; long session; + char *ip; bool valid; } AuthData; AuthData *authData; @@ -47,7 +48,7 @@ int authLength; enum Auth { AUTH_UNAUTHORIZED, AUTH_PREVIOUS, AUTH_NEW }; // General API commands -enum Auth authenticate(char *with_headers, char *payload, long *session); +enum Auth authenticate(char *with_headers, char *payload, long *session, int sock); char* getPayload(char *http_message); void sendAPIResponse(int sock, char type, char http_code); void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session); diff --git a/request.c b/request.c index 36c4bd70..b4e909ea 100644 --- a/request.c +++ b/request.c @@ -155,7 +155,7 @@ void process_api_request(char *client_message, char *full_message, int *sock, bo char *data = getPayload(full_message); long session; - char authResult = authenticate(full_message, data, &session); + char authResult = authenticate(full_message, data, &session, *sock); if(authResult == AUTH_UNAUTHORIZED && !matchesEndpoint(client_message, "GET /stats/summary") && !matchesEndpoint(client_message, "GET /stats/overTime/graph") && !matchesEndpoint(client_message, "GET /dns/status")) { From f62f9f085c408ca005e61959d98831d414392d06 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 12:51:13 -0500 Subject: [PATCH 097/168] Disable HTTP tests as authorization is not gained in the tests yet Signed-off-by: Mcat12 --- test/test_suite.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 01b1e4dc..3d033e83 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -213,21 +213,21 @@ load 'libs/bats-support/load' [[ "${status}" -eq 0 ]] } -@test "API: Correct answer to top_domains request" { - run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" - echo "output: ${lines[@]}" - echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"example.com\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] - [[ "${status}" -eq 0 ]] -} +#@test "API: Correct answer to top_domains request" { +# run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" +# echo "output: ${lines[@]}" +# echo "curl exit code: ${status}" +# [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"example.com\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] +# [[ "${status}" -eq 0 ]] +#} -@test "API: Correct answer to top_ads request" { - run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" - echo "output: ${lines[@]}" - echo "curl exit code: ${status}" - [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"ads_blocked_today\":2}" ]] - [[ "${status}" -eq 0 ]] -} +#@test "API: Correct answer to top_ads request" { +# run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" +# echo "output: ${lines[@]}" +# echo "curl exit code: ${status}" +# [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"ads_blocked_today\":2}" ]] +# [[ "${status}" -eq 0 ]] +#} @test "Arguments check: Invalid option" { run bash -c './pihole-FTL abc' From 3d78cc3644b213138f4992092af04a389432d7ba Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 17:52:24 -0500 Subject: [PATCH 098/168] Fix tests Signed-off-by: Mcat12 --- api_stats.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api_stats.c b/api_stats.c index 1a169e88..ccc2c34f 100644 --- a/api_stats.c +++ b/api_stats.c @@ -845,12 +845,12 @@ void getAllQueries(char *client_message, int *sock, char type) if(!privacymode) { if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); else - ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); + ssend(*sock,"%i %s %s %s %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); } else - ssend(*sock,"%i %s %s hidden %i %i\n",queries[i].timestamp,type,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); + ssend(*sock,"%i %s %s hidden %i %i\n",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); } else { From 595ab259ca671d7f32bd1cedd6679da7ada729e4 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 20:16:50 -0500 Subject: [PATCH 099/168] Remove HTTP API specific code so it can be replaced with a new protocol Adjusted tests to fit the current lack of output on the unix socket. Added TELNET enum and used it in place of the old SOCKET to better fit with the rest of the code base. Signed-off-by: Mcat12 --- FTL.h | 4 +- Makefile | 4 +- api.c | 175 ----------------------- api.h | 33 ++--- api_dns.c | 40 +++--- api_stats.c | 346 ++++++++++++++++++++++----------------------- main.c | 9 -- request.c | 156 +------------------- routines.h | 5 +- socket.c | 192 ++----------------------- structs.c | 10 -- test/run.sh | 2 +- test/test_suite.sh | 68 ++------- 13 files changed, 228 insertions(+), 816 deletions(-) diff --git a/FTL.h b/FTL.h index beb327a4..348e012d 100644 --- a/FTL.h +++ b/FTL.h @@ -203,8 +203,8 @@ typedef struct { int querytypedata; } memoryStruct; -enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD, AUTHDATA }; -enum { SOCKET, API, APIH }; +enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; +enum { TELNET, SOCKET }; enum { WHITELIST, BLACKLIST, WILDLIST }; enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED, DNSSEC_UNKNOWN }; diff --git a/Makefile b/Makefile index 801fbf0c..efbb725f 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -DEPS = FTL.h routines.h api.h version.h cJSON.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o cJSON.o +DEPS = FTL.h routines.h api.h version.h +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') diff --git a/api.c b/api.c index f9bd4cc0..a14b1a53 100644 --- a/api.c +++ b/api.c @@ -10,181 +10,6 @@ #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; diff --git a/api.h b/api.h index 7dc4b709..3266a2ed 100644 --- a/api.h +++ b/api.h @@ -8,50 +8,33 @@ * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ -// Endpoints under /stats/ +// Statistic methods void getStats(int *sock, char type); void getOverTime(int *sock, char type); void getTopDomains (char *client_message, int *sock, char type); void getTopClients(char *client_message, int *sock, char type); void getForwardDestinations(char *client_message, int *sock, char type); - void getQueryTypes(int *sock, char type); void getAllQueries(char *client_message, int *sock, char type); void getRecentBlocked(char *client_message, int *sock, char type); -void getMemoryUsage(int *sock, char type); void getForwardDestinationsOverTime(int *sock, char type); -void getClientID(int *sock, char type); void getQueryTypesOverTime(int *sock, char type); -void getVersion(int *sock, char type); -void getDBstats(int *sock, char type); void getClientsOverTime(int *sock); void getClientNames(int *sock); + +// FTL methods +void getMemoryUsage(int *sock, char type); +void getClientID(int *sock, char type); +void getVersion(int *sock, char type); +void getDBstats(int *sock, char type); void getUnknownQueries(int *sock); -// Endpoints under /dns/ +// DNS methods void getList(int *sock, char type, char list_type); void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); void getPiholeStatus(int *sock, char type); -// HTTP Response Codes -enum { OK, BAD_REQUEST, INTERNAL_ERROR, NOT_FOUND, UNAUTHORIZED }; - -// Authentication -typedef struct { - time_t lastQueryTime; - long session; - char *ip; - bool valid; -} AuthData; -AuthData *authData; -int authLength; -enum Auth { AUTH_UNAUTHORIZED, AUTH_PREVIOUS, AUTH_NEW }; - // General API commands -enum Auth authenticate(char *with_headers, char *payload, long *session, int sock); -char* getPayload(char *http_message); -void sendAPIResponse(int sock, char type, char http_code); -void sendAPIResponseWithCookie(int sock, char type, char http_code, const long *session); bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); diff --git a/api_dns.c b/api_dns.c index f7e93f65..6189958e 100644 --- a/api_dns.c +++ b/api_dns.c @@ -32,7 +32,7 @@ void getList(int *sock, char type, char list_type) { name = "wildlist"; } - sendAPIResponse(*sock, type, OK); +// sendAPIResponse(*sock, type, OK); ssend(*sock, "\"%s\":[", name); if((fp = fopen(file, "r")) != NULL) @@ -111,28 +111,28 @@ void getList(int *sock, char type, char list_type) { void getPiholeStatus(int *sock, char type) { int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); - sendAPIResponse(*sock, type, OK); +// sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); } void addList(int *sock, char type, char list_type, char *data) { - cJSON *input_root = cJSON_Parse(data); - cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); +// cJSON *input_root = cJSON_Parse(data); +// cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); char *domain; // Validate domain - if(!cJSON_IsString(domain_json)) { - // No domain found - sendAPIResponse(*sock, type, BAD_REQUEST); - ssend(*sock, "\"status\":\"no_domain\""); - return; - } +// if(!cJSON_IsString(domain_json)) { +// // No domain found +// sendAPIResponse(*sock, type, BAD_REQUEST); +// ssend(*sock, "\"status\":\"no_domain\""); +// return; +// } - domain = domain_json->valuestring; +// domain = domain_json->valuestring; if(!isValidDomain(domain)) { // Invalid domain - sendAPIResponse(*sock, type, BAD_REQUEST); +// sendAPIResponse(*sock, type, BAD_REQUEST); ssend(*sock, "\"status\":\"invalid_domain\""); return; } @@ -160,16 +160,16 @@ void addList(int *sock, char type, char list_type, char *data) { if(return_code == 0) { // Successfully added to list - sendAPIResponse(*sock, type, OK); +// sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":\"success\""); } else { // Failed to add to list - sendAPIResponse(*sock, type, INTERNAL_ERROR); +// sendAPIResponse(*sock, type, INTERNAL_ERROR); ssend(*sock, "\"status\":\"unknown_error\""); } - cJSON_Delete(input_root); +// cJSON_Delete(input_root); } void removeList(int *sock, char type, char list_type, char *client_message) { @@ -178,7 +178,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) { // Validate domain if(domain == NULL) { // No domain found - sendAPIResponse(*sock, type, NOT_FOUND); +// sendAPIResponse(*sock, type, NOT_FOUND); ssend(*sock, "\"status\":\"not_found\""); return; } @@ -208,7 +208,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) { if(!strstr(client_message, expected_route)) { // Invalid route free(expected_route); - sendAPIResponse(*sock, type, NOT_FOUND); +// sendAPIResponse(*sock, type, NOT_FOUND); ssend(*sock, "\"status\":\"not_found\""); return; } @@ -217,7 +217,7 @@ void removeList(int *sock, char type, char list_type, char *client_message) { if(!isValidDomain(domain)) { // Invalid domain - sendAPIResponse(*sock, type, BAD_REQUEST); +// sendAPIResponse(*sock, type, BAD_REQUEST); ssend(*sock, "\"status\":\"invalid_domain\""); return; } @@ -241,12 +241,12 @@ void removeList(int *sock, char type, char list_type, char *client_message) { if(return_code == 0) { // Successfully removed from list - sendAPIResponse(*sock, type, OK); +// sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":\"success\""); } else { // Failed to remove from list - sendAPIResponse(*sock, type, INTERNAL_ERROR); +// sendAPIResponse(*sock, type, INTERNAL_ERROR); ssend(*sock, "\"status\":\"unknown_error\""); } } diff --git a/api_stats.c b/api_stats.c index ccc2c34f..497785ea 100644 --- a/api_stats.c +++ b/api_stats.c @@ -59,7 +59,7 @@ void getStats(int *sock, char type) switch(blockingstatus) { case 0: // Blocking disabled - if(type == SOCKET) + if(type == TELNET) strncpy(domains_blocked, "N/A", 4); else strncpy(domains_blocked, "\"N/A\"", 6); @@ -85,7 +85,7 @@ void getStats(int *sock, char type) activeclients++; } - if(type == SOCKET) { + if(type == TELNET) { ssend(*sock, "domains_being_blocked %s\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", domains_blocked, total, blocked, percentage); ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", @@ -96,30 +96,30 @@ void getStats(int *sock, char type) } else { - sendAPIResponse(*sock, type, OK); - ssend( - *sock, - "\"domains_being_blocked\":%s," - "\"dns_queries_today\":%i," - "\"ads_blocked_today\":%i," - "\"ads_percentage_today\":%.4f," - "\"unique_domains\":%i," - "\"queries_forwarded\":%i," - "\"queries_cached\":%i," - "\"clients_ever_seen\":%i," - "\"unique_clients\":%i," - "\"status\":\"%s\"", - domains_blocked, - total, - blocked, - percentage, - counters.domains, - counters.forwardedqueries, - counters.cached, - counters.clients, - activeclients, - status - ); +// sendAPIResponse(*sock, type, OK); +// ssend( +// *sock, +// "\"domains_being_blocked\":%s," +// "\"dns_queries_today\":%i," +// "\"ads_blocked_today\":%i," +// "\"ads_percentage_today\":%.4f," +// "\"unique_domains\":%i," +// "\"queries_forwarded\":%i," +// "\"queries_cached\":%i," +// "\"clients_ever_seen\":%i," +// "\"unique_clients\":%i," +// "\"status\":\"%s\"", +// domains_blocked, +// total, +// blocked, +// percentage, +// counters.domains, +// counters.forwardedqueries, +// counters.cached, +// counters.clients, +// activeclients, +// status +// ); } if(debugclients) @@ -142,7 +142,7 @@ void getOverTime(int *sock, char type) } // Send data in socket format if requested - if(type == SOCKET) + if(type == TELNET) { for(i = j; i < counters.overTime; i++) { @@ -151,25 +151,25 @@ void getOverTime(int *sock, char type) } else { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type, OK); - ssend(*sock,"\"domains_over_time\":{"); - - // Send "domains_over_time" data - for(i = j; i < counters.overTime; i++) - { - if(i != j) ssend(*sock, ","); - ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); - } - ssend(*sock,"},\"ads_over_time\":{"); - - // Send "ads_over_time" data - for(i = j; i < counters.overTime; i++) - { - if(i != j) ssend(*sock, ","); - ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); - } - ssend(*sock,"}"); +// // First send header with unspecified content-length outside of the for-loop +// sendAPIResponse(*sock, type, OK); +// ssend(*sock,"\"domains_over_time\":{"); +// +// // Send "domains_over_time" data +// for(i = j; i < counters.overTime; i++) +// { +// if(i != j) ssend(*sock, ","); +// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); +// } +// ssend(*sock,"},\"ads_over_time\":{"); +// +// // Send "ads_over_time" data +// for(i = j; i < counters.overTime; i++) +// { +// if(i != j) ssend(*sock, ","); +// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); +// } +// ssend(*sock,"}"); } if(debugclients) @@ -181,7 +181,7 @@ void getTopDomains(char *client_message, int *sock, char type) int i, temparray[counters.domains][2], count=10, num; bool blocked, audit = false, desc = false; - if(type == SOCKET) + if(type == TELNET) blocked = command(client_message, ">top-ads"); else blocked = command(client_message, "/top_ads"); @@ -191,9 +191,9 @@ void getTopDomains(char *client_message, int *sock, char type) return; // Match both top-domains and top-ads - // SOCKET: >top-domains (15) + // TELNET: >top-domains (15) // API: /stats/top_domains?limit=15 - if(type == SOCKET) + if(type == TELNET) { if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { @@ -215,19 +215,19 @@ void getTopDomains(char *client_message, int *sock, char type) } // Apply Audit Log filtering? - // SOCKET: >top-domains for audit + // TELNET: >top-domains for audit // API: /stats/top_domains?audit - if(type == SOCKET && command(client_message, " for audit")) + if(type == TELNET && command(client_message, " for audit")) audit = true; - else if(type != SOCKET && command(client_message, "audit")) + else if(type != TELNET && command(client_message, "audit")) audit = true; // Sort in descending order? - // SOCKET: >top-domains desc + // TELNET: >top-domains desc // API: /stats/top_domains?order=desc - if(type == SOCKET && command(client_message, " desc")) + if(type == TELNET && command(client_message, " desc")) desc = true; - else if(type != SOCKET && command(client_message, "order=desc")) + else if(type != TELNET && command(client_message, "order=desc")) desc = true; for(i=0; i < counters.domains; i++) @@ -279,15 +279,15 @@ void getTopDomains(char *client_message, int *sock, char type) } } - if(type != SOCKET) + if(type != TELNET) { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type, OK); - - if(blocked) - ssend(*sock, "\"top_ads\":{"); - else - ssend(*sock, "\"top_domains\":{"); +// // First send header with unspecified content-length outside of the for-loop +// sendAPIResponse(*sock, type, OK); +// +// if(blocked) +// ssend(*sock, "\"top_ads\":{"); +// else +// ssend(*sock, "\"top_domains\":{"); } int skip = 0; bool first = true; @@ -316,7 +316,7 @@ void getTopDomains(char *client_message, int *sock, char type) if(blocked && showblocked && domains[j].blockedcount > 0) { - if(type == SOCKET) + if(type == TELNET) { if(audit && domains[j].wildcard) ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); @@ -325,32 +325,32 @@ void getTopDomains(char *client_message, int *sock, char type) } else { - if(!first) ssend(*sock,","); - first = false; - ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); +// if(!first) ssend(*sock,","); +// first = false; +// ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); } } else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) { - if(type == SOCKET) + if(type == TELNET) { ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); } else { - if(!first) ssend(*sock,","); - first = false; - ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); +// if(!first) ssend(*sock,","); +// first = false; +// ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); } } } - if(type != SOCKET) + if(type != TELNET) { - if(blocked) - ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); - else - ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); +// if(blocked) +// ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); +// else +// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); } if(excludedomains != NULL) @@ -370,9 +370,9 @@ void getTopClients(char *client_message, int *sock, char type) int i, temparray[counters.clients][2], count=10, num; // Match both top-domains and top-ads - // SOCKET: >top-clients (15) + // TELNET: >top-clients (15) // API: /stats/top_clients?limit=15 - if(type == SOCKET) + if(type == TELNET) { if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { @@ -397,7 +397,7 @@ void getTopClients(char *client_message, int *sock, char type) // This option can be combined with existing options, // i.e. both >top-clients withzero" and ">top-clients withzero (123)" are valid bool includezeroclients = false; - if(type == SOCKET) { + if(type == TELNET) { if(command(client_message, " withzero")) { includezeroclients = true; } @@ -425,11 +425,11 @@ void getTopClients(char *client_message, int *sock, char type) logg("Excluding %i clients from being displayed", setupVarsElements); } - if(type != SOCKET) + if(type != TELNET) { - // First send header with unspecified content-length outside of the for-loop - sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"top_clients\":{"); +// // First send header with unspecified content-length outside of the for-loop +// sendAPIResponse(*sock, type, OK); +// ssend(*sock, "\"top_clients\":{"); } int skip = 0; bool first = true; @@ -454,24 +454,24 @@ void getTopClients(char *client_message, int *sock, char type) // - "withzero" option is set, and/or // - the client made at least one query within the most recent 24 hours if(includezeroclients || clients[j].count > 0) { - if(type == SOCKET) + if(type == TELNET) { ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); } else { - if(!first) ssend(*sock,","); - first = false; - if(strlen(clients[j].name) > 0) - ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); - else - ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); +// if(!first) ssend(*sock,","); +// first = false; +// if(strlen(clients[j].name) > 0) +// ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); +// else +// ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); } } } - if(type != SOCKET) - ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); +// if(type != TELNET) +// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); if(excludeclients != NULL) clearSetupVarsArray(); @@ -486,7 +486,7 @@ void getForwardDestinations(char *client_message, int *sock, char type) bool allocated = false, first = true, sort = true; int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; - if(type == SOCKET && command(client_message, "unsorted")) + if(type == TELNET && command(client_message, "unsorted")) sort = false; else if(strstr(client_message, "unsorted")) sort = false; @@ -516,11 +516,11 @@ void getForwardDestinations(char *client_message, int *sock, char type) totalqueries = counters.forwardedqueries + counters.cached + counters.blocked; // Send HTTP headers with unknown content length - sendAPIResponse(*sock, type, OK); +// sendAPIResponse(*sock, type, OK); // Send initial JSON output - if(type != SOCKET) - ssend(*sock, "\"forward_destinations\":{"); +// if(type != TELNET) +// ssend(*sock, "\"forward_destinations\":{"); // Loop over available forward destinations for(i=0; i < min(counters.forwarded+1, 10); i++) @@ -584,19 +584,19 @@ void getForwardDestinations(char *client_message, int *sock, char type) // Send data if count > 0 if(percentage > 0.0) { - if(type == SOCKET) + if(type == TELNET) { ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); } else { - if(!first) ssend(*sock, ","); - first = false; - - if(strlen(name) > 0) - ssend(*sock, "\"%s|%s\":%.2f", name, ip, percentage); - else - ssend(*sock, "\"%s\":%.2f", ip, percentage); +// if(!first) ssend(*sock, ","); +// first = false; +// +// if(strlen(name) > 0) +// ssend(*sock, "\"%s|%s\":%.2f", name, ip, percentage); +// else +// ssend(*sock, "\"%s\":%.2f", ip, percentage); } } @@ -608,8 +608,8 @@ void getForwardDestinations(char *client_message, int *sock, char type) } } - if(type != SOCKET) - ssend(*sock, "}"); +// if(type != TELNET) +// ssend(*sock, "}"); if(debugclients) logg("Sent forward destination data to client, ID: %i", *sock); @@ -627,11 +627,11 @@ void getQueryTypes(int *sock, char type) percentageIPv6 = 1e2*counters.IPv6/total; } - if(type == SOCKET) + if(type == TELNET) ssend(*sock,"A (IPv4): %.2f\nAAAA (IPv6): %.2f\n", percentageIPv4, percentageIPv6); else { - sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"query_types\":{\"A (IPv4)\":%.2f,\"AAAA (IPv6)\":%.2f}", percentageIPv4, percentageIPv6); +// sendAPIResponse(*sock, type, OK); +// ssend(*sock, "\"query_types\":{\"A (IPv4)\":%.2f,\"AAAA (IPv6)\":%.2f}", percentageIPv4, percentageIPv6); } if(debugclients) @@ -655,7 +655,7 @@ void getAllQueries(char *client_message, int *sock, char type) char *clientname = NULL; bool filterclientname = false; - if(type == SOCKET) + if(type == TELNET) { // Time filtering? if(command(client_message, ">getallqueries-time")) @@ -724,7 +724,7 @@ void getAllQueries(char *client_message, int *sock, char type) int ibeg = 0, num; // Test for integer that specifies number of entries to be shown - if(type == SOCKET) + if(type == TELNET) { if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { @@ -794,11 +794,11 @@ void getAllQueries(char *client_message, int *sock, char type) logg("Privacy mode enabled"); } - if(type != SOCKET) - { - sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"history\":["); - } +// if(type != TELNET) +// { +// sendAPIResponse(*sock, type, OK); +// ssend(*sock, "\"history\":["); +// } int i; bool first = true; for(i=ibeg; i < counters.queries; i++) @@ -840,7 +840,7 @@ void getAllQueries(char *client_message, int *sock, char type) continue; } - if(type == SOCKET) + if(type == TELNET) { if(!privacymode) { @@ -854,24 +854,24 @@ void getAllQueries(char *client_message, int *sock, char type) } else { - // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1], - if(!first) ssend(*sock, ","); - first = false; - - if(!privacymode) - { - if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); - else - ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); - } - else - ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); +// // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1], +// if(!first) ssend(*sock, ","); +// first = false; +// +// if(!privacymode) +// { +// if(strlen(clients[queries[i].clientID].name) > 0) +// ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); +// else +// ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); +// } +// else +// ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); } } - if(type != SOCKET) - ssend(*sock, "]"); +// if(type != TELNET) +// ssend(*sock, "]"); // Free allocated memory if(filterclientname) @@ -893,7 +893,7 @@ void getRecentBlocked(char *client_message, int *sock, char type) return; // Test for integer that specifies number of entries to be shown - if(type == SOCKET) + if(type == TELNET) { if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { @@ -916,11 +916,11 @@ void getRecentBlocked(char *client_message, int *sock, char type) } } - if(type != SOCKET) - { - sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"recent_blocked\":["); - } +// if(type != TELNET) +// { +// sendAPIResponse(*sock, type, OK); +// ssend(*sock, "\"recent_blocked\":["); +// } // Find most recent query with either status 1 (blocked) // or status 4 (wildcard blocked) @@ -934,15 +934,15 @@ void getRecentBlocked(char *client_message, int *sock, char type) if(queries[i].status == 1 || queries[i].status == 4) { found++; - if(type == SOCKET) + if(type == TELNET) { ssend(*sock,"%s\n", domains[queries[i].domainID].domain); } else { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); +// if(!first) ssend(*sock, ","); +// first = false; +// ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); } } @@ -950,11 +950,11 @@ void getRecentBlocked(char *client_message, int *sock, char type) break; } - if(type != SOCKET) - ssend(*sock, "]"); +// if(type != TELNET) +// ssend(*sock, "]"); } -// only available via SOCKET +// only available via TELNET void getMemoryUsage(int *sock, char type) { unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); @@ -994,11 +994,11 @@ void getForwardDestinationsOverTime(int *sock, char type) } } - if(type != SOCKET) - { - sendAPIResponse(*sock, type, OK); - ssend(*sock,"\"over_time\":{"); - } +// if(type != TELNET) +// { +// sendAPIResponse(*sock, type, OK); +// ssend(*sock,"\"over_time\":{"); +// } if(sendit > -1) { @@ -1008,15 +1008,15 @@ void getForwardDestinationsOverTime(int *sock, char type) double percentage; validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(type == SOCKET) + if(type == TELNET) { ssend(*sock, "%i", overTime[i].timestamp); } else { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%i\":[", overTime[i].timestamp); +// if(!first) ssend(*sock, ","); +// first = false; +// ssend(*sock, "\"%i\":[", overTime[i].timestamp); } int j, forwardedsum = 0; @@ -1067,7 +1067,7 @@ void getForwardDestinationsOverTime(int *sock, char type) else percentage = 0.0; - if(type == SOCKET) + if(type == TELNET) ssend(*sock, " %.2f", percentage); else ssend(*sock, "%.2f,", percentage); @@ -1080,19 +1080,19 @@ void getForwardDestinationsOverTime(int *sock, char type) else percentage = 0.0; - if(type == SOCKET) + if(type == TELNET) ssend(*sock, " %.2f\n", percentage); else ssend(*sock, "%.2f]", percentage); } } - if(type != SOCKET) - { - ssend(*sock,"},"); - // Manually set API -> Don't send header a second time - getForwardDestinations(">forward-dest unsorted", sock, API); - } +// if(type != TELNET) +// { +// ssend(*sock,"},"); +// // Manually set API -> Don't send header a second time +// getForwardDestinations(">forward-dest unsorted", sock, SOCKET); +// } if(debugclients) logg("Sent overTime forwarded data to client, ID: %i", *sock); @@ -1120,11 +1120,11 @@ void getQueryTypesOverTime(int *sock, char type) } } - if(type != SOCKET) - { - sendAPIResponse(*sock, type, OK); - ssend(*sock,"\"query_types\":{"); - } +// if(type != TELNET) +// { +// sendAPIResponse(*sock, type, OK); +// ssend(*sock,"\"query_types\":{"); +// } if(sendit > -1) { @@ -1141,18 +1141,18 @@ void getQueryTypesOverTime(int *sock, char type) percentageIPv6 = 1e2*overTime[i].querytypedata[1] / sum; } - if(type == SOCKET) + if(type == TELNET) ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6); else { - if(!first) ssend(*sock, ","); - first = false; - ssend(*sock, "\"%i\":[%.2f,%.2f]", overTime[i].timestamp, percentageIPv4, percentageIPv6); +// if(!first) ssend(*sock, ","); +// first = false; +// ssend(*sock, "\"%i\":[%.2f,%.2f]", overTime[i].timestamp, percentageIPv4, percentageIPv6); } } } - if(type != SOCKET) - ssend(*sock,"}"); +// if(type != TELNET) +// ssend(*sock,"}"); if(debugclients) logg("Sent overTime query types data to client, ID: %i", *sock); diff --git a/main.c b/main.c index 70c92f36..39922238 100644 --- a/main.c +++ b/main.c @@ -89,14 +89,6 @@ int main (int argc, char* argv[]) { } sleepms(100); - // Start API thread - pthread_t api_listenthread; - if(pthread_create( &api_listenthread, &attr, api_listening_thread, NULL ) != 0) - { - logg("Unable to open API listening thread. Exiting..."); - killed = 1; - } - while(!killed) { sleepms(100); @@ -176,7 +168,6 @@ int main (int argc, char* argv[]) { pthread_cancel(socket_listenthread); close_telnet_socket(); close_unix_socket(); - close_api_socket(); removepid(); logg("########## FTL terminated! ##########"); return 1; diff --git a/request.c b/request.c index 959d066c..1f7d1e27 100644 --- a/request.c +++ b/request.c @@ -11,13 +11,12 @@ #include "FTL.h" #include "api.h" -void process_socket_request(char *client_message, int *sock) +void process_request(char *client_message, int *sock, char type) { char EOT[2]; EOT[0] = 0x04; EOT[1] = 0x00; bool processed = false; - char type = SOCKET; if(command(client_message, ">stats")) { @@ -149,159 +148,6 @@ void process_socket_request(char *client_message, int *sock) } } -void process_api_request(char *client_message, char *full_message, int *sock, bool header) -{ - char type; - if(header) - type = APIH; - else - type = API; - - char *data = getPayload(full_message); - long session; - - char authResult = authenticate(full_message, data, &session, *sock); - if(authResult == AUTH_UNAUTHORIZED && !matchesEndpoint(client_message, "GET /stats/summary") - && !matchesEndpoint(client_message, "GET /stats/overTime/graph") - && !matchesEndpoint(client_message, "GET /dns/status")) { - sendAPIResponse(*sock, type, UNAUTHORIZED); - ssend(*sock, "\"status\":\"unauthorized\"}"); - return; - } - - if(authResult == AUTH_NEW) { - sendAPIResponseWithCookie(*sock, type, OK, &session); - ssend(*sock, "\"status\":\"authorized\",\"session\":%ld}", session); - return; - } - - if(matchesEndpoint(client_message, "GET /stats/summary")) - { - getStats(sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/overTime/graph")) - { - getOverTime(sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/top_domains") || matchesEndpoint(client_message, "GET /stats/top_ads")) - { - getTopDomains(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/top_clients")) - { - getTopClients(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/forward_dest") || matchesEndpoint(client_message, "GET /stats/forward_destinations")) - { - getForwardDestinations(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/dashboard")) - { - getStats(sock, type); - type = API; - ssend(*sock, ","); - getOverTime(sock, type); - ssend(*sock, ","); - getTopDomains(client_message, sock, type); - ssend(*sock, ","); - getTopClients(client_message, sock, type); - ssend(*sock, ","); - getForwardDestinations(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/query_types")) - { - getQueryTypes(sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/history")) - { - getAllQueries(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/recent_blocked")) - { - getRecentBlocked(client_message, sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/overTime/forward_dest")) - { - getForwardDestinationsOverTime(sock, type); - } - else if(matchesEndpoint(client_message, "GET /stats/overTime/query_types")) - { - getQueryTypesOverTime(sock, type); - } - else if(matchesEndpoint(client_message, "GET /dns/whitelist")) - { - getList(sock, type, WHITELIST); - } - else if(matchesEndpoint(client_message, "POST /dns/whitelist")) - { - addList(sock, type, WHITELIST, data); - } - else if(matchesRegex("DELETE \\/dns\\/whitelist\\/[^\\/]*$", client_message)) - { - removeList(sock, type, WHITELIST, client_message); - } - else if(matchesEndpoint(client_message, "GET /dns/blacklist")) - { - getList(sock, type, BLACKLIST); - } - else if(matchesEndpoint(client_message, "POST /dns/blacklist")) - { - addList(sock, type, BLACKLIST, data); - } - else if(matchesRegex("DELETE \\/dns\\/blacklist\\/[^\\/]*$", client_message)) - { - removeList(sock, type, BLACKLIST, client_message); - } - else if(matchesEndpoint(client_message, "GET /dns/wildlist")) - { - getList(sock, type, WILDLIST); - } - else if(matchesEndpoint(client_message, "POST /dns/wildlist")) - { - addList(sock, type, WILDLIST, data); - } - else if(matchesRegex("DELETE \\/dns\\/wildlist\\/[^\\/]*$", client_message)) - { - removeList(sock, type, WILDLIST, client_message); - } - else if(matchesEndpoint(client_message, "GET /dns/status")) - { - getPiholeStatus(sock, type); - } - else if(header) - { - sendAPIResponse(*sock, type, NOT_FOUND); - ssend(*sock, "\"status\":\"not_found\""); - } - - ssend(*sock, "}"); -} - bool command(char *client_message, const char* cmd) { return strstr(client_message, cmd) != NULL; } - -bool matchesEndpoint(char *client_message, const char *cmd) { - char *get_params_start = strstr(client_message, "?"); - bool result; - - // Check if there are GET parameters to ignore - if(get_params_start != NULL) { - char without_get_params[256]; - - // Check to make sure we don't overflow the buffer - if(strlen(cmd)+1 > sizeof(without_get_params) / sizeof(char)) - return false; - - size_t msg_len = get_params_start - client_message; - - strncpy(without_get_params, client_message, msg_len); - without_get_params[msg_len] = 0; - - result = strcmp(without_get_params, cmd) == 0; - } - else - result = strcmp(client_message, cmd) == 0; - - return result; -} diff --git a/routines.h b/routines.h index 88b53940..44db3760 100644 --- a/routines.h +++ b/routines.h @@ -40,15 +40,12 @@ void memory_check(int which); void close_telnet_socket(void); void close_unix_socket(void); -void close_api_socket(void); void seom(int sock); void ssend(int sock, const char *format, ...); void *telnet_listening_thread(void *args); void *socket_listening_thread(void *args); -void *api_listening_thread(void *args); -void process_socket_request(char *client_message, int *sock); -void process_api_request(char *client_message, char *full_message, int *sock, bool header); +void process_request(char *client_message, int *sock, char type); bool command(char *client_message, const char* cmd); bool matchesEndpoint(char *client_message, const char *cmd); diff --git a/socket.c b/socket.c index bea63b8d..a119efae 100644 --- a/socket.c +++ b/socket.c @@ -21,7 +21,7 @@ #define BACKLOG 5 // File descriptors -int telnetfd, socketfd, apifd; +int telnetfd, socketfd; void saveport(int port) { @@ -62,7 +62,7 @@ void bind_to_telnet_port(char type, int *socketdescriptor) memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; - if(config.socket_listenlocal && type == SOCKET) + if(config.socket_listenlocal) serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); else serv_addr.sin_addr.s_addr = INADDR_ANY; @@ -77,12 +77,9 @@ void bind_to_telnet_port(char type, int *socketdescriptor) switch(type) { - case SOCKET: + case TELNET: port_init = 4711; break; - case API: - port_init = 4747; - break; default: logg("Incompatible socket type %i", (int)type); exit(EXIT_FAILURE); @@ -110,7 +107,7 @@ void bind_to_telnet_port(char type, int *socketdescriptor) exit(EXIT_FAILURE); } - if(type == SOCKET) + if(type == TELNET) saveport(port); // The listen system call allows the process to listen on the socket for connections @@ -119,18 +116,8 @@ void bind_to_telnet_port(char type, int *socketdescriptor) logg("Error on listening"); exit(EXIT_FAILURE); } - switch(type) - { - case SOCKET: - logg("Listening on port %i for incoming socket connections", port); - break; - case API: - logg("Listening on port %i for incoming API connections", port); - break; - default: - /* That cannot happen */ - break; - } + + logg("Listening on port %i for incoming telnet connections", port); } @@ -243,11 +230,6 @@ void close_unix_socket(void) close(socketfd); } -void close_api_socket(void) -{ - close(apifd); -} - void *telnet_connection_handler_thread(void *socket_desc) { //Get the socket descriptor @@ -276,7 +258,7 @@ void *telnet_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_socket_request(message, &sock); + process_request(message, &sock, TELNET); free(message); // Release thread lock @@ -334,7 +316,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_socket_request(message, &sock); + process_request(message, &sock, SOCKET); free(message); // Release thread lock @@ -381,7 +363,7 @@ void *telnet_listening_thread(void *args) prctl(PR_SET_NAME,"telnet listener",0,0,0); // Initialize sockets only after initial log parsing in listenting_thread - bind_to_telnet_port(SOCKET, &telnetfd); + bind_to_telnet_port(TELNET, &telnetfd); // Listen as long as FTL is not killed while(!killed) @@ -443,159 +425,3 @@ void *socket_listening_thread(void *args) } return 0; } - - -void *api_connection_handler_thread(void *socket_desc) -{ - //Get the socket descriptor - int sock = *(int*)socket_desc; - // Store copy only for displaying the debug messages - int sockID = sock; - char client_message[SOCKETBUFFERLEN] = ""; - - // Set thread name - char threadname[16]; - sprintf(threadname,"api-%i",sockID); - prctl(PR_SET_NAME,threadname,0,0,0); - - //Receive from client - if(recv(sock, client_message, SOCKETBUFFERLEN-1, 0) > 0) - { - char *message = calloc(strlen(client_message)+1,sizeof(char)); - strcpy(message, client_message); - - // Clear client message receive buffer - memset(client_message, 0, sizeof client_message); - - if(debug) - logg("Received API request: \n%s", message); - - if(strncmp(message, "GET ", 4) == 0 || strncmp(message, "POST ", 5) == 0 || strncmp(message, "DELETE ", 7) == 0) - { - // HTTP requests can be simple or full. - // A simple request contains one line only, and looks like this: - // GET /index.html - // A full request can contain more than one line and may look like this: - // GET /index.html HTTP/1.1 - // User-Agent: Wget/1.16 (linux-gnueabihf) - // Accept: */* - // Host: 127.0.0.1:4747 - // Connection: Keep-Alive - bool header = false; - - // Extract requested URL including arguments - const char *p2; - if(strstr(message, "HTTP/") != NULL) - { - // Output HTTP response headers only if we have a full request - header = true; - // End of request = "HTTP/" - p2 = strstr(message, " HTTP/"); - } - else - { - // End of requst = end of first line - p2 = strstr(message, "\n"); - } - if(p2 != NULL) - { - size_t len = p2 - message; - char *request = calloc(len+1, sizeof(char)); - strncpy(request, message, len); - request[len] = '\0'; - - // Are we asked for a favicon? - if(strstr(request, "/favicon.ico") != NULL) - ssend(sock, "HTTP/1.0 404 Not Found\nServer: FTL\n\n"); - else - { - enable_thread_lock(threadname); - process_api_request(request, message, &sock, header); - disable_thread_lock(threadname); - } - - // Free allocated memory - free(request); - } - else - { - logg("API received malformated request: \"%s\"", message); - } - } - else if(strncmp(message, "OPTIONS ", 8) == 0) - { - // OPTIONS request: CORS preflight - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\nAccess-Control-Allow-Origin: *\n" - "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\n" - "Access-Control-Allow-Headers: Content-Type\n\n"); - } - else if(strncmp(message, "HEAD ", 5) == 0) - { - // HEAD request: We do not send any content at all - ssend(sock, "HTTP/1.0 200 OK\nServer: FTL\n\n"); - } - else - { - if(debug) - logg("API received something strange"); - } - - // Close connection to show that we reached the end of the transmission - close(sock); - sock = 0; - - // Free allocated memory - free(message); - } - - //Free the socket pointer - if(sock != 0) - close(sock); - free(socket_desc); - - if(clientip[sock] != NULL) { - free(clientip[sock]); - clientip[sock] = NULL; - } - - return 0; -} - -void *api_listening_thread(void *args) -{ - int *newsock; - // We will use the attributes object later to start all threads in detached mode - pthread_attr_t attr; - // Initialize thread attributes object with default attribute values - pthread_attr_init(&attr); - // When a detached thread terminates, its resources are automatically released back to - // the system without the need for another thread to join with the terminated thread - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - // Set thread name - prctl(PR_SET_NAME,"API listener",0,0,0); - - // Initialize sockets only after initial log parsing in listening_thread - bind_to_telnet_port(API, &apifd); - - // Listen as long as FTL is not killed - while(!killed) - { - // Look for new clients that want to connect - int csck = listener(apifd); - if(csck < 0) continue; - - // Allocate memory used to transport client socket ID to client listening thread - newsock = calloc(1,sizeof(int)); - *newsock = csck; - - pthread_t api_connection_thread; - // Create a new thread - if(pthread_create( &api_connection_thread, &attr, api_connection_handler_thread, (void*) newsock ) != 0) - { - // Log the error code description - logg("WARNING: Unable to open client API thread, error: %s", strerror(errno)); - } - } - return 0; -} diff --git a/structs.c b/structs.c index f01237e0..7524b058 100644 --- a/structs.c +++ b/structs.c @@ -9,7 +9,6 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" -#include "api.h" FTLFileNamesStruct FTLfiles = { "/etc/pihole/pihole-FTL.conf", @@ -120,15 +119,6 @@ void memory_check(int which) exit(EXIT_FAILURE); } break; - case AUTHDATA: - // Always called when we need one more entry, like wildcard - logg_struct_resize("authdata", authLength+1, 1); - authData = realloc(authData, (authLength+1) * sizeof(AuthData)); - - if(authData == NULL) { - logg("FATAL: Memory allocation failed! Exiting"); - exit(EXIT_FAILURE); - } default: /* That cannot happen */ break; diff --git a/test/run.sh b/test/run.sh index 5aaf2508..e03fc3f0 100755 --- a/test/run.sh +++ b/test/run.sh @@ -60,7 +60,7 @@ git submodule add https://github.com/ztombol/bats-support test/libs/bats-support # Block until FTL is ready, retry once per second for 45 seconds n=0 until [ $n -ge 45 ]; do - (nc -vv -z -w 30 127.0.0.1 4711 && nc -vv -z -w 30 127.0.0.1 4747) && break + nc -vv -z -w 30 127.0.0.1 4711 && break n=$[$n+1] echo "..." tail -n2 pihole-FTL.log diff --git a/test/test_suite.sh b/test/test_suite.sh index 3d033e83..cbf410db 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -183,52 +183,6 @@ load 'libs/bats-support/load' [[ "${lines[@]}" == *"INSERT INTO \"ftl\" VALUES(0,1);"* ]] } -@test "HTTP server: FTL responding correctly to HEAD request" { - run bash -c "curl --head -s 127.0.0.1:4747" - echo "output: ${lines[@]}" - echo "curl exit code: ${status}" - [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] - [[ ${lines[1]} == "Server: FTL" ]] - [[ ${lines[2]} == "" ]] - [[ "${status}" -eq 0 ]] -} - -@test "HTTP server: FTL responding correctly to GET request" { - run bash -c "curl -s 127.0.0.1:4747" - echo "output: ${lines[@]}" - echo "curl exit code: ${status}" - [[ "${status}" -eq 0 ]] -} - -@test "API: Correct answer to summary request (including header check)" { - run bash -c "curl -si 127.0.0.1:4747/stats/summary" - echo "output: ${lines[@]}" - echo "curl exit code: ${status}" - [[ ${lines[0]} == "HTTP/1.0 200 OK" ]] - [[ ${lines[1]} == "Server: FTL" ]] - [[ ${lines[2]} == "Cache-Control: no-cache" ]] - [[ ${lines[3]} == "Access-Control-Allow-Origin: *" ]] - [[ ${lines[4]} == "Content-Type: application/json" ]] - [[ ${lines[5]} == "{\"domains_being_blocked\":-1,\"dns_queries_today\":7,\"ads_blocked_today\":2,\"ads_percentage_today\":28.5714,\"unique_domains\":6,\"queries_forwarded\":3,\"queries_cached\":2,\"clients_ever_seen\":3,\"unique_clients\":3,\"status\":\"unknown\"}" ]] - [[ "${status}" -eq 0 ]] -} - -#@test "API: Correct answer to top_domains request" { -# run bash -c "curl -s 127.0.0.1:4747/stats/top_domains" -# echo "output: ${lines[@]}" -# echo "curl exit code: ${status}" -# [[ ${lines[0]} == "{\"top_domains\":{\"play.google.com\":2,\"example.com\":1,\"checkip.dyndns.org\":1,\"raspberrypi\":1},\"dns_queries_today\":7}" ]] -# [[ "${status}" -eq 0 ]] -#} - -#@test "API: Correct answer to top_ads request" { -# run bash -c "curl -s 127.0.0.1:4747/stats/top_ads" -# echo "output: ${lines[@]}" -# echo "curl exit code: ${status}" -# [[ ${lines[0]} == "{\"top_ads\":{\"addomain.com\":1,\"blacklisted.com\":1},\"ads_blocked_today\":2}" ]] -# [[ "${status}" -eq 0 ]] -#} - @test "Arguments check: Invalid option" { run bash -c './pihole-FTL abc' echo "output: ${lines[@]}" @@ -247,17 +201,17 @@ load 'libs/bats-support/load' echo "output: ${lines[@]}" [[ ${lines[0]} == "Socket created" ]] [[ ${lines[1]} == "Connection established" ]] - [[ ${lines[2]} == "domains_being_blocked -1" ]] - [[ ${lines[3]} == "dns_queries_today 7" ]] - [[ ${lines[4]} == "ads_blocked_today 2" ]] - [[ ${lines[5]} == "ads_percentage_today 28.571428" ]] - [[ ${lines[6]} == "unique_domains 6" ]] - [[ ${lines[7]} == "queries_forwarded 3" ]] - [[ ${lines[8]} == "queries_cached 2" ]] - [[ ${lines[9]} == "clients_ever_seen 3" ]] - [[ ${lines[10]} == "unique_clients 3" ]] - [[ ${lines[11]} == "status unknown" ]] - [[ ${lines[12]} == "---EOM---" ]] +# [[ ${lines[2]} == "domains_being_blocked -1" ]] +# [[ ${lines[3]} == "dns_queries_today 7" ]] +# [[ ${lines[4]} == "ads_blocked_today 2" ]] +# [[ ${lines[5]} == "ads_percentage_today 28.571428" ]] +# [[ ${lines[6]} == "unique_domains 6" ]] +# [[ ${lines[7]} == "queries_forwarded 3" ]] +# [[ ${lines[8]} == "queries_cached 2" ]] +# [[ ${lines[9]} == "clients_ever_seen 3" ]] +# [[ ${lines[10]} == "unique_clients 3" ]] +# [[ ${lines[11]} == "status unknown" ]] + [[ ${lines[2]} == "---EOM---" ]] } @test "Final part of the tests: Killing pihole-FTL process" { From 620bb44daab10b1d2a4f72c998d01091ccf0be5b Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 20:36:23 -0500 Subject: [PATCH 100/168] Remove duplicate dnsmasq config file location Signed-off-by: Mcat12 --- FTL.h | 1 - api_dns.c | 2 +- structs.c | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/FTL.h b/FTL.h index 348e012d..93bcb00b 100644 --- a/FTL.h +++ b/FTL.h @@ -90,7 +90,6 @@ typedef struct { const char* whitelist; const char* blacklist; const char* setupVars; - const char* dnsmasqconf; const char* wildcards; const char* auditlist; const char* dnsmasqconfig; diff --git a/api_dns.c b/api_dns.c index 6189958e..67d0b3d6 100644 --- a/api_dns.c +++ b/api_dns.c @@ -110,7 +110,7 @@ void getList(int *sock, char type, char list_type) { } void getPiholeStatus(int *sock, char type) { - int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconf); + int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconfig); // sendAPIResponse(*sock, type, OK); ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); } diff --git a/structs.c b/structs.c index 7524b058..dd5fe217 100644 --- a/structs.c +++ b/structs.c @@ -26,7 +26,6 @@ logFileNamesStruct files = { "/etc/pihole/whitelist.txt", "/etc/pihole/blacklist.txt", "/etc/pihole/setupVars.conf", - "/etc/dnsmasq.d/01-pihole.conf", "/etc/dnsmasq.d/03-pihole-wildcard.conf", "/etc/pihole/auditlog.list", "/etc/dnsmasq.d/01-pihole.conf" From 97f6ac534a5fc983773592cf52897698ee5d18e7 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 20:44:18 -0500 Subject: [PATCH 101/168] Remove extra parameter from bind_to_telnet_port Since it has been specialized, it is only called to create the telnet port. Signed-off-by: Mcat12 --- socket.c | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/socket.c b/socket.c index a119efae..c9090291 100644 --- a/socket.c +++ b/socket.c @@ -38,7 +38,7 @@ void saveport(int port) } } -void bind_to_telnet_port(char type, int *socketdescriptor) +void bind_to_telnet_port(int *socketdescriptor) { *socketdescriptor = socket(AF_INET, SOCK_STREAM, 0); @@ -73,18 +73,8 @@ void bind_to_telnet_port(char type, int *socketdescriptor) // convert this to network byte order using the function htons() // which converts a port number in host byte order to a port number // in network byte order - int port, port_init; - - switch(type) - { - case TELNET: - port_init = 4711; - break; - default: - logg("Incompatible socket type %i", (int)type); - exit(EXIT_FAILURE); - break; - } + int port; + int port_init = 4711; bool bound = false; for(port = port_init; port <= (port_init + 20); port++) @@ -107,8 +97,7 @@ void bind_to_telnet_port(char type, int *socketdescriptor) exit(EXIT_FAILURE); } - if(type == TELNET) - saveport(port); + saveport(port); // The listen system call allows the process to listen on the socket for connections if(listen(*socketdescriptor, BACKLOG) == -1) @@ -363,7 +352,7 @@ void *telnet_listening_thread(void *args) prctl(PR_SET_NAME,"telnet listener",0,0,0); // Initialize sockets only after initial log parsing in listenting_thread - bind_to_telnet_port(TELNET, &telnetfd); + bind_to_telnet_port(&telnetfd); // Listen as long as FTL is not killed while(!killed) From c0048e912aad3691c85447579ca0be712e528fa7 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 3 Jan 2018 20:48:06 -0500 Subject: [PATCH 102/168] Remove Json libraries Signed-off-by: Mcat12 --- api_dns.c | 1 - cJSON.c | 2701 ----------------------------------------------------- cJSON.h | 263 ------ 3 files changed, 2965 deletions(-) delete mode 100644 cJSON.c delete mode 100644 cJSON.h diff --git a/api_dns.c b/api_dns.c index 67d0b3d6..e58580c1 100644 --- a/api_dns.c +++ b/api_dns.c @@ -10,7 +10,6 @@ #include "FTL.h" #include "api.h" -#include "cJSON.h" void getList(int *sock, char type, char list_type) { FILE *fp; diff --git a/cJSON.c b/cJSON.c deleted file mode 100644 index 93798ac5..00000000 --- a/cJSON.c +++ /dev/null @@ -1,2701 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -/* cJSON */ -/* JSON parser in C. */ - -#ifdef __GNUC__ -#pragma GCC visibility push(default) -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __GNUC__ -#pragma GCC visibility pop -#endif - -#include "cJSON.h" - -/* define our own boolean type */ -#define true ((cJSON_bool)1) -#define false ((cJSON_bool)0) - -typedef struct { - const unsigned char *json; - size_t position; -} error; -static error global_error = { NULL, 0 }; - -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) -{ - return (const char*) (global_error.json + global_error.position); -} - -/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 5) || (CJSON_VERSION_PATCH != 7) - #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. -#endif - -CJSON_PUBLIC(const char*) cJSON_Version(void) -{ - static char version[15]; - sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); - - return version; -} - -/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ -static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) -{ - if ((string1 == NULL) || (string2 == NULL)) - { - return 1; - } - - if (string1 == string2) - { - return 0; - } - - for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) - { - if (*string1 == '\0') - { - return 0; - } - } - - return tolower(*string1) - tolower(*string2); -} - -typedef struct internal_hooks -{ - void *(*allocate)(size_t size); - void (*deallocate)(void *pointer); - void *(*reallocate)(void *pointer, size_t size); -} internal_hooks; - -static internal_hooks global_hooks = { malloc, free, realloc }; - -static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) -{ - size_t length = 0; - unsigned char *copy = NULL; - - if (string == NULL) - { - return NULL; - } - - length = strlen((const char*)string) + sizeof(""); - if (!(copy = (unsigned char*)hooks->allocate(length))) - { - return NULL; - } - memcpy(copy, string, length); - - return copy; -} - -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) -{ - if (hooks == NULL) - { - /* Reset hooks */ - global_hooks.allocate = malloc; - global_hooks.deallocate = free; - global_hooks.reallocate = realloc; - return; - } - - global_hooks.allocate = malloc; - if (hooks->malloc_fn != NULL) - { - global_hooks.allocate = hooks->malloc_fn; - } - - global_hooks.deallocate = free; - if (hooks->free_fn != NULL) - { - global_hooks.deallocate = hooks->free_fn; - } - - /* use realloc only if both free and malloc are used */ - global_hooks.reallocate = NULL; - if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) - { - global_hooks.reallocate = realloc; - } -} - -/* Internal constructor. */ -static cJSON *cJSON_New_Item(const internal_hooks * const hooks) -{ - cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); - if (node) - { - memset(node, '\0', sizeof(cJSON)); - } - - return node; -} - -/* Delete a cJSON structure. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) -{ - cJSON *next = NULL; - while (item != NULL) - { - next = item->next; - if (!(item->type & cJSON_IsReference) && (item->child != NULL)) - { - cJSON_Delete(item->child); - } - if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) - { - global_hooks.deallocate(item->valuestring); - } - if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) - { - global_hooks.deallocate(item->string); - } - global_hooks.deallocate(item); - item = next; - } -} - -/* get the decimal point character of the current locale */ -static unsigned char get_decimal_point(void) -{ - struct lconv *lconv = localeconv(); - return (unsigned char) lconv->decimal_point[0]; -} - -typedef struct -{ - const unsigned char *content; - size_t length; - size_t offset; - size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ - internal_hooks hooks; -} parse_buffer; - -/* check if the given size is left to read in a given parse buffer (starting with 1) */ -#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) -#define cannot_read(buffer, size) (!can_read(buffer, size)) -/* check if the buffer can be accessed at the given index (starting with 0) */ -#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) -#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) -/* get a pointer to the buffer at the position */ -#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) - -/* Parse the input text to generate a number, and populate the result into item. */ -static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) -{ - double number = 0; - unsigned char *after_end = NULL; - unsigned char number_c_string[64]; - unsigned char decimal_point = get_decimal_point(); - size_t i = 0; - - if ((input_buffer == NULL) || (input_buffer->content == NULL)) - { - return false; - } - - /* copy the number into a temporary buffer and replace '.' with the decimal point - * of the current locale (for strtod) - * This also takes care of '\0' not necessarily being available for marking the end of the input */ - for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) - { - switch (buffer_at_offset(input_buffer)[i]) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': - case 'e': - case 'E': - number_c_string[i] = buffer_at_offset(input_buffer)[i]; - break; - - case '.': - number_c_string[i] = decimal_point; - break; - - default: - goto loop_end; - } - } -loop_end: - number_c_string[i] = '\0'; - - number = strtod((const char*)number_c_string, (char**)&after_end); - if (number_c_string == after_end) - { - return false; /* parse_error */ - } - - item->valuedouble = number; - - /* use saturation in case of overflow */ - if (number >= INT_MAX) - { - item->valueint = INT_MAX; - } - else if (number <= INT_MIN) - { - item->valueint = INT_MIN; - } - else - { - item->valueint = (int)number; - } - - item->type = cJSON_Number; - - input_buffer->offset += (size_t)(after_end - number_c_string); - return true; -} - -/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) -{ - if (number >= INT_MAX) - { - object->valueint = INT_MAX; - } - else if (number <= INT_MIN) - { - object->valueint = INT_MIN; - } - else - { - object->valueint = (int)number; - } - - return object->valuedouble = number; -} - -typedef struct -{ - unsigned char *buffer; - size_t length; - size_t offset; - size_t depth; /* current nesting depth (for formatted printing) */ - cJSON_bool noalloc; - cJSON_bool format; /* is this print a formatted print */ - internal_hooks hooks; -} printbuffer; - -/* realloc printbuffer if necessary to have at least "needed" bytes more */ -static unsigned char* ensure(printbuffer * const p, size_t needed) -{ - unsigned char *newbuffer = NULL; - size_t newsize = 0; - - if ((p == NULL) || (p->buffer == NULL)) - { - return NULL; - } - - if ((p->length > 0) && (p->offset >= p->length)) - { - /* make sure that offset is valid */ - return NULL; - } - - if (needed > INT_MAX) - { - /* sizes bigger than INT_MAX are currently not supported */ - return NULL; - } - - needed += p->offset + 1; - if (needed <= p->length) - { - return p->buffer + p->offset; - } - - if (p->noalloc) { - return NULL; - } - - /* calculate new buffer size */ - if (needed > (INT_MAX / 2)) - { - /* overflow of int, use INT_MAX if possible */ - if (needed <= INT_MAX) - { - newsize = INT_MAX; - } - else - { - return NULL; - } - } - else - { - newsize = needed * 2; - } - - if (p->hooks.reallocate != NULL) - { - /* reallocate with realloc if available */ - newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); - if (newbuffer == NULL) - { - p->hooks.deallocate(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; - } - } - else - { - /* otherwise reallocate manually */ - newbuffer = (unsigned char*)p->hooks.allocate(newsize); - if (!newbuffer) - { - p->hooks.deallocate(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; - } - if (newbuffer) - { - memcpy(newbuffer, p->buffer, p->offset + 1); - } - p->hooks.deallocate(p->buffer); - } - p->length = newsize; - p->buffer = newbuffer; - - return newbuffer + p->offset; -} - -/* calculate the new length of the string in a printbuffer and update the offset */ -static void update_offset(printbuffer * const buffer) -{ - const unsigned char *buffer_pointer = NULL; - if ((buffer == NULL) || (buffer->buffer == NULL)) - { - return; - } - buffer_pointer = buffer->buffer + buffer->offset; - - buffer->offset += strlen((const char*)buffer_pointer); -} - -/* Render the number nicely from the given item into a string. */ -static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - double d = item->valuedouble; - int length = 0; - size_t i = 0; - unsigned char number_buffer[26]; /* temporary buffer to print the number into */ - unsigned char decimal_point = get_decimal_point(); - double test; - - if (output_buffer == NULL) - { - return false; - } - - /* This checks for NaN and Infinity */ - if ((d * 0) != 0) - { - length = sprintf((char*)number_buffer, "null"); - } - else - { - /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ - length = sprintf((char*)number_buffer, "%1.15g", d); - - /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) - { - /* If not, print with 17 decimal places of precision */ - length = sprintf((char*)number_buffer, "%1.17g", d); - } - } - - /* sprintf failed or buffer overrun occured */ - if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) - { - return false; - } - - /* reserve appropriate space in the output */ - output_pointer = ensure(output_buffer, (size_t)length); - if (output_pointer == NULL) - { - return false; - } - - /* copy the printed number to the output and replace locale - * dependent decimal point with '.' */ - for (i = 0; i < ((size_t)length); i++) - { - if (number_buffer[i] == decimal_point) - { - output_pointer[i] = '.'; - continue; - } - - output_pointer[i] = number_buffer[i]; - } - output_pointer[i] = '\0'; - - output_buffer->offset += (size_t)length; - - return true; -} - -/* parse 4 digit hexadecimal number */ -static unsigned parse_hex4(const unsigned char * const input) -{ - unsigned int h = 0; - size_t i = 0; - - for (i = 0; i < 4; i++) - { - /* parse digit */ - if ((input[i] >= '0') && (input[i] <= '9')) - { - h += (unsigned int) input[i] - '0'; - } - else if ((input[i] >= 'A') && (input[i] <= 'F')) - { - h += (unsigned int) 10 + input[i] - 'A'; - } - else if ((input[i] >= 'a') && (input[i] <= 'f')) - { - h += (unsigned int) 10 + input[i] - 'a'; - } - else /* invalid */ - { - return 0; - } - - if (i < 3) - { - /* shift left to make place for the next nibble */ - h = h << 4; - } - } - - return h; -} - -/* converts a UTF-16 literal to UTF-8 - * A literal can be one or two sequences of the form \uXXXX */ -static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) -{ - long unsigned int codepoint = 0; - unsigned int first_code = 0; - const unsigned char *first_sequence = input_pointer; - unsigned char utf8_length = 0; - unsigned char utf8_position = 0; - unsigned char sequence_length = 0; - unsigned char first_byte_mark = 0; - - if ((input_end - first_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - /* get the first utf16 sequence */ - first_code = parse_hex4(first_sequence + 2); - - /* check that the code is valid */ - if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) - { - goto fail; - } - - /* UTF16 surrogate pair */ - if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) - { - const unsigned char *second_sequence = first_sequence + 6; - unsigned int second_code = 0; - sequence_length = 12; /* \uXXXX\uXXXX */ - - if ((input_end - second_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) - { - /* missing second half of the surrogate pair */ - goto fail; - } - - /* get the second utf16 sequence */ - second_code = parse_hex4(second_sequence + 2); - /* check that the code is valid */ - if ((second_code < 0xDC00) || (second_code > 0xDFFF)) - { - /* invalid second half of the surrogate pair */ - goto fail; - } - - - /* calculate the unicode codepoint from the surrogate pair */ - codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); - } - else - { - sequence_length = 6; /* \uXXXX */ - codepoint = first_code; - } - - /* encode as UTF-8 - * takes at maximum 4 bytes to encode: - * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - if (codepoint < 0x80) - { - /* normal ascii, encoding 0xxxxxxx */ - utf8_length = 1; - } - else if (codepoint < 0x800) - { - /* two bytes, encoding 110xxxxx 10xxxxxx */ - utf8_length = 2; - first_byte_mark = 0xC0; /* 11000000 */ - } - else if (codepoint < 0x10000) - { - /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ - utf8_length = 3; - first_byte_mark = 0xE0; /* 11100000 */ - } - else if (codepoint <= 0x10FFFF) - { - /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ - utf8_length = 4; - first_byte_mark = 0xF0; /* 11110000 */ - } - else - { - /* invalid unicode codepoint */ - goto fail; - } - - /* encode as utf8 */ - for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) - { - /* 10xxxxxx */ - (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - } - /* encode first byte */ - if (utf8_length > 1) - { - (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); - } - else - { - (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); - } - - *output_pointer += utf8_length; - - return sequence_length; - -fail: - return 0; -} - -/* Parse the input text into an unescaped cinput, and populate item. */ -static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) -{ - const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; - const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; - unsigned char *output_pointer = NULL; - unsigned char *output = NULL; - - /* not a string */ - if (buffer_at_offset(input_buffer)[0] != '\"') - { - goto fail; - } - - { - /* calculate approximate size of the output (overestimate) */ - size_t allocation_length = 0; - size_t skipped_bytes = 0; - while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) - { - /* is escape sequence */ - if (input_end[0] == '\\') - { - if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) - { - /* prevent buffer overflow when last input character is a backslash */ - goto fail; - } - skipped_bytes++; - input_end++; - } - input_end++; - } - if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) - { - goto fail; /* string ended unexpectedly */ - } - - /* This is at most how much we need for the output */ - allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; - output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); - if (output == NULL) - { - goto fail; /* allocation failure */ - } - } - - output_pointer = output; - /* loop through the string literal */ - while (input_pointer < input_end) - { - if (*input_pointer != '\\') - { - *output_pointer++ = *input_pointer++; - } - /* escape sequence */ - else - { - unsigned char sequence_length = 2; - if ((input_end - input_pointer) < 1) - { - goto fail; - } - - switch (input_pointer[1]) - { - case 'b': - *output_pointer++ = '\b'; - break; - case 'f': - *output_pointer++ = '\f'; - break; - case 'n': - *output_pointer++ = '\n'; - break; - case 'r': - *output_pointer++ = '\r'; - break; - case 't': - *output_pointer++ = '\t'; - break; - case '\"': - case '\\': - case '/': - *output_pointer++ = input_pointer[1]; - break; - - /* UTF-16 literal */ - case 'u': - sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); - if (sequence_length == 0) - { - /* failed to convert UTF16-literal to UTF-8 */ - goto fail; - } - break; - - default: - goto fail; - } - input_pointer += sequence_length; - } - } - - /* zero terminate the output */ - *output_pointer = '\0'; - - item->type = cJSON_String; - item->valuestring = (char*)output; - - input_buffer->offset = (size_t) (input_end - input_buffer->content); - input_buffer->offset++; - - return true; - -fail: - if (output != NULL) - { - input_buffer->hooks.deallocate(output); - } - - if (input_pointer != NULL) - { - input_buffer->offset = (size_t)(input_pointer - input_buffer->content); - } - - return false; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) -{ - const unsigned char *input_pointer = NULL; - unsigned char *output = NULL; - unsigned char *output_pointer = NULL; - size_t output_length = 0; - /* numbers of additional characters needed for escaping */ - size_t escape_characters = 0; - - if (output_buffer == NULL) - { - return false; - } - - /* empty string */ - if (input == NULL) - { - output = ensure(output_buffer, sizeof("\"\"")); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "\"\""); - - return true; - } - - /* set "flag" to 1 if something needs to be escaped */ - for (input_pointer = input; *input_pointer; input_pointer++) - { - switch (*input_pointer) - { - case '\"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - /* one character escape sequence */ - escape_characters++; - break; - default: - if (*input_pointer < 32) - { - /* UTF-16 escape sequence uXXXX */ - escape_characters += 5; - } - break; - } - } - output_length = (size_t)(input_pointer - input) + escape_characters; - - output = ensure(output_buffer, output_length + sizeof("\"\"")); - if (output == NULL) - { - return false; - } - - /* no characters have to be escaped */ - if (escape_characters == 0) - { - output[0] = '\"'; - memcpy(output + 1, input, output_length); - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; - } - - output[0] = '\"'; - output_pointer = output + 1; - /* copy the string */ - for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) - { - if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) - { - /* normal character, copy */ - *output_pointer = *input_pointer; - } - else - { - /* character needs to be escaped */ - *output_pointer++ = '\\'; - switch (*input_pointer) - { - case '\\': - *output_pointer = '\\'; - break; - case '\"': - *output_pointer = '\"'; - break; - case '\b': - *output_pointer = 'b'; - break; - case '\f': - *output_pointer = 'f'; - break; - case '\n': - *output_pointer = 'n'; - break; - case '\r': - *output_pointer = 'r'; - break; - case '\t': - *output_pointer = 't'; - break; - default: - /* escape and print as unicode codepoint */ - sprintf((char*)output_pointer, "u%04x", *input_pointer); - output_pointer += 4; - break; - } - } - } - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; -} - -/* Invoke print_string_ptr (which is useful) on an item. */ -static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) -{ - return print_string_ptr((unsigned char*)item->valuestring, p); -} - -/* Predeclare these prototypes. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); - -/* Utility to jump whitespace and cr/lf */ -static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL)) - { - return NULL; - } - - while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) - { - buffer->offset++; - } - - if (buffer->offset == buffer->length) - { - buffer->offset--; - } - - return buffer; -} - -/* Parse an object - create a new root, and populate. */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) -{ - parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; - cJSON *item = NULL; - - /* reset error position */ - global_error.json = NULL; - global_error.position = 0; - - if (value == NULL) - { - goto fail; - } - - buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + sizeof(""); - buffer.offset = 0; - buffer.hooks = global_hooks; - - item = cJSON_New_Item(&global_hooks); - if (item == NULL) /* memory fail */ - { - goto fail; - } - - if (!parse_value(item, buffer_skip_whitespace(&buffer))) - { - /* parse failure. ep is set. */ - goto fail; - } - - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) - { - buffer_skip_whitespace(&buffer); - if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') - { - goto fail; - } - } - if (return_parse_end) - { - *return_parse_end = (const char*)buffer_at_offset(&buffer); - } - - return item; - -fail: - if (item != NULL) - { - cJSON_Delete(item); - } - - if (value != NULL) - { - error local_error; - local_error.json = (const unsigned char*)value; - local_error.position = 0; - - if (buffer.offset < buffer.length) - { - local_error.position = buffer.offset; - } - else if (buffer.length > 0) - { - local_error.position = buffer.length - 1; - } - - if (return_parse_end != NULL) - { - *return_parse_end = (const char*)local_error.json + local_error.position; - } - else - { - global_error = local_error; - } - } - - return NULL; -} - -/* Default options for cJSON_Parse */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) -{ - return cJSON_ParseWithOpts(value, 0, 0); -} - -#define cjson_min(a, b) ((a < b) ? a : b) - -static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) -{ - printbuffer buffer[1]; - unsigned char *printed = NULL; - - memset(buffer, 0, sizeof(buffer)); - - /* create buffer */ - buffer->buffer = (unsigned char*) hooks->allocate(256); - buffer->format = format; - buffer->hooks = *hooks; - if (buffer->buffer == NULL) - { - goto fail; - } - - /* print the value */ - if (!print_value(item, buffer)) - { - goto fail; - } - update_offset(buffer); - - /* check if reallocate is available */ - if (hooks->reallocate != NULL) - { - printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->length); - buffer->buffer = NULL; - if (printed == NULL) { - goto fail; - } - } - else /* otherwise copy the JSON over to a new buffer */ - { - printed = (unsigned char*) hooks->allocate(buffer->offset + 1); - if (printed == NULL) - { - goto fail; - } - memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); - printed[buffer->offset] = '\0'; /* just to be sure */ - - /* free the buffer */ - hooks->deallocate(buffer->buffer); - } - - return printed; - -fail: - if (buffer->buffer != NULL) - { - hooks->deallocate(buffer->buffer); - } - - if (printed != NULL) - { - hooks->deallocate(printed); - } - - return NULL; -} - -/* Render a cJSON item/entity/structure to text. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) -{ - return (char*)print(item, true, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) -{ - return (char*)print(item, false, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - - if (prebuffer < 0) - { - return NULL; - } - - p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); - if (!p.buffer) - { - return NULL; - } - - p.length = (size_t)prebuffer; - p.offset = 0; - p.noalloc = false; - p.format = fmt; - p.hooks = global_hooks; - - if (!print_value(item, &p)) - { - global_hooks.deallocate(p.buffer); - return NULL; - } - - return (char*)p.buffer; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - - if ((len < 0) || (buf == NULL)) - { - return false; - } - - p.buffer = (unsigned char*)buf; - p.length = (size_t)len; - p.offset = 0; - p.noalloc = true; - p.format = fmt; - p.hooks = global_hooks; - - return print_value(item, &p); -} - -/* Parser core - when encountering text, process appropriately. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) -{ - if ((input_buffer == NULL) || (input_buffer->content == NULL)) - { - return false; /* no input */ - } - - /* parse the different types of values */ - /* null */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) - { - item->type = cJSON_NULL; - input_buffer->offset += 4; - return true; - } - /* false */ - if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) - { - item->type = cJSON_False; - input_buffer->offset += 5; - return true; - } - /* true */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) - { - item->type = cJSON_True; - item->valueint = 1; - input_buffer->offset += 4; - return true; - } - /* string */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) - { - return parse_string(item, input_buffer); - } - /* number */ - if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) - { - return parse_number(item, input_buffer); - } - /* array */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) - { - return parse_array(item, input_buffer); - } - /* object */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) - { - return parse_object(item, input_buffer); - } - - - return false; -} - -/* Render a value to text. */ -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output = NULL; - - if ((item == NULL) || (output_buffer == NULL)) - { - return false; - } - - switch ((item->type) & 0xFF) - { - case cJSON_NULL: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "null"); - return true; - - case cJSON_False: - output = ensure(output_buffer, 6); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "false"); - return true; - - case cJSON_True: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "true"); - return true; - - case cJSON_Number: - return print_number(item, output_buffer); - - case cJSON_Raw: - { - size_t raw_length = 0; - if (item->valuestring == NULL) - { - if (!output_buffer->noalloc) - { - output_buffer->hooks.deallocate(output_buffer->buffer); - } - return false; - } - - raw_length = strlen(item->valuestring) + sizeof(""); - output = ensure(output_buffer, raw_length); - if (output == NULL) - { - return false; - } - memcpy(output, item->valuestring, raw_length); - return true; - } - - case cJSON_String: - return print_string(item, output_buffer); - - case cJSON_Array: - return print_array(item, output_buffer); - - case cJSON_Object: - return print_object(item, output_buffer); - - default: - return false; - } -} - -/* Build an array from input text. */ -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) -{ - cJSON *head = NULL; /* head of the linked list */ - cJSON *current_item = NULL; - - if (input_buffer->depth >= CJSON_NESTING_LIMIT) - { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (buffer_at_offset(input_buffer)[0] != '[') - { - /* not an array */ - goto fail; - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) - { - /* empty array */ - goto success; - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) - { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do - { - /* allocate next item */ - cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); - if (new_item == NULL) - { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) - { - /* start the linked list */ - current_item = head = new_item; - } - else - { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse next value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) - { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } - while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') - { - goto fail; /* expected end of array */ - } - -success: - input_buffer->depth--; - - item->type = cJSON_Array; - item->child = head; - - input_buffer->offset++; - - return true; - -fail: - if (head != NULL) - { - cJSON_Delete(head); - } - - return false; -} - -/* Render an array to text */ -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_element = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output array. */ - /* opening square bracket */ - output_pointer = ensure(output_buffer, 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer = '['; - output_buffer->offset++; - output_buffer->depth++; - - while (current_element != NULL) - { - if (!print_value(current_element, output_buffer)) - { - return false; - } - update_offset(output_buffer); - if (current_element->next) - { - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ','; - if(output_buffer->format) - { - *output_pointer++ = ' '; - } - *output_pointer = '\0'; - output_buffer->offset += length; - } - current_element = current_element->next; - } - - output_pointer = ensure(output_buffer, 2); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ']'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Build an object from the text. */ -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) -{ - cJSON *head = NULL; /* linked list head */ - cJSON *current_item = NULL; - - if (input_buffer->depth >= CJSON_NESTING_LIMIT) - { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) - { - goto fail; /* not an object */ - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) - { - goto success; /* empty object */ - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) - { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do - { - /* allocate next item */ - cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); - if (new_item == NULL) - { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) - { - /* start the linked list */ - current_item = head = new_item; - } - else - { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse the name of the child */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_string(current_item, input_buffer)) - { - goto fail; /* faile to parse name */ - } - buffer_skip_whitespace(input_buffer); - - /* swap valuestring and string, because we parsed the name */ - current_item->string = current_item->valuestring; - current_item->valuestring = NULL; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) - { - goto fail; /* invalid object */ - } - - /* parse the value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) - { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } - while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) - { - goto fail; /* expected end of object */ - } - -success: - input_buffer->depth--; - - item->type = cJSON_Object; - item->child = head; - - input_buffer->offset++; - return true; - -fail: - if (head != NULL) - { - cJSON_Delete(head); - } - - return false; -} - -/* Render an object to text. */ -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_item = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output: */ - length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer++ = '{'; - output_buffer->depth++; - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - output_buffer->offset += length; - - while (current_item) - { - if (output_buffer->format) - { - size_t i; - output_pointer = ensure(output_buffer, output_buffer->depth); - if (output_pointer == NULL) - { - return false; - } - for (i = 0; i < output_buffer->depth; i++) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += output_buffer->depth; - } - - /* print key */ - if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ':'; - if (output_buffer->format) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += length; - - /* print value */ - if (!print_value(current_item, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - /* print comma if not last */ - length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0)); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - if (current_item->next) - { - *output_pointer++ = ','; - } - - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - *output_pointer = '\0'; - output_buffer->offset += length; - - current_item = current_item->next; - } - - output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); - if (output_pointer == NULL) - { - return false; - } - if (output_buffer->format) - { - size_t i; - for (i = 0; i < (output_buffer->depth - 1); i++) - { - *output_pointer++ = '\t'; - } - } - *output_pointer++ = '}'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Get Array size/item / object item. */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) -{ - cJSON *child = NULL; - size_t size = 0; - - if (array == NULL) - { - return 0; - } - - child = array->child; - - while(child != NULL) - { - size++; - child = child->next; - } - - /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ - - return (int)size; -} - -static cJSON* get_array_item(const cJSON *array, size_t index) -{ - cJSON *current_child = NULL; - - if (array == NULL) - { - return NULL; - } - - current_child = array->child; - while ((current_child != NULL) && (index > 0)) - { - index--; - current_child = current_child->next; - } - - return current_child; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) -{ - if (index < 0) - { - return NULL; - } - - return get_array_item(array, (size_t)index); -} - -static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) -{ - cJSON *current_element = NULL; - - if ((object == NULL) || (name == NULL)) - { - return NULL; - } - - current_element = object->child; - if (case_sensitive) - { - while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) - { - current_element = current_element->next; - } - } - else - { - while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) - { - current_element = current_element->next; - } - } - - return current_element; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, false); -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, true); -} - -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) -{ - return cJSON_GetObjectItem(object, string) ? 1 : 0; -} - -/* Utility for array list handling. */ -static void suffix_object(cJSON *prev, cJSON *item) -{ - prev->next = item; - item->prev = prev; -} - -/* Utility for handling references. */ -static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) -{ - cJSON *reference = NULL; - if (item == NULL) - { - return NULL; - } - - reference = cJSON_New_Item(hooks); - if (reference == NULL) - { - return NULL; - } - - memcpy(reference, item, sizeof(cJSON)); - reference->string = NULL; - reference->type |= cJSON_IsReference; - reference->next = reference->prev = NULL; - return reference; -} - -/* Add item to array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) -{ - cJSON *child = NULL; - - if ((item == NULL) || (array == NULL)) - { - return; - } - - child = array->child; - - if (child == NULL) - { - /* list is empty, start new one */ - array->child = item; - } - else - { - /* append to the end */ - while (child->next) - { - child = child->next; - } - suffix_object(child, item); - } -} - -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) -{ - if (item == NULL) - { - return; - } - - /* call cJSON_AddItemToObjectCS for code reuse */ - cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); - /* remove cJSON_StringIsConst flag */ - item->type &= ~cJSON_StringIsConst; -} - -#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) - #pragma GCC diagnostic push -#endif -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -/* Add an item to an object with constant string as key */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) -{ - if ((item == NULL) || (string == NULL)) - { - return; - } - if (!(item->type & cJSON_StringIsConst) && item->string) - { - global_hooks.deallocate(item->string); - } - item->string = (char*)string; - item->type |= cJSON_StringIsConst; - cJSON_AddItemToArray(object, item); -} -#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) - #pragma GCC diagnostic pop -#endif - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) -{ - if (array == NULL) - { - return; - } - - cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) -{ - if ((object == NULL) || (string == NULL)) - { - return; - } - - cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) -{ - if ((parent == NULL) || (item == NULL)) - { - return NULL; - } - - if (item->prev != NULL) - { - /* not the first element */ - item->prev->next = item->next; - } - if (item->next != NULL) - { - /* not the last element */ - item->next->prev = item->prev; - } - - if (item == parent->child) - { - /* first element */ - parent->child = item->next; - } - /* make sure the detached item doesn't point anywhere anymore */ - item->prev = NULL; - item->next = NULL; - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) -{ - if (which < 0) - { - return NULL; - } - - return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) -{ - cJSON_Delete(cJSON_DetachItemFromArray(array, which)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) -{ - cJSON *to_detach = cJSON_GetObjectItem(object, string); - - return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ - cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); - - return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) -{ - cJSON_Delete(cJSON_DetachItemFromObject(object, string)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ - cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); -} - -/* Replace array/object items with new ones. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) -{ - cJSON *after_inserted = NULL; - - if (which < 0) - { - return; - } - - after_inserted = get_array_item(array, (size_t)which); - if (after_inserted == NULL) - { - cJSON_AddItemToArray(array, newitem); - return; - } - - newitem->next = after_inserted; - newitem->prev = after_inserted->prev; - after_inserted->prev = newitem; - if (after_inserted == array->child) - { - array->child = newitem; - } - else - { - newitem->prev->next = newitem; - } -} - -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) -{ - if ((parent == NULL) || (replacement == NULL) || (item == NULL)) - { - return false; - } - - if (replacement == item) - { - return true; - } - - replacement->next = item->next; - replacement->prev = item->prev; - - if (replacement->next != NULL) - { - replacement->next->prev = replacement; - } - if (replacement->prev != NULL) - { - replacement->prev->next = replacement; - } - if (parent->child == item) - { - parent->child = replacement; - } - - item->next = NULL; - item->prev = NULL; - cJSON_Delete(item); - - return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) -{ - if (which < 0) - { - return; - } - - cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); -} - -static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) -{ - if ((replacement == NULL) || (string == NULL)) - { - return false; - } - - /* replace the name in the replacement */ - if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) - { - cJSON_free(replacement->string); - } - replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); - replacement->type &= ~cJSON_StringIsConst; - - cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); - - return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) -{ - replace_item_in_object(object, string, newitem, false); -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) -{ - replace_item_in_object(object, string, newitem, true); -} - -/* Create basic types: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_NULL; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_True; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_False; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = b ? cJSON_True : cJSON_False; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Number; - item->valuedouble = num; - - /* use saturation in case of overflow */ - if (num >= INT_MAX) - { - item->valueint = INT_MAX; - } - else if (num <= INT_MIN) - { - item->valueint = INT_MIN; - } - else - { - item->valueint = (int)num; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_String; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Raw; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type=cJSON_Array; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if (item) - { - item->type = cJSON_Object; - } - - return item; -} - -/* Create Arrays: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if (!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber((double)numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0;a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (strings == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for (i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateString(strings[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p,n); - } - p = n; - } - - return a; -} - -/* Duplication */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) -{ - cJSON *newitem = NULL; - cJSON *child = NULL; - cJSON *next = NULL; - cJSON *newchild = NULL; - - /* Bail on bad ptr */ - if (!item) - { - goto fail; - } - /* Create new item */ - newitem = cJSON_New_Item(&global_hooks); - if (!newitem) - { - goto fail; - } - /* Copy over all vars */ - newitem->type = item->type & (~cJSON_IsReference); - newitem->valueint = item->valueint; - newitem->valuedouble = item->valuedouble; - if (item->valuestring) - { - newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); - if (!newitem->valuestring) - { - goto fail; - } - } - if (item->string) - { - newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); - if (!newitem->string) - { - goto fail; - } - } - /* If non-recursive, then we're done! */ - if (!recurse) - { - return newitem; - } - /* Walk the ->next chain for the child. */ - child = item->child; - while (child != NULL) - { - newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) - { - goto fail; - } - if (next != NULL) - { - /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - next->next = newchild; - newchild->prev = next; - next = newchild; - } - else - { - /* Set newitem->child and move to it */ - newitem->child = newchild; - next = newchild; - } - child = child->next; - } - - return newitem; - -fail: - if (newitem != NULL) - { - cJSON_Delete(newitem); - } - - return NULL; -} - -CJSON_PUBLIC(void) cJSON_Minify(char *json) -{ - unsigned char *into = (unsigned char*)json; - - if (json == NULL) - { - return; - } - - while (*json) - { - if (*json == ' ') - { - json++; - } - else if (*json == '\t') - { - /* Whitespace characters. */ - json++; - } - else if (*json == '\r') - { - json++; - } - else if (*json=='\n') - { - json++; - } - else if ((*json == '/') && (json[1] == '/')) - { - /* double-slash comments, to end of line. */ - while (*json && (*json != '\n')) - { - json++; - } - } - else if ((*json == '/') && (json[1] == '*')) - { - /* multiline comments. */ - while (*json && !((*json == '*') && (json[1] == '/'))) - { - json++; - } - json += 2; - } - else if (*json == '\"') - { - /* string literals, which are \" sensitive. */ - *into++ = (unsigned char)*json++; - while (*json && (*json != '\"')) - { - if (*json == '\\') - { - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - else - { - /* All other characters. */ - *into++ = (unsigned char)*json++; - } - } - - /* and null-terminate. */ - *into = '\0'; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Invalid; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_False; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xff) == cJSON_True; -} - - -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & (cJSON_True | cJSON_False)) != 0; -} -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_NULL; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Number; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_String; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Array; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Object; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Raw; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) -{ - if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) - { - return false; - } - - /* check if type is valid */ - switch (a->type & 0xFF) - { - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - case cJSON_Number: - case cJSON_String: - case cJSON_Raw: - case cJSON_Array: - case cJSON_Object: - break; - - default: - return false; - } - - /* identical objects are equal */ - if (a == b) - { - return true; - } - - switch (a->type & 0xFF) - { - /* in these cases and equal type is enough */ - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - return true; - - case cJSON_Number: - if (a->valuedouble == b->valuedouble) - { - return true; - } - return false; - - case cJSON_String: - case cJSON_Raw: - if ((a->valuestring == NULL) || (b->valuestring == NULL)) - { - return false; - } - if (strcmp(a->valuestring, b->valuestring) == 0) - { - return true; - } - - return false; - - case cJSON_Array: - { - cJSON *a_element = a->child; - cJSON *b_element = b->child; - - for (; (a_element != NULL) && (b_element != NULL);) - { - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - - a_element = a_element->next; - b_element = b_element->next; - } - - /* one of the arrays is longer than the other */ - if (a_element != b_element) { - return false; - } - - return true; - } - - case cJSON_Object: - { - cJSON *a_element = NULL; - cJSON *b_element = NULL; - cJSON_ArrayForEach(a_element, a) - { - /* TODO This has O(n^2) runtime, which is horrible! */ - b_element = get_object_item(b, a_element->string, case_sensitive); - if (b_element == NULL) - { - return false; - } - - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - } - - /* doing this twice, once on a and b to prevent true comparison if a subset of b - * TODO: Do this the proper way, this is just a fix for now */ - cJSON_ArrayForEach(b_element, b) - { - a_element = get_object_item(a, b_element->string, case_sensitive); - if (a_element == NULL) - { - return false; - } - - if (!cJSON_Compare(b_element, a_element, case_sensitive)) - { - return false; - } - } - - return true; - } - - default: - return false; - } -} - -CJSON_PUBLIC(void *) cJSON_malloc(size_t size) -{ - return global_hooks.allocate(size); -} - -CJSON_PUBLIC(void) cJSON_free(void *object) -{ - global_hooks.deallocate(object); -} diff --git a/cJSON.h b/cJSON.h deleted file mode 100644 index 0878fd0f..00000000 --- a/cJSON.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef cJSON__h -#define cJSON__h - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* project version */ -#define CJSON_VERSION_MAJOR 1 -#define CJSON_VERSION_MINOR 5 -#define CJSON_VERSION_PATCH 7 - -#include - -/* cJSON Types: */ -#define cJSON_Invalid (0) -#define cJSON_False (1 << 0) -#define cJSON_True (1 << 1) -#define cJSON_NULL (1 << 2) -#define cJSON_Number (1 << 3) -#define cJSON_String (1 << 4) -#define cJSON_Array (1 << 5) -#define cJSON_Object (1 << 6) -#define cJSON_Raw (1 << 7) /* raw json */ - -#define cJSON_IsReference 256 -#define cJSON_StringIsConst 512 - -/* The cJSON structure: */ -typedef struct cJSON -{ - /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON *next; - struct cJSON *prev; - /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - struct cJSON *child; - - /* The type of the item, as above. */ - int type; - - /* The item's string, if type==cJSON_String and type == cJSON_Raw */ - char *valuestring; - /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ - int valueint; - /* The item's number, if type==cJSON_Number */ - double valuedouble; - - /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ - char *string; -} cJSON; - -typedef struct cJSON_Hooks -{ - void *(*malloc_fn)(size_t sz); - void (*free_fn)(void *ptr); -} cJSON_Hooks; - -typedef int cJSON_bool; - -#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) -#define __WINDOWS__ -#endif -#ifdef __WINDOWS__ - -/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: - -CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols -CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) -CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol - -For *nix builds that support visibility attribute, you can define similar behavior by - -setting default visibility to hidden by adding --fvisibility=hidden (for gcc) -or --xldscope=hidden (for sun cc) -to CFLAGS - -then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does - -*/ - -/* export symbols by default, this is necessary for copy pasting the C and header file */ -#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_EXPORT_SYMBOLS -#endif - -#if defined(CJSON_HIDE_SYMBOLS) -#define CJSON_PUBLIC(type) type __stdcall -#elif defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall -#elif defined(CJSON_IMPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall -#endif -#else /* !WIN32 */ -#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) -#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type -#else -#define CJSON_PUBLIC(type) type -#endif -#endif - -/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. - * This is to prevent stack overflows. */ -#ifndef CJSON_NESTING_LIMIT -#define CJSON_NESTING_LIMIT 1000 -#endif - -/* returns the version of cJSON as a string */ -CJSON_PUBLIC(const char*) cJSON_Version(void); - -/* Supply malloc, realloc and free functions to cJSON */ -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); - -/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error. If not, then cJSON_GetErrorPtr() does the job. */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); - -/* Render a cJSON entity to text for transfer/storage. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); -/* Render a cJSON entity to text for transfer/storage without any formatting. */ -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); -/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); -/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ -/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); -/* Delete a cJSON entity and all subentities. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); - -/* Returns the number of items in an array (or object). */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); -/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); -/* Get item "string" from object. Case insensitive. */ -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); - -/* These functions check the type of an item */ -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); - -/* These calls create a cJSON item of the appropriate type. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); -/* raw json */ -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); - -/* These utilities create an Array of count items. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); - -/* Append item to the specified array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); -/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. - * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before - * writing to `item->string` */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); -/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); - -/* Remove/Detatch items from Arrays/Objects. */ -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); - -/* Update array items. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); - -/* Duplicate a cJSON item */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); -/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will -need to be released. With recurse!=0, it will duplicate any children connected to the item. -The item->next and ->prev pointers are always zero on return from Duplicate. */ -/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. - * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); - - -CJSON_PUBLIC(void) cJSON_Minify(char *json); - -/* Macros for creating things quickly. */ -#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) -#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) -#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) -#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) -#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) -#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) -#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s)) - -/* When assigning an integer value, it needs to be propagated to valuedouble too. */ -#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) -/* helper for the cJSON_SetNumberValue macro */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); -#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) - -/* Macro for iterating over an array or object */ -#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) - -/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ -CJSON_PUBLIC(void *) cJSON_malloc(size_t size); -CJSON_PUBLIC(void) cJSON_free(void *object); - -#ifdef __cplusplus -} -#endif - -#endif From e5279853715320b30df42130b05b2952307e2779 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 4 Jan 2018 12:04:04 -0500 Subject: [PATCH 103/168] Implement getStats using MessagePack Signed-off-by: Mcat12 --- Makefile | 2 +- api.h | 6 ++++++ api_stats.c | 34 ++++++++++------------------------ msgpack.c | 40 ++++++++++++++++++++++++++++++++++++++++ request.c | 2 +- routines.h | 2 +- socket.c | 8 ++++++-- socket_client.c | 16 ++++++++-------- test/test_suite.sh | 12 +----------- 9 files changed, 74 insertions(+), 48 deletions(-) create mode 100644 msgpack.c diff --git a/Makefile b/Makefile index efbb725f..e550cf52 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # Please see LICENSE file for your rights under this license. DEPS = FTL.h routines.h api.h version.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o msgpack.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') diff --git a/api.h b/api.h index 3266a2ed..b012cc93 100644 --- a/api.h +++ b/api.h @@ -38,3 +38,9 @@ void getPiholeStatus(int *sock, char type); // General API commands bool matchesRegex(char *regex_expression, char *input); bool isValidDomain(char *domain); + +// MessagePack serialization helpers +void pack_eom(int sock); +void pack_int(int sock, int value); +void pack_float(int sock, float value); +void pack_unsigned_char(int sock, unsigned char value); diff --git a/api_stats.c b/api_stats.c index 497785ea..24b67063 100644 --- a/api_stats.c +++ b/api_stats.c @@ -96,30 +96,16 @@ void getStats(int *sock, char type) } else { -// sendAPIResponse(*sock, type, OK); -// ssend( -// *sock, -// "\"domains_being_blocked\":%s," -// "\"dns_queries_today\":%i," -// "\"ads_blocked_today\":%i," -// "\"ads_percentage_today\":%.4f," -// "\"unique_domains\":%i," -// "\"queries_forwarded\":%i," -// "\"queries_cached\":%i," -// "\"clients_ever_seen\":%i," -// "\"unique_clients\":%i," -// "\"status\":\"%s\"", -// domains_blocked, -// total, -// blocked, -// percentage, -// counters.domains, -// counters.forwardedqueries, -// counters.cached, -// counters.clients, -// activeclients, -// status -// ); + pack_int(*sock, counters.gravity); + pack_int(*sock, total); + pack_int(*sock, blocked); + pack_float(*sock, percentage); + pack_int(*sock, counters.domains); + pack_int(*sock, counters.forwardedqueries); + pack_int(*sock, counters.cached); + pack_int(*sock, counters.clients); + pack_int(*sock, activeclients); + pack_unsigned_char(*sock, blockingstatus); } if(debugclients) diff --git a/msgpack.c b/msgpack.c new file mode 100644 index 00000000..81d1edc4 --- /dev/null +++ b/msgpack.c @@ -0,0 +1,40 @@ +/* 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 +* MessagePack serialization +* +* 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" + +void pack_eom(int sock) { + // This byte is explicitly never used in the MessagePack spec, so it is perfect to use as an EOM for this API. + unsigned char eom = 0xc1; + write(sock, &eom, sizeof(eom)); +} + +void pack_number(int sock, unsigned char format, void *value, size_t size) { + write(sock, &format, sizeof(format)); + write(sock, value, size); +} + +void pack_int(int sock, int value) { + uint32_t bigEValue = htonl((uint32_t) value); + pack_number(sock, 0xd2, &bigEValue, sizeof(bigEValue)); +} + +void pack_float(int sock, float value) { + // Need to use memcpy to do a direct copy without reinterpreting the bytes. It should get optimized away. + uint32_t bigEValue; + memcpy(&bigEValue, &value, sizeof(bigEValue)); + bigEValue = htonl(bigEValue); + pack_number(sock, 0xca, &bigEValue, sizeof(bigEValue)); +} + +void pack_unsigned_char(int sock, unsigned char value) { + pack_number(sock, 0xcc, &value, sizeof(value)); +} diff --git a/request.c b/request.c index 1f7d1e27..269185b7 100644 --- a/request.c +++ b/request.c @@ -144,7 +144,7 @@ void process_request(char *client_message, int *sock, char type) if(*sock != 0) { // Send EOM - seom(*sock); + seom(*sock, type); } } diff --git a/routines.h b/routines.h index 44db3760..e0339a0d 100644 --- a/routines.h +++ b/routines.h @@ -40,7 +40,7 @@ void memory_check(int which); void close_telnet_socket(void); void close_unix_socket(void); -void seom(int sock); +void seom(int sock, char type); void ssend(int sock, const char *format, ...); void *telnet_listening_thread(void *args); void *socket_listening_thread(void *args); diff --git a/socket.c b/socket.c index c9090291..9c070579 100644 --- a/socket.c +++ b/socket.c @@ -9,6 +9,7 @@ * Please see LICENSE file for your rights under this license. */ #include "FTL.h" +#include "api.h" // The backlog argument defines the maximum length // to which the queue of pending connections for @@ -158,9 +159,12 @@ void removeport(void) fclose(f); } -void seom(int sock) +void seom(int sock, char type) { - ssend(sock, "---EOM---\n\n"); + if(type == TELNET) + ssend(sock, "---EOM---\n\n"); + else + pack_eom(sock); } void ssend(int sock, const char *format, ...) diff --git a/socket_client.c b/socket_client.c index 080bf107..d8f77908 100644 --- a/socket_client.c +++ b/socket_client.c @@ -22,7 +22,8 @@ int main (int argc, char **argv) { int socketfd; char *buffer = malloc (BUF); struct sockaddr_un address; - int size, ret; + ssize_t size; + int ret; // Create socket socketfd = socket(PF_LOCAL, SOCK_STREAM, 0); @@ -58,19 +59,18 @@ int main (int argc, char **argv) { // Try to receive data until either recv() fails or we see "--EOM--" while((size = recv(socketfd, buffer, BUF-1, 0)) > -1) { - // Zero-terminate incoming message - if(size > 0) - buffer[size] = '\0'; - // Print received data to stdout - printf("%s", buffer); + for(int i = 0; i < size; ++i) { + printf("%02x ", (unsigned char) buffer[i]); + } // Exit on End Of Message - if(strstr(buffer, "--EOM--") != NULL) + if((unsigned char) buffer[size-1] == 0xc1) break; - } + printf("\n"); + // Close Unix socket connection close(socketfd); return EXIT_SUCCESS; diff --git a/test/test_suite.sh b/test/test_suite.sh index cbf410db..58fb7c44 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -201,17 +201,7 @@ load 'libs/bats-support/load' echo "output: ${lines[@]}" [[ ${lines[0]} == "Socket created" ]] [[ ${lines[1]} == "Connection established" ]] -# [[ ${lines[2]} == "domains_being_blocked -1" ]] -# [[ ${lines[3]} == "dns_queries_today 7" ]] -# [[ ${lines[4]} == "ads_blocked_today 2" ]] -# [[ ${lines[5]} == "ads_percentage_today 28.571428" ]] -# [[ ${lines[6]} == "unique_domains 6" ]] -# [[ ${lines[7]} == "queries_forwarded 3" ]] -# [[ ${lines[8]} == "queries_cached 2" ]] -# [[ ${lines[9]} == "clients_ever_seen 3" ]] -# [[ ${lines[10]} == "unique_clients 3" ]] -# [[ ${lines[11]} == "status unknown" ]] - [[ ${lines[2]} == "---EOM---" ]] + [[ ${lines[2]} == "d2 ff ff ff ff d2 00 00 00 07 d2 00 00 00 02 ca 41 e4 92 49 d2 00 00 00 06 d2 00 00 00 03 d2 00 00 00 02 d2 00 00 00 03 d2 00 00 00 03 cc 02 c1" ]] } @test "Final part of the tests: Killing pihole-FTL process" { From 439227a1a8f132c6fbf6b827da7d8bb40dfaebfa Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 4 Jan 2018 12:11:47 -0500 Subject: [PATCH 104/168] Don't declare variables in for loops Signed-off-by: Mcat12 --- socket_client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/socket_client.c b/socket_client.c index d8f77908..6df0fc27 100644 --- a/socket_client.c +++ b/socket_client.c @@ -60,7 +60,8 @@ int main (int argc, char **argv) { while((size = recv(socketfd, buffer, BUF-1, 0)) > -1) { // Print received data to stdout - for(int i = 0; i < size; ++i) { + int i; + for(i = 0; i < size; ++i) { printf("%02x ", (unsigned char) buffer[i]); } From b5c9c6e8093cbd724d1719bbebc7179da6a0eff5 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 4 Jan 2018 12:23:16 -0500 Subject: [PATCH 105/168] Fix socket test Signed-off-by: Mcat12 --- test/test_suite.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_suite.sh b/test/test_suite.sh index 58fb7c44..b2944033 100644 --- a/test/test_suite.sh +++ b/test/test_suite.sh @@ -201,7 +201,7 @@ load 'libs/bats-support/load' echo "output: ${lines[@]}" [[ ${lines[0]} == "Socket created" ]] [[ ${lines[1]} == "Connection established" ]] - [[ ${lines[2]} == "d2 ff ff ff ff d2 00 00 00 07 d2 00 00 00 02 ca 41 e4 92 49 d2 00 00 00 06 d2 00 00 00 03 d2 00 00 00 02 d2 00 00 00 03 d2 00 00 00 03 cc 02 c1" ]] + [[ ${lines[2]} == "d2 ff ff ff ff d2 00 00 00 07 d2 00 00 00 02 ca 41 e4 92 49 d2 00 00 00 06 d2 00 00 00 03 d2 00 00 00 02 d2 00 00 00 03 d2 00 00 00 03 cc 02 c1 " ]] } @test "Final part of the tests: Killing pihole-FTL process" { From 616b10a59918d299571b08a7bfc23148b399a5da Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 4 Jan 2018 12:28:40 -0500 Subject: [PATCH 106/168] Add swrite function It will print a warning if an error happens, like the other s* functions in socket.c Signed-off-by: Mcat12 --- msgpack.c | 6 +++--- routines.h | 1 + socket.c | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/msgpack.c b/msgpack.c index 81d1edc4..991747a8 100644 --- a/msgpack.c +++ b/msgpack.c @@ -14,12 +14,12 @@ void pack_eom(int sock) { // This byte is explicitly never used in the MessagePack spec, so it is perfect to use as an EOM for this API. unsigned char eom = 0xc1; - write(sock, &eom, sizeof(eom)); + swrite(sock, &eom, sizeof(eom)); } void pack_number(int sock, unsigned char format, void *value, size_t size) { - write(sock, &format, sizeof(format)); - write(sock, value, size); + swrite(sock, &format, sizeof(format)); + swrite(sock, value, size); } void pack_int(int sock, int value) { diff --git a/routines.h b/routines.h index e0339a0d..880e980e 100644 --- a/routines.h +++ b/routines.h @@ -42,6 +42,7 @@ void close_telnet_socket(void); void close_unix_socket(void); void seom(int sock, char type); void ssend(int sock, const char *format, ...); +void swrite(int sock, void *value, size_t size); void *telnet_listening_thread(void *args); void *socket_listening_thread(void *args); diff --git a/socket.c b/socket.c index 9c070579..8c29d366 100644 --- a/socket.c +++ b/socket.c @@ -179,6 +179,11 @@ void ssend(int sock, const char *format, ...) logg("WARNING: Socket write returned error code %i", errno); } +void swrite(int sock, void *value, size_t size) { + if(!write(sock, value, size)) + logg("WARNING: Socket write returned error code %i", errno); +} + int listener(int sockfd) { struct sockaddr_in cli_addr; From 203cc946bd05120e4d5dab693f410e7350486274 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 4 Jan 2018 19:31:23 +0100 Subject: [PATCH 107/168] Add istelnet array Signed-off-by: DL6ER --- FTL.h | 1 + socket.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/FTL.h b/FTL.h index 93bcb00b..e8de47f5 100644 --- a/FTL.h +++ b/FTL.h @@ -250,3 +250,4 @@ bool travis; bool DBdeleteoldqueries; bool rereadgravity; char * clientip[MAXCONNS]; +bool istelnet[MAXCONNS]; diff --git a/socket.c b/socket.c index 8c29d366..7887445e 100644 --- a/socket.c +++ b/socket.c @@ -23,6 +23,7 @@ // File descriptors int telnetfd, socketfd; +bool istelnet[MAXCONNS]; void saveport(int port) { @@ -232,6 +233,9 @@ void *telnet_connection_handler_thread(void *socket_desc) { //Get the socket descriptor int sock = *(int*)socket_desc; + // Set connection type to telnet + istelnet[sock] = true; + // Store copy only for displaying the debug messages int sockID = sock; char client_message[SOCKETBUFFERLEN] = ""; @@ -290,6 +294,8 @@ void *socket_connection_handler_thread(void *socket_desc) { //Get the socket descriptor int sock = *(int*)socket_desc; + // Set connection type to not telnet + istelnet[sock] = false; // Store copy only for displaying the debug messages int sockID = sock; char client_message[SOCKETBUFFERLEN] = ""; From f0793be61b65d7ebbefbdd4369a1f189ddaaaac2 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 4 Jan 2018 19:33:20 +0100 Subject: [PATCH 108/168] Use istelnet[] in getStats() Signed-off-by: DL6ER --- api.h | 2 +- api_stats.c | 6 +++--- request.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api.h b/api.h index b012cc93..1e925ba5 100644 --- a/api.h +++ b/api.h @@ -9,7 +9,7 @@ * Please see LICENSE file for your rights under this license. */ // Statistic methods -void getStats(int *sock, char type); +void getStats(int *sock); void getOverTime(int *sock, char type); void getTopDomains (char *client_message, int *sock, char type); void getTopClients(char *client_message, int *sock, char type); diff --git a/api_stats.c b/api_stats.c index 24b67063..5e74d57e 100644 --- a/api_stats.c +++ b/api_stats.c @@ -42,7 +42,7 @@ int cmpdesc(const void *a, const void *b) return 0; } -void getStats(int *sock, char type) +void getStats(int *sock) { int blocked = counters.blocked + counters.wildcardblocked; int total = counters.queries - counters.invalidqueries; @@ -59,7 +59,7 @@ void getStats(int *sock, char type) switch(blockingstatus) { case 0: // Blocking disabled - if(type == TELNET) + if(istelnet[*sock]) strncpy(domains_blocked, "N/A", 4); else strncpy(domains_blocked, "\"N/A\"", 6); @@ -85,7 +85,7 @@ void getStats(int *sock, char type) activeclients++; } - if(type == TELNET) { + if(istelnet[*sock]) { ssend(*sock, "domains_being_blocked %s\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", domains_blocked, total, blocked, percentage); ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", diff --git a/request.c b/request.c index 269185b7..f5396d84 100644 --- a/request.c +++ b/request.c @@ -21,7 +21,7 @@ void process_request(char *client_message, int *sock, char type) if(command(client_message, ">stats")) { processed = true; - getStats(sock, type); + getStats(sock); } else if(command(client_message, ">overTime")) { From e54b3913f9e6f6c17a71b334012e1177d67be78d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:14:36 -0500 Subject: [PATCH 109/168] Add socket implementation for getOverTime Adds the map16 structure to msgpack.c and tightens the pack_int signature to only accept int32_t types. Signed-off-by: Mcat12 --- api.h | 3 ++- api_stats.c | 67 +++++++++++++++++++++++------------------------------ msgpack.c | 9 ++++++- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/api.h b/api.h index 1e925ba5..c8a1b616 100644 --- a/api.h +++ b/api.h @@ -41,6 +41,7 @@ bool isValidDomain(char *domain); // MessagePack serialization helpers void pack_eom(int sock); -void pack_int(int sock, int value); +void pack_int32(int sock, int32_t value); void pack_float(int sock, float value); void pack_unsigned_char(int sock, unsigned char value); +void pack_map16_start(int sock, uint16_t length); diff --git a/api_stats.c b/api_stats.c index 5e74d57e..90204731 100644 --- a/api_stats.c +++ b/api_stats.c @@ -96,15 +96,15 @@ void getStats(int *sock) } else { - pack_int(*sock, counters.gravity); - pack_int(*sock, total); - pack_int(*sock, blocked); + pack_int32(*sock, counters.gravity); + pack_int32(*sock, total); + pack_int32(*sock, blocked); pack_float(*sock, percentage); - pack_int(*sock, counters.domains); - pack_int(*sock, counters.forwardedqueries); - pack_int(*sock, counters.cached); - pack_int(*sock, counters.clients); - pack_int(*sock, activeclients); + pack_int32(*sock, counters.domains); + pack_int32(*sock, counters.forwardedqueries); + pack_int32(*sock, counters.cached); + pack_int32(*sock, counters.clients); + pack_int32(*sock, activeclients); pack_unsigned_char(*sock, blockingstatus); } @@ -117,45 +117,36 @@ void getOverTime(int *sock, char type) int i, j = 9999999; // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) - for(i=0; i < counters.overTime; i++) - { + for(i=0; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(overTime[i].total > 0 || overTime[i].blocked > 0) - { + if(overTime[i].total > 0 || overTime[i].blocked > 0) { j = i; break; } } - // Send data in socket format if requested - if(type == TELNET) - { - for(i = j; i < counters.overTime; i++) - { + if(type == TELNET) { + for(i = j; i < counters.overTime; i++) { ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); } } - else - { -// // First send header with unspecified content-length outside of the for-loop -// sendAPIResponse(*sock, type, OK); -// ssend(*sock,"\"domains_over_time\":{"); -// -// // Send "domains_over_time" data -// for(i = j; i < counters.overTime; i++) -// { -// if(i != j) ssend(*sock, ","); -// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].total); -// } -// ssend(*sock,"},\"ads_over_time\":{"); -// -// // Send "ads_over_time" data -// for(i = j; i < counters.overTime; i++) -// { -// if(i != j) ssend(*sock, ","); -// ssend(*sock,"\"%i\":%i",overTime[i].timestamp,overTime[i].blocked); -// } -// ssend(*sock,"}"); + else { + // We can use the map16 type because there should only be about 288 time slots (TIMEFRAME set to "yesterday") + // and map16 can hold up to (2^16)-1 = 65535 pairs + + // Send domains over time + pack_map16_start(*sock, (uint16_t) (counters.overTime - j)); + for(i = j; i < counters.overTime; i++) { + pack_int32(*sock, overTime[i].timestamp); + pack_int32(*sock, overTime[i].total); + } + + // Send ads over time + pack_map16_start(*sock, (uint16_t) (counters.overTime - j)); + for(i = j; i < counters.overTime; i++) { + pack_int32(*sock, overTime[i].timestamp); + pack_int32(*sock, overTime[i].blocked); + } } if(debugclients) diff --git a/msgpack.c b/msgpack.c index 991747a8..5b79e8cf 100644 --- a/msgpack.c +++ b/msgpack.c @@ -22,7 +22,7 @@ void pack_number(int sock, unsigned char format, void *value, size_t size) { swrite(sock, value, size); } -void pack_int(int sock, int value) { +void pack_int32(int sock, int32_t value) { uint32_t bigEValue = htonl((uint32_t) value); pack_number(sock, 0xd2, &bigEValue, sizeof(bigEValue)); } @@ -38,3 +38,10 @@ void pack_float(int sock, float value) { void pack_unsigned_char(int sock, unsigned char value) { pack_number(sock, 0xcc, &value, sizeof(value)); } + +void pack_map16_start(int sock, uint16_t length) { + unsigned char format = 0xde; + swrite(sock, &format, sizeof(format)); + uint16_t bigELength = htons(length); + swrite(sock, &bigELength, sizeof(bigELength)); +} From c0022a95c9701bdc7b8957a106b2f3cb825e02ed Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:15:55 -0500 Subject: [PATCH 110/168] Use istelnet for getOverTime Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 4 ++-- request.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api.h b/api.h index c8a1b616..6dc8485d 100644 --- a/api.h +++ b/api.h @@ -10,7 +10,7 @@ // Statistic methods void getStats(int *sock); -void getOverTime(int *sock, char type); +void getOverTime(int *sock); void getTopDomains (char *client_message, int *sock, char type); void getTopClients(char *client_message, int *sock, char type); void getForwardDestinations(char *client_message, int *sock, char type); diff --git a/api_stats.c b/api_stats.c index 90204731..b061b249 100644 --- a/api_stats.c +++ b/api_stats.c @@ -112,7 +112,7 @@ void getStats(int *sock) logg("Sent stats data to client, ID: %i", *sock); } -void getOverTime(int *sock, char type) +void getOverTime(int *sock) { int i, j = 9999999; @@ -125,7 +125,7 @@ void getOverTime(int *sock, char type) } } - if(type == TELNET) { + if(istelnet[*sock]) { for(i = j; i < counters.overTime; i++) { ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); } diff --git a/request.c b/request.c index f5396d84..a3cff418 100644 --- a/request.c +++ b/request.c @@ -26,7 +26,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">overTime")) { processed = true; - getOverTime(sock, type); + getOverTime(sock); } else if(command(client_message, ">top-domains") || command(client_message, ">top-ads")) { From adfcaccb3641ae3933ea63d08b0fa96b028229f3 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:36:19 -0500 Subject: [PATCH 111/168] Use istelnet for getAllQueries Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 25 +++++++++---------------- request.c | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/api.h b/api.h index 6dc8485d..599c6416 100644 --- a/api.h +++ b/api.h @@ -15,7 +15,7 @@ void getTopDomains (char *client_message, int *sock, char type); void getTopClients(char *client_message, int *sock, char type); void getForwardDestinations(char *client_message, int *sock, char type); void getQueryTypes(int *sock, char type); -void getAllQueries(char *client_message, int *sock, char type); +void getAllQueries(char *client_message, int *sock); void getRecentBlocked(char *client_message, int *sock, char type); void getForwardDestinationsOverTime(int *sock, char type); void getQueryTypesOverTime(int *sock, char type); diff --git a/api_stats.c b/api_stats.c index b061b249..a8865bb1 100644 --- a/api_stats.c +++ b/api_stats.c @@ -42,8 +42,7 @@ int cmpdesc(const void *a, const void *b) return 0; } -void getStats(int *sock) -{ +void getStats(int *sock) { int blocked = counters.blocked + counters.wildcardblocked; int total = counters.queries - counters.invalidqueries; float percentage = 0.0; @@ -56,8 +55,7 @@ void getStats(int *sock) char domains_blocked[11]; char status[9]; - switch(blockingstatus) - { + switch(blockingstatus) { case 0: // Blocking disabled if(istelnet[*sock]) strncpy(domains_blocked, "N/A", 4); @@ -78,8 +76,7 @@ void getStats(int *sock) // unique_clients: count only clients that have been active within the most recent 24 hours int i, activeclients = 0; - for(i=0; i < counters.clients; i++) - { + for(i=0; i < counters.clients; i++) { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); if(clients[i].count > 0) activeclients++; @@ -94,8 +91,7 @@ void getStats(int *sock) ssend(*sock, "unique_clients %i\n", activeclients); ssend(*sock, "status %s\n", status); } - else - { + else { pack_int32(*sock, counters.gravity); pack_int32(*sock, total); pack_int32(*sock, blocked); @@ -112,8 +108,7 @@ void getStats(int *sock) logg("Sent stats data to client, ID: %i", *sock); } -void getOverTime(int *sock) -{ +void getOverTime(int *sock) { int i, j = 9999999; // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) @@ -616,9 +611,7 @@ void getQueryTypes(int *sock, char type) } -void getAllQueries(char *client_message, int *sock, char type) -{ - +void getAllQueries(char *client_message, int *sock) { // Exit before processing any data if requested via config setting if(!config.query_display) return; @@ -632,7 +625,7 @@ void getAllQueries(char *client_message, int *sock, char type) char *clientname = NULL; bool filterclientname = false; - if(type == TELNET) + if(istelnet[*sock]) { // Time filtering? if(command(client_message, ">getallqueries-time")) @@ -701,7 +694,7 @@ void getAllQueries(char *client_message, int *sock, char type) int ibeg = 0, num; // Test for integer that specifies number of entries to be shown - if(type == TELNET) + if(istelnet[*sock]) { if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { @@ -817,7 +810,7 @@ void getAllQueries(char *client_message, int *sock, char type) continue; } - if(type == TELNET) + if(istelnet[*sock]) { if(!privacymode) { diff --git a/request.c b/request.c index a3cff418..2293f636 100644 --- a/request.c +++ b/request.c @@ -56,7 +56,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">getallqueries")) { processed = true; - getAllQueries(client_message, sock, type); + getAllQueries(client_message, sock); } else if(command(client_message, ">recentBlocked")) { From c6369ddbbed55b41d1ac9776f15073b99b8e720d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:41:45 -0500 Subject: [PATCH 112/168] Remove old HTTP argument parsing from getAllQueries Signed-off-by: Mcat12 --- api_stats.c | 106 +++++++++------------------------------------------- 1 file changed, 18 insertions(+), 88 deletions(-) diff --git a/api_stats.c b/api_stats.c index a8865bb1..4ad37451 100644 --- a/api_stats.c +++ b/api_stats.c @@ -625,100 +625,30 @@ void getAllQueries(char *client_message, int *sock) { char *clientname = NULL; bool filterclientname = false; - if(istelnet[*sock]) - { - // Time filtering? - if(command(client_message, ">getallqueries-time")) - { - sscanf(client_message, ">getallqueries-time %i %i",&from, &until); - } - // Domain filtering? - if(command(client_message, ">getallqueries-domain")) - { - sscanf(client_message, ">getallqueries-domain %ms", &domainname); - filterdomainname = true; - } - // Client filtering? - if(command(client_message, ">getallqueries-client")) - { - sscanf(client_message, ">getallqueries-client %ms", &clientname); - filterclientname = true; - } + // Time filtering? + if(command(client_message, ">getallqueries-time")) { + sscanf(client_message, ">getallqueries-time %i %i",&from, &until); } - else - { - // Time filtering? - const char * temp = strstr(client_message, "from="); - if(temp != NULL) - { - int num; - if(sscanf(temp, "from=%i", &num) > 0) - { - // User wants a different number of requests - from = num; - } - } - temp = strstr(client_message, "until="); - if(temp != NULL) - { - int num; - if(sscanf(temp, "until=%i", &num) > 0) - { - // User wants a different number of requests - until = num; - } - } - - // Domain filtering? - temp = strstr(client_message, "domain="); - if(temp != NULL) - { - char *temp2 = strdup(temp); - temp2[strcspn(temp2, "&")] = 0; - sscanf(temp2, "domain=%ms", &domainname); - free(temp2); - filterdomainname = true; - } - temp = strstr(client_message, "client="); - - // Client filtering? - if(temp != NULL) - { - char *temp2 = strdup(temp); - temp2[strcspn(temp2, "&")] = 0; - sscanf(temp2, "client=%ms", &clientname); - free(temp2); - filterclientname = true; - } + // Domain filtering? + if(command(client_message, ">getallqueries-domain")) { + sscanf(client_message, ">getallqueries-domain %ms", &domainname); + filterdomainname = true; + } + // Client filtering? + if(command(client_message, ">getallqueries-client")) { + sscanf(client_message, ">getallqueries-client %ms", &clientname); + filterclientname = true; } int ibeg = 0, num; // Test for integer that specifies number of entries to be shown - if(istelnet[*sock]) + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - // Don't allow a start index that is smaller than zero - ibeg = counters.queries-num; - if(ibeg < 0) - ibeg = 0; - } - } + // User wants a different number of requests + // Don't allow a start index that is smaller than zero + ibeg = counters.queries-num; + if(ibeg < 0) + ibeg = 0; } // Get potentially existing filtering flags From 4eaf794b633664954637d1788d59e7779a78e8bc Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:48:43 -0500 Subject: [PATCH 113/168] Tighten parameters on pack_unsigned_char Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 2 +- msgpack.c | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api.h b/api.h index 599c6416..26e387bc 100644 --- a/api.h +++ b/api.h @@ -43,5 +43,5 @@ bool isValidDomain(char *domain); void pack_eom(int sock); void pack_int32(int sock, int32_t value); void pack_float(int sock, float value); -void pack_unsigned_char(int sock, unsigned char value); +void pack_uint8(int sock, uint8_t value); void pack_map16_start(int sock, uint16_t length); diff --git a/api_stats.c b/api_stats.c index 4ad37451..bd3c729b 100644 --- a/api_stats.c +++ b/api_stats.c @@ -101,7 +101,7 @@ void getStats(int *sock) { pack_int32(*sock, counters.cached); pack_int32(*sock, counters.clients); pack_int32(*sock, activeclients); - pack_unsigned_char(*sock, blockingstatus); + pack_uint8(*sock, blockingstatus); } if(debugclients) diff --git a/msgpack.c b/msgpack.c index 5b79e8cf..d5a7fabd 100644 --- a/msgpack.c +++ b/msgpack.c @@ -22,6 +22,10 @@ void pack_number(int sock, unsigned char format, void *value, size_t size) { swrite(sock, value, size); } +void pack_uint8(int sock, uint8_t value) { + pack_number(sock, 0xcc, &value, sizeof(value)); +} + void pack_int32(int sock, int32_t value) { uint32_t bigEValue = htonl((uint32_t) value); pack_number(sock, 0xd2, &bigEValue, sizeof(bigEValue)); @@ -35,10 +39,6 @@ void pack_float(int sock, float value) { pack_number(sock, 0xca, &bigEValue, sizeof(bigEValue)); } -void pack_unsigned_char(int sock, unsigned char value) { - pack_number(sock, 0xcc, &value, sizeof(value)); -} - void pack_map16_start(int sock, uint16_t length) { unsigned char format = 0xde; swrite(sock, &format, sizeof(format)); From 4386138ddd3199f8e697e6c810522d38cedb937a Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 5 Jan 2018 22:50:43 -0500 Subject: [PATCH 114/168] Use uint8_t instead of unsigned char in msgpack.c Signed-off-by: Mcat12 --- msgpack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msgpack.c b/msgpack.c index d5a7fabd..ca8c694c 100644 --- a/msgpack.c +++ b/msgpack.c @@ -13,11 +13,11 @@ void pack_eom(int sock) { // This byte is explicitly never used in the MessagePack spec, so it is perfect to use as an EOM for this API. - unsigned char eom = 0xc1; + uint8_t eom = 0xc1; swrite(sock, &eom, sizeof(eom)); } -void pack_number(int sock, unsigned char format, void *value, size_t size) { +void pack_number(int sock, uint8_t format, void *value, size_t size) { swrite(sock, &format, sizeof(format)); swrite(sock, value, size); } @@ -40,7 +40,7 @@ void pack_float(int sock, float value) { } void pack_map16_start(int sock, uint16_t length) { - unsigned char format = 0xde; + uint8_t format = 0xde; swrite(sock, &format, sizeof(format)); uint16_t bigELength = htons(length); swrite(sock, &bigELength, sizeof(bigELength)); From 348a2ee81e902e3176d4bcf502d9e2e38c71264b Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 6 Jan 2018 00:46:20 -0500 Subject: [PATCH 115/168] Add socket implementation for getAllQueries Signed-off-by: Mcat12 --- api.h | 5 ++++- api_stats.c | 44 +++++++++++++++++++++++++------------------- msgpack.c | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/api.h b/api.h index 26e387bc..6e719cca 100644 --- a/api.h +++ b/api.h @@ -41,7 +41,10 @@ bool isValidDomain(char *domain); // MessagePack serialization helpers void pack_eom(int sock); +void pack_uint8(int sock, uint8_t value); void pack_int32(int sock, int32_t value); void pack_float(int sock, float value); -void pack_uint8(int sock, uint8_t value); +void pack_fixstr(int sock, char *string); +void pack_str32(int sock, char *string); +void pack_fixarray(int sock, uint8_t length); void pack_map16_start(int sock, uint16_t length); diff --git a/api_stats.c b/api_stats.c index bd3c729b..27f87e53 100644 --- a/api_stats.c +++ b/api_stats.c @@ -694,12 +694,6 @@ void getAllQueries(char *client_message, int *sock) { logg("Privacy mode enabled"); } -// if(type != TELNET) -// { -// sendAPIResponse(*sock, type, OK); -// ssend(*sock, "\"history\":["); -// } - int i; bool first = true; for(i=ibeg; i < counters.queries; i++) { @@ -754,19 +748,31 @@ void getAllQueries(char *client_message, int *sock) { } else { -// // {"data":[["1497351662","IPv4","clients4.google.com","10.8.0.2",2,1], -// if(!first) ssend(*sock, ","); -// first = false; -// -// if(!privacymode) -// { -// if(strlen(clients[queries[i].clientID].name) > 0) -// ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,domains[queries[i].domainID].dnssec); -// else -// ssend(*sock,"[%i,\"%s\",\"%s\",\"%s\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,domains[queries[i].domainID].dnssec); -// } -// else -// ssend(*sock,"[%i,\"%s\",\"%s\",\"hidden\",%i,%i]",queries[i].timestamp,qtype,domains[queries[i].domainID].domain,queries[i].status,domains[queries[i].domainID].dnssec); + char *client; + + if(!privacymode) { + if(strlen(clients[queries[i].clientID].name) > 0) + client = clients[queries[i].clientID].name; + else + client = clients[queries[i].clientID].ip; + } + else + client = "hidden"; + + // Use a fixarray because the length of the array will only be length 6 (max is 15 for fixarray) + pack_fixarray(*sock, 6); + + pack_int32(*sock, queries[i].timestamp); + + // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) + pack_fixstr(*sock, qtype); + + // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) + pack_str32(*sock, domains[queries[i].domainID].domain); + pack_str32(*sock, client); + + pack_uint8(*sock, queries[i].status); + pack_uint8(*sock, domains[queries[i].domainID].dnssec); } } diff --git a/msgpack.c b/msgpack.c index ca8c694c..b9729e34 100644 --- a/msgpack.c +++ b/msgpack.c @@ -17,18 +17,18 @@ void pack_eom(int sock) { swrite(sock, &eom, sizeof(eom)); } -void pack_number(int sock, uint8_t format, void *value, size_t size) { +void pack_basic(int sock, uint8_t format, void *value, size_t size) { swrite(sock, &format, sizeof(format)); swrite(sock, value, size); } void pack_uint8(int sock, uint8_t value) { - pack_number(sock, 0xcc, &value, sizeof(value)); + pack_basic(sock, 0xcc, &value, sizeof(value)); } void pack_int32(int sock, int32_t value) { uint32_t bigEValue = htonl((uint32_t) value); - pack_number(sock, 0xd2, &bigEValue, sizeof(bigEValue)); + pack_basic(sock, 0xd2, &bigEValue, sizeof(bigEValue)); } void pack_float(int sock, float value) { @@ -36,7 +36,48 @@ void pack_float(int sock, float value) { uint32_t bigEValue; memcpy(&bigEValue, &value, sizeof(bigEValue)); bigEValue = htonl(bigEValue); - pack_number(sock, 0xca, &bigEValue, sizeof(bigEValue)); + pack_basic(sock, 0xca, &bigEValue, sizeof(bigEValue)); +} + +void pack_fixstr(int sock, char *string) { + // Make sure that the length is less than 32 + size_t length = strlen(string); + + if(length >= 32) { + logg("Tried to send a fixstr longer than 31 bytes!"); + exit(EXIT_FAILURE); + } + + uint8_t format = (uint8_t) (0xA0 | length); + swrite(sock, &format, sizeof(format)); + swrite(sock, string, length); +} + +void pack_str32(int sock, char *string) { + // Make sure that the length is less than 4294967296 + size_t length = strlen(string); + + if(length >= 4294967296) { + logg("Tried to send a str32 longer than 4294967295 bytes!"); + exit(EXIT_FAILURE); + } + + uint8_t format = 0xdb; + swrite(sock, &format, sizeof(format)); + uint32_t bigELength = htonl((uint32_t) length); + swrite(sock, &bigELength, sizeof(bigELength)); + swrite(sock, string, length); +} + +void pack_fixarray(int sock, uint8_t length) { + // Make sure that the length is less than 16 + if(length >= 16) { + logg("Tried to send a fixarray longer than 15 elements!"); + exit(EXIT_FAILURE); + } + + uint8_t format = (uint8_t) (0x90 | length); + swrite(sock, &format, sizeof(format)); } void pack_map16_start(int sock, uint16_t length) { From 7d3dfb635136d3b665540a6412da88fd26287867 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 6 Jan 2018 12:32:45 -0500 Subject: [PATCH 116/168] Don't send fixarray for getAllQueries It's unnecessary Signed-off-by: Mcat12 --- api.h | 1 - api_stats.c | 3 --- msgpack.c | 11 ----------- 3 files changed, 15 deletions(-) diff --git a/api.h b/api.h index 6e719cca..91a79737 100644 --- a/api.h +++ b/api.h @@ -46,5 +46,4 @@ void pack_int32(int sock, int32_t value); void pack_float(int sock, float value); void pack_fixstr(int sock, char *string); void pack_str32(int sock, char *string); -void pack_fixarray(int sock, uint8_t length); void pack_map16_start(int sock, uint16_t length); diff --git a/api_stats.c b/api_stats.c index 27f87e53..056b5a67 100644 --- a/api_stats.c +++ b/api_stats.c @@ -759,9 +759,6 @@ void getAllQueries(char *client_message, int *sock) { else client = "hidden"; - // Use a fixarray because the length of the array will only be length 6 (max is 15 for fixarray) - pack_fixarray(*sock, 6); - pack_int32(*sock, queries[i].timestamp); // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) diff --git a/msgpack.c b/msgpack.c index b9729e34..edb83b42 100644 --- a/msgpack.c +++ b/msgpack.c @@ -69,17 +69,6 @@ void pack_str32(int sock, char *string) { swrite(sock, string, length); } -void pack_fixarray(int sock, uint8_t length) { - // Make sure that the length is less than 16 - if(length >= 16) { - logg("Tried to send a fixarray longer than 15 elements!"); - exit(EXIT_FAILURE); - } - - uint8_t format = (uint8_t) (0x90 | length); - swrite(sock, &format, sizeof(format)); -} - void pack_map16_start(int sock, uint16_t length) { uint8_t format = 0xde; swrite(sock, &format, sizeof(format)); From e07786e3b614c3658a08a1e5146558465ac3a17c Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 6 Jan 2018 19:27:11 -0500 Subject: [PATCH 117/168] Remove dead code in getAllQueries Signed-off-by: Mcat12 --- api_stats.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/api_stats.c b/api_stats.c index 056b5a67..600e90f7 100644 --- a/api_stats.c +++ b/api_stats.c @@ -773,9 +773,6 @@ void getAllQueries(char *client_message, int *sock) { } } -// if(type != TELNET) -// ssend(*sock, "]"); - // Free allocated memory if(filterclientname) free(clientname); From 05f86041cff67b71eb28db4a15cb0d5d42a8d76b Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 6 Jan 2018 22:28:41 -0500 Subject: [PATCH 118/168] Add socket implementation to getTopDomains and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 125 +++++++++++++++------------------------------------- request.c | 2 +- 3 files changed, 37 insertions(+), 92 deletions(-) diff --git a/api.h b/api.h index 91a79737..78a2767b 100644 --- a/api.h +++ b/api.h @@ -11,7 +11,7 @@ // Statistic methods void getStats(int *sock); void getOverTime(int *sock); -void getTopDomains (char *client_message, int *sock, char type); +void getTopDomains(char *client_message, int *sock); void getTopClients(char *client_message, int *sock, char type); void getForwardDestinations(char *client_message, int *sock, char type); void getQueryTypes(int *sock, char type); diff --git a/api_stats.c b/api_stats.c index 600e90f7..77fa7f22 100644 --- a/api_stats.c +++ b/api_stats.c @@ -148,62 +148,35 @@ void getOverTime(int *sock) { logg("Sent overTime data to client, ID: %i", *sock); } -void getTopDomains(char *client_message, int *sock, char type) +void getTopDomains(char *client_message, int *sock) { int i, temparray[counters.domains][2], count=10, num; bool blocked, audit = false, desc = false; - if(type == TELNET) - blocked = command(client_message, ">top-ads"); - else - blocked = command(client_message, "/top_ads"); + blocked = command(client_message, ">top-ads"); // Exit before processing any data if requested via config setting if(!config.query_display) return; // Match both top-domains and top-ads - // TELNET: >top-domains (15) - // API: /stats/top_domains?limit=15 - if(type == TELNET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } + // example: >top-domains (15) + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + count = num; } // Apply Audit Log filtering? - // TELNET: >top-domains for audit - // API: /stats/top_domains?audit - if(type == TELNET && command(client_message, " for audit")) - audit = true; - else if(type != TELNET && command(client_message, "audit")) + // example: >top-domains for audit + if(command(client_message, " for audit")) audit = true; // Sort in descending order? - // TELNET: >top-domains desc - // API: /stats/top_domains?order=desc - if(type == TELNET && command(client_message, " desc")) - desc = true; - else if(type != TELNET && command(client_message, "order=desc")) + // example: >top-domains desc + if(command(client_message, " desc")) desc = true; - for(i=0; i < counters.domains; i++) - { + for(i=0; i < counters.domains; i++) { validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); temparray[i][0] = i; if(blocked) @@ -223,14 +196,12 @@ void getTopDomains(char *client_message, int *sock, char type) // Get filter char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); bool showpermitted = true, showblocked = true; - if(filter != NULL) - { + if(filter != NULL) { if((strcmp(filter, "permittedonly")) == 0) showblocked = false; else if((strcmp(filter, "blockedonly")) == 0) showpermitted = false; - else if((strcmp(filter, "nothing")) == 0) - { + else if((strcmp(filter, "nothing")) == 0) { showpermitted = false; showblocked = false; } @@ -239,11 +210,9 @@ void getTopDomains(char *client_message, int *sock, char type) // Get domains which the user doesn't want to see char * excludedomains = NULL; - if(!audit) - { + if(!audit) { excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); - if(excludedomains != NULL) - { + if(excludedomains != NULL) { getSetupVarsArray(excludedomains); if(debugclients) @@ -251,85 +220,61 @@ void getTopDomains(char *client_message, int *sock, char type) } } - if(type != TELNET) - { -// // First send header with unspecified content-length outside of the for-loop -// sendAPIResponse(*sock, type, OK); -// -// if(blocked) -// ssend(*sock, "\"top_ads\":{"); -// else -// ssend(*sock, "\"top_domains\":{"); + if(!istelnet[*sock]) { + // Send the data required to get the percentage each domain has been blocked / queried + if(blocked) + pack_int32(*sock, counters.blocked); + else + pack_int32(*sock, counters.queries - counters.invalidqueries); } int skip = 0; bool first = true; - for(i=0; i < min(counters.domains, count+skip); i++) - { + for(i=0; i < min(counters.domains, count+skip); i++) { // Get sorted indices int j = temparray[counters.domains-i-1][0]; validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); // Skip this domain if there is a filter on it - if(excludedomains != NULL) - { - if(insetupVarsArray(domains[j].domain)) - { + if(excludedomains != NULL) { + if(insetupVarsArray(domains[j].domain)) { skip++; continue; } } // Skip this domain if already included in audit - if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) - { + if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) { skip++; continue; } - if(blocked && showblocked && domains[j].blockedcount > 0) - { - if(type == TELNET) - { + if(blocked && showblocked && domains[j].blockedcount > 0) { + if(istelnet[*sock]) { if(audit && domains[j].wildcard) ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); else ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); } - else - { -// if(!first) ssend(*sock,","); -// first = false; -// ssend(*sock,"\"%s\":%i", domains[j].domain, domains[j].blockedcount); + else { + pack_str32(*sock, domains[j].domain); + pack_int32(*sock, domains[j].blockedcount); } } else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) { - if(type == TELNET) - { + if(istelnet[*sock]) ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); - } - else - { -// if(!first) ssend(*sock,","); -// first = false; -// ssend(*sock,"\"%s\":%i", domains[j].domain, (domains[j].count - domains[j].blockedcount)); + else { + pack_str32(*sock, domains[j].domain); + pack_int32(*sock, domains[j].count - domains[j].blockedcount); } } } - if(type != TELNET) - { -// if(blocked) -// ssend(*sock,"},\"ads_blocked_today\":%i", counters.blocked); -// else -// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); - } - if(excludedomains != NULL) clearSetupVarsArray(); - if(debugclients) - { + if(debugclients) { if(blocked) logg("Sent top ads list data to client, ID: %i", *sock); else diff --git a/request.c b/request.c index 2293f636..e672df66 100644 --- a/request.c +++ b/request.c @@ -31,7 +31,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">top-domains") || command(client_message, ">top-ads")) { processed = true; - getTopDomains(client_message, sock, type); + getTopDomains(client_message, sock); } else if(command(client_message, ">top-clients")) { From 00f88772322ef2c0187ca36eeaf577ed7fc8ad0e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 6 Jan 2018 23:10:35 -0500 Subject: [PATCH 119/168] Add socket implementation to getTopClients and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 80 ++++++++++++++--------------------------------------- request.c | 2 +- 3 files changed, 23 insertions(+), 61 deletions(-) diff --git a/api.h b/api.h index 78a2767b..d64c90c3 100644 --- a/api.h +++ b/api.h @@ -12,7 +12,7 @@ void getStats(int *sock); void getOverTime(int *sock); void getTopDomains(char *client_message, int *sock); -void getTopClients(char *client_message, int *sock, char type); +void getTopClients(char *client_message, int *sock); void getForwardDestinations(char *client_message, int *sock, char type); void getQueryTypes(int *sock, char type); void getAllQueries(char *client_message, int *sock); diff --git a/api_stats.c b/api_stats.c index 77fa7f22..e1ff5dc6 100644 --- a/api_stats.c +++ b/api_stats.c @@ -282,48 +282,25 @@ void getTopDomains(char *client_message, int *sock) } } -void getTopClients(char *client_message, int *sock, char type) +void getTopClients(char *client_message, int *sock) { int i, temparray[counters.clients][2], count=10, num; // Match both top-domains and top-ads - // TELNET: >top-clients (15) - // API: /stats/top_clients?limit=15 - if(type == TELNET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - count = num; - } - } + // example: >top-clients (15) + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + count = num; } // Show also clients which have not been active recently? // This option can be combined with existing options, // i.e. both >top-clients withzero" and ">top-clients withzero (123)" are valid bool includezeroclients = false; - if(type == TELNET) { - if(command(client_message, " withzero")) { - includezeroclients = true; - } - } - else - includezeroclients = strstr(client_message, "withzero") != NULL; + if(command(client_message, " withzero")) + includezeroclients = true; - for(i=0; i < counters.clients; i++) - { + for(i=0; i < counters.clients; i++) { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); temparray[i][0] = i; temparray[i][1] = clients[i].count; @@ -334,34 +311,28 @@ void getTopClients(char *client_message, int *sock, char type) // Get clients which the user doesn't want to see char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - if(excludeclients != NULL) - { + if(excludeclients != NULL) { getSetupVarsArray(excludeclients); if(debugclients) logg("Excluding %i clients from being displayed", setupVarsElements); } - if(type != TELNET) - { -// // First send header with unspecified content-length outside of the for-loop -// sendAPIResponse(*sock, type, OK); -// ssend(*sock, "\"top_clients\":{"); + if(!istelnet[*sock]) { + // Send the total queries so they can make percentages from this data + pack_int32(*sock, counters.queries - counters.invalidqueries); } int skip = 0; bool first = true; - for(i=0; i < min(counters.clients, count+skip); i++) - { + for(i=0; i < min(counters.clients, count+skip); i++) { // Get sorted indices int j = temparray[counters.clients-i-1][0]; validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); // Skip this client if there is a filter on it - if(excludeclients != NULL) - { + if(excludeclients != NULL) { if(insetupVarsArray(clients[j].ip) || - insetupVarsArray(clients[j].name)) - { + insetupVarsArray(clients[j].name)) { skip++; continue; } @@ -370,26 +341,17 @@ void getTopClients(char *client_message, int *sock, char type) // Return this client if either // - "withzero" option is set, and/or // - the client made at least one query within the most recent 24 hours - if(includezeroclients || clients[j].count > 0) { - if(type == TELNET) - { + if(includezeroclients || clients[j].count > 0) { + if(istelnet[*sock]) ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); - } - else - { -// if(!first) ssend(*sock,","); -// first = false; -// if(strlen(clients[j].name) > 0) -// ssend(*sock,"\"%s|%s\":%i", clients[j].name, clients[j].ip, clients[j].count); -// else -// ssend(*sock,"\"%s\":%i", clients[j].ip, clients[j].count); + else { + pack_str32(*sock, clients[j].name); + pack_str32(*sock, clients[j].ip); + pack_int32(*sock, clients[j].count); } } } -// if(type != TELNET) -// ssend(*sock,"},\"dns_queries_today\":%i", (counters.queries - counters.invalidqueries)); - if(excludeclients != NULL) clearSetupVarsArray(); diff --git a/request.c b/request.c index e672df66..1e0114f0 100644 --- a/request.c +++ b/request.c @@ -36,7 +36,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">top-clients")) { processed = true; - getTopClients(client_message, sock, type); + getTopClients(client_message, sock); } else if(command(client_message, ">forward-dest")) { From 33dd350370a71ae16000b7dc52c300111faca3c4 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 14:53:11 -0500 Subject: [PATCH 120/168] Add socket implementation to getForwardDestinations and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 48 ++++++++++++------------------------------------ request.c | 4 ++-- 3 files changed, 15 insertions(+), 39 deletions(-) diff --git a/api.h b/api.h index d64c90c3..b7342dc2 100644 --- a/api.h +++ b/api.h @@ -13,7 +13,7 @@ void getStats(int *sock); void getOverTime(int *sock); void getTopDomains(char *client_message, int *sock); void getTopClients(char *client_message, int *sock); -void getForwardDestinations(char *client_message, int *sock, char type); +void getForwardDestinations(char *client_message, int *sock); void getQueryTypes(int *sock, char type); void getAllQueries(char *client_message, int *sock); void getRecentBlocked(char *client_message, int *sock, char type); diff --git a/api_stats.c b/api_stats.c index e1ff5dc6..762d5c75 100644 --- a/api_stats.c +++ b/api_stats.c @@ -360,14 +360,12 @@ void getTopClients(char *client_message, int *sock) } -void getForwardDestinations(char *client_message, int *sock, char type) +void getForwardDestinations(char *client_message, int *sock) { bool allocated = false, first = true, sort = true; int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; - if(type == TELNET && command(client_message, "unsorted")) - sort = false; - else if(strstr(client_message, "unsorted")) + if(command(client_message, "unsorted")) sort = false; for(i=0; i < counters.forwarded; i++) { @@ -394,16 +392,8 @@ void getForwardDestinations(char *client_message, int *sock, char type) totalqueries = counters.forwardedqueries + counters.cached + counters.blocked; - // Send HTTP headers with unknown content length -// sendAPIResponse(*sock, type, OK); - - // Send initial JSON output -// if(type != TELNET) -// ssend(*sock, "\"forward_destinations\":{"); - // Loop over available forward destinations - for(i=0; i < min(counters.forwarded+1, 10); i++) - { + for(i=0; i < min(counters.forwarded+1, 10); i++) { char *name, *ip; double percentage; @@ -415,8 +405,7 @@ void getForwardDestinations(char *client_message, int *sock, char type) j = i; // Is this the "local" forward destination? - if(j == counters.forwarded) - { + if(j == counters.forwarded) { ip = calloc(4,1); strcpy(ip, "::1"); name = calloc(6,1); @@ -430,8 +419,7 @@ void getForwardDestinations(char *client_message, int *sock, char type) allocated = true; } - else - { + else { validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); ip = forwarded[j].ip; name = forwarded[j].name; @@ -461,35 +449,23 @@ void getForwardDestinations(char *client_message, int *sock, char type) } // Send data if count > 0 - if(percentage > 0.0) - { - if(type == TELNET) - { + if(percentage > 0.0) { + if(istelnet[*sock]) ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); - } - else - { -// if(!first) ssend(*sock, ","); -// first = false; -// -// if(strlen(name) > 0) -// ssend(*sock, "\"%s|%s\":%.2f", name, ip, percentage); -// else -// ssend(*sock, "\"%s\":%.2f", ip, percentage); + else { + pack_str32(*sock, name); + pack_str32(*sock, ip); + pack_float(*sock, (float) percentage); } } // Free previously allocated memory only if we allocated it - if(allocated) - { + if(allocated) { free(ip); free(name); } } -// if(type != TELNET) -// ssend(*sock, "}"); - if(debugclients) logg("Sent forward destination data to client, ID: %i", *sock); } diff --git a/request.c b/request.c index 1e0114f0..a7ad5779 100644 --- a/request.c +++ b/request.c @@ -41,12 +41,12 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">forward-dest")) { processed = true; - getForwardDestinations(client_message, sock, type); + getForwardDestinations(client_message, sock); } else if(command(client_message, ">forward-names")) { processed = true; - getForwardDestinations(">forward-dest unsorted", sock, type); + getForwardDestinations(">forward-dest unsorted", sock); } else if(command(client_message, ">querytypes")) { From 3f1ffafc8fcc297e74e8fadc29c31b1adf94d324 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 15:24:48 -0500 Subject: [PATCH 121/168] Add socket implementation to getQueryTypes and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 8 ++++---- request.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api.h b/api.h index b7342dc2..17d2e1b4 100644 --- a/api.h +++ b/api.h @@ -14,7 +14,7 @@ void getOverTime(int *sock); void getTopDomains(char *client_message, int *sock); void getTopClients(char *client_message, int *sock); void getForwardDestinations(char *client_message, int *sock); -void getQueryTypes(int *sock, char type); +void getQueryTypes(int *sock); void getAllQueries(char *client_message, int *sock); void getRecentBlocked(char *client_message, int *sock, char type); void getForwardDestinationsOverTime(int *sock, char type); diff --git a/api_stats.c b/api_stats.c index 762d5c75..570e05d3 100644 --- a/api_stats.c +++ b/api_stats.c @@ -471,7 +471,7 @@ void getForwardDestinations(char *client_message, int *sock) } -void getQueryTypes(int *sock, char type) +void getQueryTypes(int *sock) { int total = counters.IPv4 + counters.IPv6; double percentageIPv4 = 0.0, percentageIPv6 = 0.0; @@ -482,11 +482,11 @@ void getQueryTypes(int *sock, char type) percentageIPv6 = 1e2*counters.IPv6/total; } - if(type == TELNET) + if(istelnet[*sock]) ssend(*sock,"A (IPv4): %.2f\nAAAA (IPv6): %.2f\n", percentageIPv4, percentageIPv6); else { -// sendAPIResponse(*sock, type, OK); -// ssend(*sock, "\"query_types\":{\"A (IPv4)\":%.2f,\"AAAA (IPv6)\":%.2f}", percentageIPv4, percentageIPv6); + pack_float(*sock, (float) percentageIPv4); + pack_float(*sock, (float) percentageIPv6); } if(debugclients) diff --git a/request.c b/request.c index a7ad5779..db4cf5dc 100644 --- a/request.c +++ b/request.c @@ -51,7 +51,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">querytypes")) { processed = true; - getQueryTypes(sock, type); + getQueryTypes(sock); } else if(command(client_message, ">getallqueries")) { From 555a0cbc6cca661a786fe90ed090a1b8b252be0d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 15:48:09 -0500 Subject: [PATCH 122/168] Add socket implementation to getRecentBlocked and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 66 ++++++++++++----------------------------------------- request.c | 2 +- 3 files changed, 16 insertions(+), 54 deletions(-) diff --git a/api.h b/api.h index 17d2e1b4..425c21ef 100644 --- a/api.h +++ b/api.h @@ -16,7 +16,7 @@ void getTopClients(char *client_message, int *sock); void getForwardDestinations(char *client_message, int *sock); void getQueryTypes(int *sock); void getAllQueries(char *client_message, int *sock); -void getRecentBlocked(char *client_message, int *sock, char type); +void getRecentBlocked(char *client_message, int *sock); void getForwardDestinationsOverTime(int *sock, char type); void getQueryTypesOverTime(int *sock, char type); void getClientsOverTime(int *sock); diff --git a/api_stats.c b/api_stats.c index 570e05d3..c56102bf 100644 --- a/api_stats.c +++ b/api_stats.c @@ -148,8 +148,7 @@ void getOverTime(int *sock) { logg("Sent overTime data to client, ID: %i", *sock); } -void getTopDomains(char *client_message, int *sock) -{ +void getTopDomains(char *client_message, int *sock) { int i, temparray[counters.domains][2], count=10, num; bool blocked, audit = false, desc = false; @@ -282,8 +281,7 @@ void getTopDomains(char *client_message, int *sock) } } -void getTopClients(char *client_message, int *sock) -{ +void getTopClients(char *client_message, int *sock) { int i, temparray[counters.clients][2], count=10, num; // Match both top-domains and top-ads @@ -360,8 +358,7 @@ void getTopClients(char *client_message, int *sock) } -void getForwardDestinations(char *client_message, int *sock) -{ +void getForwardDestinations(char *client_message, int *sock) { bool allocated = false, first = true, sort = true; int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; @@ -471,8 +468,7 @@ void getForwardDestinations(char *client_message, int *sock) } -void getQueryTypes(int *sock) -{ +void getQueryTypes(int *sock) { int total = counters.IPv4 + counters.IPv6; double percentageIPv4 = 0.0, percentageIPv6 = 0.0; @@ -667,8 +663,7 @@ void getAllQueries(char *client_message, int *sock) { logg("Sent all queries data to client, ID: %i", *sock); } -void getRecentBlocked(char *client_message, int *sock, char type) -{ +void getRecentBlocked(char *client_message, int *sock) { int i, num=1; // Exit before processing any data if requested via config setting @@ -676,65 +671,32 @@ void getRecentBlocked(char *client_message, int *sock, char type) return; // Test for integer that specifies number of entries to be shown - if(type == TELNET) - { - if(sscanf(client_message, "%*[^(](%i)", &num) > 0) - { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - } + if(sscanf(client_message, "%*[^(](%i)", &num) > 0) { + // User wants a different number of requests + if(num >= counters.queries) + num = 0; } - else - { - const char * limit = strstr(client_message, "limit="); - if(limit != NULL) - { - if(sscanf(limit, "limit=%i", &num) > 0) - { - // User wants a different number of requests - if(num >= counters.queries) - num = 0; - } - } - } - -// if(type != TELNET) -// { -// sendAPIResponse(*sock, type, OK); -// ssend(*sock, "\"recent_blocked\":["); -// } // Find most recent query with either status 1 (blocked) // or status 4 (wildcard blocked) int found = 0; bool first = true; - for(i = counters.queries - 1; i > 0 ; i--) - { + for(i = counters.queries - 1; i > 0 ; i--) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); // Check if this query has been removed due to garbage collection if(!queries[i].valid) continue; - if(queries[i].status == 1 || queries[i].status == 4) - { + if(queries[i].status == 1 || queries[i].status == 4) { found++; - if(type == TELNET) - { + + if(istelnet[*sock]) ssend(*sock,"%s\n", domains[queries[i].domainID].domain); - } else - { -// if(!first) ssend(*sock, ","); -// first = false; -// ssend(*sock, "\"%s\"", domains[queries[i].domainID].domain); - } + pack_str32(*sock, domains[queries[i].domainID].domain); } if(found >= num) break; } - -// if(type != TELNET) -// ssend(*sock, "]"); } // only available via TELNET diff --git a/request.c b/request.c index db4cf5dc..817e9fcb 100644 --- a/request.c +++ b/request.c @@ -61,7 +61,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">recentBlocked")) { processed = true; - getRecentBlocked(client_message, sock, type); + getRecentBlocked(client_message, sock); } else if(command(client_message, ">memory")) { From 55e18eaa9468652b9ef8a46f5341c5e1f2ac27ba Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 16:29:39 -0500 Subject: [PATCH 123/168] Remove extra formatting changes Signed-off-by: Mcat12 --- api_stats.c | 141 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 47 deletions(-) diff --git a/api_stats.c b/api_stats.c index c56102bf..e5aac02a 100644 --- a/api_stats.c +++ b/api_stats.c @@ -42,7 +42,8 @@ int cmpdesc(const void *a, const void *b) return 0; } -void getStats(int *sock) { +void getStats(int *sock) +{ int blocked = counters.blocked + counters.wildcardblocked; int total = counters.queries - counters.invalidqueries; float percentage = 0.0; @@ -55,7 +56,8 @@ void getStats(int *sock) { char domains_blocked[11]; char status[9]; - switch(blockingstatus) { + switch(blockingstatus) + { case 0: // Blocking disabled if(istelnet[*sock]) strncpy(domains_blocked, "N/A", 4); @@ -76,7 +78,8 @@ void getStats(int *sock) { // unique_clients: count only clients that have been active within the most recent 24 hours int i, activeclients = 0; - for(i=0; i < counters.clients; i++) { + for(i=0; i < counters.clients; i++) + { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); if(clients[i].count > 0) activeclients++; @@ -91,7 +94,8 @@ void getStats(int *sock) { ssend(*sock, "unique_clients %i\n", activeclients); ssend(*sock, "status %s\n", status); } - else { + else + { pack_int32(*sock, counters.gravity); pack_int32(*sock, total); pack_int32(*sock, blocked); @@ -108,24 +112,30 @@ void getStats(int *sock) { logg("Sent stats data to client, ID: %i", *sock); } -void getOverTime(int *sock) { +void getOverTime(int *sock) +{ int i, j = 9999999; // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) - for(i=0; i < counters.overTime; i++) { + for(i=0; i < counters.overTime; i++) + { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(overTime[i].total > 0 || overTime[i].blocked > 0) { + if(overTime[i].total > 0 || overTime[i].blocked > 0) + { j = i; break; } } - if(istelnet[*sock]) { - for(i = j; i < counters.overTime; i++) { + if(istelnet[*sock]) + { + for(i = j; i < counters.overTime; i++) + { ssend(*sock,"%i %i %i\n",overTime[i].timestamp,overTime[i].total,overTime[i].blocked); } } - else { + else + { // We can use the map16 type because there should only be about 288 time slots (TIMEFRAME set to "yesterday") // and map16 can hold up to (2^16)-1 = 65535 pairs @@ -148,7 +158,8 @@ void getOverTime(int *sock) { logg("Sent overTime data to client, ID: %i", *sock); } -void getTopDomains(char *client_message, int *sock) { +void getTopDomains(char *client_message, int *sock) +{ int i, temparray[counters.domains][2], count=10, num; bool blocked, audit = false, desc = false; @@ -175,7 +186,8 @@ void getTopDomains(char *client_message, int *sock) { if(command(client_message, " desc")) desc = true; - for(i=0; i < counters.domains; i++) { + for(i=0; i < counters.domains; i++) + { validate_access("domains", i, true, __LINE__, __FUNCTION__, __FILE__); temparray[i][0] = i; if(blocked) @@ -195,12 +207,14 @@ void getTopDomains(char *client_message, int *sock) { // Get filter char * filter = read_setupVarsconf("API_QUERY_LOG_SHOW"); bool showpermitted = true, showblocked = true; - if(filter != NULL) { + if(filter != NULL) + { if((strcmp(filter, "permittedonly")) == 0) showblocked = false; else if((strcmp(filter, "blockedonly")) == 0) showpermitted = false; - else if((strcmp(filter, "nothing")) == 0) { + else if((strcmp(filter, "nothing")) == 0) + { showpermitted = false; showblocked = false; } @@ -209,9 +223,11 @@ void getTopDomains(char *client_message, int *sock) { // Get domains which the user doesn't want to see char * excludedomains = NULL; - if(!audit) { + if(!audit) + { excludedomains = read_setupVarsconf("API_EXCLUDE_DOMAINS"); - if(excludedomains != NULL) { + if(excludedomains != NULL) + { getSetupVarsArray(excludedomains); if(debugclients) @@ -219,7 +235,8 @@ void getTopDomains(char *client_message, int *sock) { } } - if(!istelnet[*sock]) { + if(!istelnet[*sock]) + { // Send the data required to get the percentage each domain has been blocked / queried if(blocked) pack_int32(*sock, counters.blocked); @@ -228,33 +245,40 @@ void getTopDomains(char *client_message, int *sock) { } int skip = 0; bool first = true; - for(i=0; i < min(counters.domains, count+skip); i++) { + for(i=0; i < min(counters.domains, count+skip); i++) + { // Get sorted indices int j = temparray[counters.domains-i-1][0]; validate_access("domains", j, true, __LINE__, __FUNCTION__, __FILE__); // Skip this domain if there is a filter on it - if(excludedomains != NULL) { - if(insetupVarsArray(domains[j].domain)) { + if(excludedomains != NULL) + { + if(insetupVarsArray(domains[j].domain)) + { skip++; continue; } } // Skip this domain if already included in audit - if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) { + if(audit && countlineswith(domains[j].domain, files.auditlist) > 0) + { skip++; continue; } - if(blocked && showblocked && domains[j].blockedcount > 0) { - if(istelnet[*sock]) { + if(blocked && showblocked && domains[j].blockedcount > 0) + { + if(istelnet[*sock]) + { if(audit && domains[j].wildcard) ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); else ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); } - else { + else + { pack_str32(*sock, domains[j].domain); pack_int32(*sock, domains[j].blockedcount); } @@ -263,7 +287,8 @@ void getTopDomains(char *client_message, int *sock) { { if(istelnet[*sock]) ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); - else { + else + { pack_str32(*sock, domains[j].domain); pack_int32(*sock, domains[j].count - domains[j].blockedcount); } @@ -273,7 +298,8 @@ void getTopDomains(char *client_message, int *sock) { if(excludedomains != NULL) clearSetupVarsArray(); - if(debugclients) { + if(debugclients) + { if(blocked) logg("Sent top ads list data to client, ID: %i", *sock); else @@ -281,7 +307,8 @@ void getTopDomains(char *client_message, int *sock) { } } -void getTopClients(char *client_message, int *sock) { +void getTopClients(char *client_message, int *sock) +{ int i, temparray[counters.clients][2], count=10, num; // Match both top-domains and top-ads @@ -298,7 +325,8 @@ void getTopClients(char *client_message, int *sock) { if(command(client_message, " withzero")) includezeroclients = true; - for(i=0; i < counters.clients; i++) { + for(i=0; i < counters.clients; i++) + { validate_access("clients", i, true, __LINE__, __FUNCTION__, __FILE__); temparray[i][0] = i; temparray[i][1] = clients[i].count; @@ -309,28 +337,33 @@ void getTopClients(char *client_message, int *sock) { // Get clients which the user doesn't want to see char * excludeclients = read_setupVarsconf("API_EXCLUDE_CLIENTS"); - if(excludeclients != NULL) { + if(excludeclients != NULL) + { getSetupVarsArray(excludeclients); if(debugclients) logg("Excluding %i clients from being displayed", setupVarsElements); } - if(!istelnet[*sock]) { + if(!istelnet[*sock]) + { // Send the total queries so they can make percentages from this data pack_int32(*sock, counters.queries - counters.invalidqueries); } int skip = 0; bool first = true; - for(i=0; i < min(counters.clients, count+skip); i++) { + for(i=0; i < min(counters.clients, count+skip); i++) + { // Get sorted indices int j = temparray[counters.clients-i-1][0]; validate_access("clients", j, true, __LINE__, __FUNCTION__, __FILE__); // Skip this client if there is a filter on it - if(excludeclients != NULL) { + if(excludeclients != NULL) + { if(insetupVarsArray(clients[j].ip) || - insetupVarsArray(clients[j].name)) { + insetupVarsArray(clients[j].name)) + { skip++; continue; } @@ -339,10 +372,12 @@ void getTopClients(char *client_message, int *sock) { // Return this client if either // - "withzero" option is set, and/or // - the client made at least one query within the most recent 24 hours - if(includezeroclients || clients[j].count > 0) { + if(includezeroclients || clients[j].count > 0) + { if(istelnet[*sock]) ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); - else { + else + { pack_str32(*sock, clients[j].name); pack_str32(*sock, clients[j].ip); pack_int32(*sock, clients[j].count); @@ -358,7 +393,8 @@ void getTopClients(char *client_message, int *sock) { } -void getForwardDestinations(char *client_message, int *sock) { +void getForwardDestinations(char *client_message, int *sock) +{ bool allocated = false, first = true, sort = true; int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; @@ -390,7 +426,8 @@ void getForwardDestinations(char *client_message, int *sock) { totalqueries = counters.forwardedqueries + counters.cached + counters.blocked; // Loop over available forward destinations - for(i=0; i < min(counters.forwarded+1, 10); i++) { + for(i=0; i < min(counters.forwarded+1, 10); i++) + { char *name, *ip; double percentage; @@ -402,7 +439,8 @@ void getForwardDestinations(char *client_message, int *sock) { j = i; // Is this the "local" forward destination? - if(j == counters.forwarded) { + if(j == counters.forwarded) + { ip = calloc(4,1); strcpy(ip, "::1"); name = calloc(6,1); @@ -416,7 +454,8 @@ void getForwardDestinations(char *client_message, int *sock) { allocated = true; } - else { + else + { validate_access("forwarded", j, true, __LINE__, __FUNCTION__, __FILE__); ip = forwarded[j].ip; name = forwarded[j].name; @@ -446,10 +485,12 @@ void getForwardDestinations(char *client_message, int *sock) { } // Send data if count > 0 - if(percentage > 0.0) { + if(percentage > 0.0) + { if(istelnet[*sock]) ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); - else { + else + { pack_str32(*sock, name); pack_str32(*sock, ip); pack_float(*sock, (float) percentage); @@ -457,7 +498,8 @@ void getForwardDestinations(char *client_message, int *sock) { } // Free previously allocated memory only if we allocated it - if(allocated) { + if(allocated) + { free(ip); free(name); } @@ -468,7 +510,8 @@ void getForwardDestinations(char *client_message, int *sock) { } -void getQueryTypes(int *sock) { +void getQueryTypes(int *sock) +{ int total = counters.IPv4 + counters.IPv6; double percentageIPv4 = 0.0, percentageIPv6 = 0.0; @@ -490,7 +533,8 @@ void getQueryTypes(int *sock) { } -void getAllQueries(char *client_message, int *sock) { +void getAllQueries(char *client_message, int *sock) +{ // Exit before processing any data if requested via config setting if(!config.query_display) return; @@ -663,7 +707,8 @@ void getAllQueries(char *client_message, int *sock) { logg("Sent all queries data to client, ID: %i", *sock); } -void getRecentBlocked(char *client_message, int *sock) { +void getRecentBlocked(char *client_message, int *sock) +{ int i, num=1; // Exit before processing any data if requested via config setting @@ -680,12 +725,14 @@ void getRecentBlocked(char *client_message, int *sock) { // Find most recent query with either status 1 (blocked) // or status 4 (wildcard blocked) int found = 0; bool first = true; - for(i = counters.queries - 1; i > 0 ; i--) { + for(i = counters.queries - 1; i > 0 ; i--) + { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); // Check if this query has been removed due to garbage collection if(!queries[i].valid) continue; - if(queries[i].status == 1 || queries[i].status == 4) { + if(queries[i].status == 1 || queries[i].status == 4) + { found++; if(istelnet[*sock]) From 81200a270d401f6a75f053d367a6d1be74d34ec5 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 21:10:14 -0500 Subject: [PATCH 124/168] Add socket implementation to getMemoryUsage and use istelnet Signed-off-by: Mcat12 --- api.h | 3 ++- api_stats.c | 21 ++++++++++++++++----- msgpack.c | 4 ++++ request.c | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/api.h b/api.h index 425c21ef..9935d736 100644 --- a/api.h +++ b/api.h @@ -23,7 +23,7 @@ void getClientsOverTime(int *sock); void getClientNames(int *sock); // FTL methods -void getMemoryUsage(int *sock, char type); +void getMemoryUsage(int *sock); void getClientID(int *sock, char type); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); @@ -42,6 +42,7 @@ bool isValidDomain(char *domain); // MessagePack serialization helpers void pack_eom(int sock); void pack_uint8(int sock, uint8_t value); +void pack_uint64(int sock, uint64_t value); void pack_int32(int sock, int32_t value); void pack_float(int sock, float value); void pack_fixstr(int sock, char *string); diff --git a/api_stats.c b/api_stats.c index e5aac02a..2fc8b59c 100644 --- a/api_stats.c +++ b/api_stats.c @@ -746,26 +746,37 @@ void getRecentBlocked(char *client_message, int *sock) } } -// only available via TELNET -void getMemoryUsage(int *sock, char type) +void getMemoryUsage(int *sock) { unsigned long int structbytes = sizeof(countersStruct) + sizeof(ConfigStruct) + counters.queries_MAX*sizeof(queriesDataStruct) + counters.forwarded_MAX*sizeof(forwardedDataStruct) + counters.clients_MAX*sizeof(clientsDataStruct) + counters.domains_MAX*sizeof(domainsDataStruct) + counters.overTime_MAX*sizeof(overTimeDataStruct) + (counters.wildcarddomains)*sizeof(*wildcarddomains); char *structprefix = calloc(2, sizeof(char)); double formated = 0.0; format_memory_size(structprefix, structbytes, &formated); - ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); + + if(istelnet[*sock]) + ssend(*sock,"memory allocated for internal data structure: %lu bytes (%.2f %sB)\n",structbytes,formated,structprefix); + else + pack_uint64(*sock, structbytes); free(structprefix); unsigned long int dynamicbytes = memory.wildcarddomains + memory.domainnames + memory.clientips + memory.clientnames + memory.forwardedips + memory.forwardednames + memory.forwarddata; char *dynamicprefix = calloc(2, sizeof(char)); format_memory_size(dynamicprefix, dynamicbytes, &formated); - ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); + + if(istelnet[*sock]) + ssend(*sock,"dynamically allocated allocated memory used for strings: %lu bytes (%.2f %sB)\n",dynamicbytes,formated,dynamicprefix); + else + pack_uint64(*sock, dynamicbytes); free(dynamicprefix); unsigned long int totalbytes = structbytes + dynamicbytes; char *totalprefix = calloc(2, sizeof(char)); format_memory_size(totalprefix, totalbytes, &formated); - ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); + + if(istelnet[*sock]) + ssend(*sock,"Sum: %lu bytes (%.2f %sB)\n",totalbytes,formated,totalprefix); + else + pack_uint64(*sock, totalbytes); free(totalprefix); if(debugclients) diff --git a/msgpack.c b/msgpack.c index edb83b42..bdbdb1a0 100644 --- a/msgpack.c +++ b/msgpack.c @@ -26,6 +26,10 @@ void pack_uint8(int sock, uint8_t value) { pack_basic(sock, 0xcc, &value, sizeof(value)); } +void pack_uint64(int sock, uint64_t value) { + pack_basic(sock, 0xcf, &value, sizeof(value)); +} + void pack_int32(int sock, int32_t value) { uint32_t bigEValue = htonl((uint32_t) value); pack_basic(sock, 0xd2, &bigEValue, sizeof(bigEValue)); diff --git a/request.c b/request.c index 817e9fcb..7107e12d 100644 --- a/request.c +++ b/request.c @@ -66,7 +66,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">memory")) { processed = true; - getMemoryUsage(sock, type); + getMemoryUsage(sock); } else if(command(client_message, ">clientID")) { From 89ea9960aa24c9a6be3f7840934d01796d7cea14 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 21:49:18 -0500 Subject: [PATCH 125/168] Fix pack_uint64 It needed to have its endianness flipped. Signed-off-by: Mcat12 --- msgpack.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/msgpack.c b/msgpack.c index bdbdb1a0..22d95601 100644 --- a/msgpack.c +++ b/msgpack.c @@ -27,7 +27,20 @@ void pack_uint8(int sock, uint8_t value) { } void pack_uint64(int sock, uint64_t value) { - pack_basic(sock, 0xcf, &value, sizeof(value)); + char *ptr = (char *) &value; + uint32_t part1, part2; + + // Copy the two halves of the 64 bit input into uint32_t's so we can use htonl + memcpy(&part1, ptr, 4); + memcpy(&part2, ptr + 4, 4); + + // Flip each half around + part1 = htonl(part1); + part2 = htonl(part2); + + // Arrange them to form the big-endian version of the original input + uint64_t bigEValue = (uint64_t) part1 << 32 | part2; + pack_basic(sock, 0xcf, &bigEValue, sizeof(bigEValue)); } void pack_int32(int sock, int32_t value) { From 7d0f680347b2f4edc3478ab1595a0604b60b9c53 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:19:20 -0500 Subject: [PATCH 126/168] Add socket implementation to getForwardDestinationsOverTime and istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 33 ++++++++++++--------------------- request.c | 2 +- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/api.h b/api.h index 9935d736..41bd0ad6 100644 --- a/api.h +++ b/api.h @@ -17,7 +17,7 @@ void getForwardDestinations(char *client_message, int *sock); void getQueryTypes(int *sock); void getAllQueries(char *client_message, int *sock); void getRecentBlocked(char *client_message, int *sock); -void getForwardDestinationsOverTime(int *sock, char type); +void getForwardDestinationsOverTime(int *sock); void getQueryTypesOverTime(int *sock, char type); void getClientsOverTime(int *sock); void getClientNames(int *sock); diff --git a/api_stats.c b/api_stats.c index 2fc8b59c..1397cfbf 100644 --- a/api_stats.c +++ b/api_stats.c @@ -783,7 +783,7 @@ void getMemoryUsage(int *sock) logg("Sent memory data to client, ID: %i", *sock); } -void getForwardDestinationsOverTime(int *sock, char type) +void getForwardDestinationsOverTime(int *sock) { int i, sendit = -1; @@ -797,11 +797,11 @@ void getForwardDestinationsOverTime(int *sock, char type) } } -// if(type != TELNET) -// { -// sendAPIResponse(*sock, type, OK); -// ssend(*sock,"\"over_time\":{"); -// } + // Send the number of forward destinations (number of items for each timestamp) + if(!istelnet[*sock]) { + // Add one to include the local forwarded category + pack_int32(*sock, counters.forwarded + 1); + } if(sendit > -1) { @@ -811,15 +811,13 @@ void getForwardDestinationsOverTime(int *sock, char type) double percentage; validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - if(type == TELNET) + if(istelnet[*sock]) { ssend(*sock, "%i", overTime[i].timestamp); } else { -// if(!first) ssend(*sock, ","); -// first = false; -// ssend(*sock, "\"%i\":[", overTime[i].timestamp); + pack_int32(*sock, overTime[i].timestamp); } int j, forwardedsum = 0; @@ -870,10 +868,10 @@ void getForwardDestinationsOverTime(int *sock, char type) else percentage = 0.0; - if(type == TELNET) + if(istelnet[*sock]) ssend(*sock, " %.2f", percentage); else - ssend(*sock, "%.2f,", percentage); + pack_float(*sock, (float) percentage); } // Avoid floating point exceptions @@ -883,20 +881,13 @@ void getForwardDestinationsOverTime(int *sock, char type) else percentage = 0.0; - if(type == TELNET) + if(istelnet[*sock]) ssend(*sock, " %.2f\n", percentage); else - ssend(*sock, "%.2f]", percentage); + pack_float(*sock, (float) percentage); } } -// if(type != TELNET) -// { -// ssend(*sock,"},"); -// // Manually set API -> Don't send header a second time -// getForwardDestinations(">forward-dest unsorted", sock, SOCKET); -// } - if(debugclients) logg("Sent overTime forwarded data to client, ID: %i", *sock); } diff --git a/request.c b/request.c index 7107e12d..8adc649e 100644 --- a/request.c +++ b/request.c @@ -76,7 +76,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">ForwardedoverTime")) { processed = true; - getForwardDestinationsOverTime(sock, type); + getForwardDestinationsOverTime(sock); } else if(command(client_message, ">QueryTypesoverTime")) { From 08143393f0e2f08f308ceca12e2cec1a5e734b19 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:23:01 -0500 Subject: [PATCH 127/168] Add socket implementation to getClientID and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 8 +++++--- request.c | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/api.h b/api.h index 41bd0ad6..8bbffb19 100644 --- a/api.h +++ b/api.h @@ -24,7 +24,7 @@ void getClientNames(int *sock); // FTL methods void getMemoryUsage(int *sock); -void getClientID(int *sock, char type); +void getClientID(int *sock); void getVersion(int *sock, char type); void getDBstats(int *sock, char type); void getUnknownQueries(int *sock); diff --git a/api_stats.c b/api_stats.c index 1397cfbf..334798b1 100644 --- a/api_stats.c +++ b/api_stats.c @@ -892,10 +892,12 @@ void getForwardDestinationsOverTime(int *sock) logg("Sent overTime forwarded data to client, ID: %i", *sock); } -void getClientID(int *sock, char type) +void getClientID(int *sock) { - - ssend(*sock,"%i\n", *sock); + if(istelnet[*sock]) + ssend(*sock,"%i\n", *sock); + else + pack_int32(*sock, *sock); if(debugclients) logg("Sent client ID to client, ID: %i", *sock); diff --git a/request.c b/request.c index 8adc649e..71df2e62 100644 --- a/request.c +++ b/request.c @@ -71,7 +71,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">clientID")) { processed = true; - getClientID(sock, type); + getClientID(sock); } else if(command(client_message, ">ForwardedoverTime")) { From 779a82468e6c2ff904554a508eb98c6e9245da82 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:29:06 -0500 Subject: [PATCH 128/168] Add socket implementation to getQueryTypesOverTime and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 19 +++++-------------- request.c | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/api.h b/api.h index 8bbffb19..4d08bd15 100644 --- a/api.h +++ b/api.h @@ -18,7 +18,7 @@ void getQueryTypes(int *sock); void getAllQueries(char *client_message, int *sock); void getRecentBlocked(char *client_message, int *sock); void getForwardDestinationsOverTime(int *sock); -void getQueryTypesOverTime(int *sock, char type); +void getQueryTypesOverTime(int *sock); void getClientsOverTime(int *sock); void getClientNames(int *sock); diff --git a/api_stats.c b/api_stats.c index 334798b1..bafb1b1d 100644 --- a/api_stats.c +++ b/api_stats.c @@ -903,7 +903,7 @@ void getClientID(int *sock) logg("Sent client ID to client, ID: %i", *sock); } -void getQueryTypesOverTime(int *sock, char type) +void getQueryTypesOverTime(int *sock) { int i, sendit = -1; for(i = 0; i < counters.overTime; i++) @@ -916,12 +916,6 @@ void getQueryTypesOverTime(int *sock, char type) } } -// if(type != TELNET) -// { -// sendAPIResponse(*sock, type, OK); -// ssend(*sock,"\"query_types\":{"); -// } - if(sendit > -1) { bool first = true; @@ -937,19 +931,16 @@ void getQueryTypesOverTime(int *sock, char type) percentageIPv6 = 1e2*overTime[i].querytypedata[1] / sum; } - if(type == TELNET) + if(istelnet[*sock]) ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6); else { -// if(!first) ssend(*sock, ","); -// first = false; -// ssend(*sock, "\"%i\":[%.2f,%.2f]", overTime[i].timestamp, percentageIPv4, percentageIPv6); + pack_int32(*sock, overTime[i].timestamp); + pack_float(*sock, (float) percentageIPv4); + pack_float(*sock, (float) percentageIPv6); } } } -// if(type != TELNET) -// ssend(*sock,"}"); - if(debugclients) logg("Sent overTime query types data to client, ID: %i", *sock); } diff --git a/request.c b/request.c index 71df2e62..c97b4a0b 100644 --- a/request.c +++ b/request.c @@ -81,7 +81,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">QueryTypesoverTime")) { processed = true; - getQueryTypesOverTime(sock, type); + getQueryTypesOverTime(sock); } else if(command(client_message, ">version")) { From bd639817f4bc9b1ddf17edf94f7ae3e6ad8741a9 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:30:17 -0500 Subject: [PATCH 129/168] Remove the unused `first` boolean variables (used by Json serialization) Signed-off-by: Mcat12 --- api_stats.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/api_stats.c b/api_stats.c index bafb1b1d..531dad60 100644 --- a/api_stats.c +++ b/api_stats.c @@ -244,7 +244,7 @@ void getTopDomains(char *client_message, int *sock) pack_int32(*sock, counters.queries - counters.invalidqueries); } - int skip = 0; bool first = true; + int skip = 0; for(i=0; i < min(counters.domains, count+skip); i++) { // Get sorted indices @@ -351,7 +351,7 @@ void getTopClients(char *client_message, int *sock) pack_int32(*sock, counters.queries - counters.invalidqueries); } - int skip = 0; bool first = true; + int skip = 0; for(i=0; i < min(counters.clients, count+skip); i++) { // Get sorted indices @@ -395,7 +395,7 @@ void getTopClients(char *client_message, int *sock) void getForwardDestinations(char *client_message, int *sock) { - bool allocated = false, first = true, sort = true; + bool allocated = false, sort = true; int i, temparray[counters.forwarded+1][2], forwardedsum = 0, totalqueries = 0; if(command(client_message, "unsorted")) @@ -617,7 +617,7 @@ void getAllQueries(char *client_message, int *sock) logg("Privacy mode enabled"); } - int i; bool first = true; + int i; for(i=ibeg; i < counters.queries; i++) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); @@ -724,7 +724,7 @@ void getRecentBlocked(char *client_message, int *sock) // Find most recent query with either status 1 (blocked) // or status 4 (wildcard blocked) - int found = 0; bool first = true; + int found = 0; for(i = counters.queries - 1; i > 0 ; i--) { validate_access("queries", i, true, __LINE__, __FUNCTION__, __FILE__); @@ -805,7 +805,6 @@ void getForwardDestinationsOverTime(int *sock) if(sendit > -1) { - bool first = true; for(i = sendit; i < counters.overTime; i++) { double percentage; @@ -918,7 +917,6 @@ void getQueryTypesOverTime(int *sock) if(sendit > -1) { - bool first = true; for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); From 2671a6fde09314b13dd601aff06a3accda9a8b27 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:40:30 -0500 Subject: [PATCH 130/168] Add socket implementation of getVersion and use istelnet Signed-off-by: Mcat12 --- api.h | 2 +- api_stats.c | 31 ++++++++++++++++++++++++++----- request.c | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/api.h b/api.h index 4d08bd15..63905cca 100644 --- a/api.h +++ b/api.h @@ -25,7 +25,7 @@ void getClientNames(int *sock); // FTL methods void getMemoryUsage(int *sock); void getClientID(int *sock); -void getVersion(int *sock, char type); +void getVersion(int *sock); void getDBstats(int *sock, char type); void getUnknownQueries(int *sock); diff --git a/api_stats.c b/api_stats.c index 531dad60..2149ae70 100644 --- a/api_stats.c +++ b/api_stats.c @@ -943,7 +943,7 @@ void getQueryTypesOverTime(int *sock) logg("Sent overTime query types data to client, ID: %i", *sock); } -void getVersion(int *sock, char type) +void getVersion(int *sock) { const char * version = GIT_VERSION; const char * branch = GIT_BRANCH; @@ -954,10 +954,31 @@ void getVersion(int *sock, char type) if(strstr(branch, "(no branch)") != NULL && strstr(version, ".") != NULL) branch = "master"; - if(strstr(version, ".") != NULL) - ssend(*sock,"version %s\ntag %s\nbranch %s\ndate %s\n", version, GIT_TAG, branch, GIT_DATE); - else - ssend(*sock,"version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, branch, GIT_DATE); + if(strstr(version, ".") != NULL) { + if(istelnet[*sock]) + ssend(*sock, "version %s\ntag %s\nbranch %s\ndate %s\n", version, GIT_TAG, branch, GIT_DATE); + else { + pack_str32(*sock, (char *) version); + pack_str32(*sock, GIT_TAG); + pack_str32(*sock, (char *) branch); + pack_str32(*sock, GIT_DATE); + } + } + else { + if(istelnet[*sock]) + ssend(*sock, "version vDev-%s\ntag %s\nbranch %s\ndate %s\n", GIT_HASH, GIT_TAG, branch, GIT_DATE); + else { + char *hashVersion = calloc(5 + strlen(GIT_HASH), sizeof(char)); + sprintf(hashVersion, "vDev-%s", GIT_HASH); + + pack_str32(*sock, hashVersion); + pack_str32(*sock, GIT_TAG); + pack_str32(*sock, (char *) branch); + pack_str32(*sock, GIT_DATE); + + free(hashVersion); + } + } if(debugclients) logg("Sent version info to client, ID: %i", *sock); diff --git a/request.c b/request.c index c97b4a0b..052d1096 100644 --- a/request.c +++ b/request.c @@ -86,7 +86,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">version")) { processed = true; - getVersion(sock, type); + getVersion(sock); } else if(command(client_message, ">dbstats")) { From 64a1408e5d5f56152044226cda3a7572fe924938 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:51:04 -0500 Subject: [PATCH 131/168] Add socket implementation of getDBstats and use istelnet Signed-off-by: Mcat12 --- api.h | 3 ++- api_stats.c | 10 ++++++++-- msgpack.c | 25 +++++++++++++++++++------ request.c | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/api.h b/api.h index 63905cca..fb37161b 100644 --- a/api.h +++ b/api.h @@ -26,7 +26,7 @@ void getClientNames(int *sock); void getMemoryUsage(int *sock); void getClientID(int *sock); void getVersion(int *sock); -void getDBstats(int *sock, char type); +void getDBstats(int *sock); void getUnknownQueries(int *sock); // DNS methods @@ -44,6 +44,7 @@ void pack_eom(int sock); void pack_uint8(int sock, uint8_t value); void pack_uint64(int sock, uint64_t value); void pack_int32(int sock, int32_t value); +void pack_int64(int sock, int64_t value); void pack_float(int sock, float value); void pack_fixstr(int sock, char *string); void pack_str32(int sock, char *string); diff --git a/api_stats.c b/api_stats.c index 2149ae70..e2263ac2 100644 --- a/api_stats.c +++ b/api_stats.c @@ -984,7 +984,7 @@ void getVersion(int *sock) logg("Sent version info to client, ID: %i", *sock); } -void getDBstats(int *sock, char type) +void getDBstats(int *sock) { // Get file details struct stat st; @@ -999,7 +999,13 @@ void getDBstats(int *sock, char type) double formated = 0.0; format_memory_size(prefix, filesize, &formated); - ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); + if(istelnet[*sock]) + ssend(*sock,"queries in database: %i\ndatabase filesize: %.2f %sB\nSQLite version: %s\n", get_number_of_queries_in_DB(), formated, prefix, sqlite3_libversion()); + else { + pack_int32(*sock, get_number_of_queries_in_DB()); + pack_int64(*sock, filesize); + pack_str32(*sock, (char *) sqlite3_libversion()); + } if(debugclients) logg("Sent DB info to client, ID: %i", *sock); diff --git a/msgpack.c b/msgpack.c index 22d95601..f8c393f9 100644 --- a/msgpack.c +++ b/msgpack.c @@ -22,11 +22,7 @@ void pack_basic(int sock, uint8_t format, void *value, size_t size) { swrite(sock, value, size); } -void pack_uint8(int sock, uint8_t value) { - pack_basic(sock, 0xcc, &value, sizeof(value)); -} - -void pack_uint64(int sock, uint64_t value) { +uint64_t leToBe64(uint64_t value) { char *ptr = (char *) &value; uint32_t part1, part2; @@ -39,7 +35,15 @@ void pack_uint64(int sock, uint64_t value) { part2 = htonl(part2); // Arrange them to form the big-endian version of the original input - uint64_t bigEValue = (uint64_t) part1 << 32 | part2; + return (uint64_t) part1 << 32 | part2; +} + +void pack_uint8(int sock, uint8_t value) { + pack_basic(sock, 0xcc, &value, sizeof(value)); +} + +void pack_uint64(int sock, uint64_t value) { + uint64_t bigEValue = leToBe64(value); pack_basic(sock, 0xcf, &bigEValue, sizeof(bigEValue)); } @@ -48,6 +52,15 @@ void pack_int32(int sock, int32_t value) { pack_basic(sock, 0xd2, &bigEValue, sizeof(bigEValue)); } +void pack_int64(int sock, int64_t value) { + // Need to use memcpy to do a direct copy without reinterpreting the bytes (making negatives into positives). + // It should get optimized away. + uint64_t bigEValue; + memcpy(&bigEValue, &value, sizeof(bigEValue)); + bigEValue = leToBe64(bigEValue); + pack_basic(sock, 0xd3, &bigEValue, sizeof(bigEValue)); +} + void pack_float(int sock, float value) { // Need to use memcpy to do a direct copy without reinterpreting the bytes. It should get optimized away. uint32_t bigEValue; diff --git a/request.c b/request.c index 052d1096..3ff8a5a3 100644 --- a/request.c +++ b/request.c @@ -91,7 +91,7 @@ void process_request(char *client_message, int *sock, char type) else if(command(client_message, ">dbstats")) { processed = true; - getDBstats(sock, type); + getDBstats(sock); } else if(command(client_message, ">ClientsoverTime")) { From f90f6c8b4fd810bf3f80f5a9ef54ed410d3676b7 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 22:52:54 -0500 Subject: [PATCH 132/168] Use ssend in getClientsOverTime Signed-off-by: Mcat12 --- api_stats.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/api_stats.c b/api_stats.c index e2263ac2..43667a15 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1013,7 +1013,6 @@ void getDBstats(int *sock) void getClientsOverTime(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i, sendit = -1; for(i = 0; i < counters.overTime; i++) @@ -1055,7 +1054,7 @@ void getClientsOverTime(int *sock) for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - sprintf(server_message, "%i", overTime[i].timestamp); + ssend(*sock, "%i", overTime[i].timestamp); // Loop over forward destinations to generate output to be sent to the client int j; @@ -1073,11 +1072,10 @@ void getClientsOverTime(int *sock) thisclient = overTime[i].clientdata[j]; } - sprintf(server_message + strlen(server_message), " %i", thisclient); + ssend(*sock, " %i", thisclient); } - sprintf(server_message + strlen(server_message), "\n"); - ssend(*sock, server_message); + ssend(*sock, "\n"); } if(excludeclients != NULL) From dde16029596bea593a5ee8b064f62c17247b4f86 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 23:02:42 -0500 Subject: [PATCH 133/168] Add socket implementation for getClientsOverTime and use istelnet Signed-off-by: Mcat12 --- api_stats.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/api_stats.c b/api_stats.c index 43667a15..edc2f4f4 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1054,7 +1054,11 @@ void getClientsOverTime(int *sock) for(i = sendit; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - ssend(*sock, "%i", overTime[i].timestamp); + + if(istelnet[*sock]) + ssend(*sock, "%i", overTime[i].timestamp); + else + pack_int32(*sock, overTime[i].timestamp); // Loop over forward destinations to generate output to be sent to the client int j; @@ -1072,10 +1076,16 @@ void getClientsOverTime(int *sock) thisclient = overTime[i].clientdata[j]; } - ssend(*sock, " %i", thisclient); + if(istelnet[*sock]) + ssend(*sock, " %i", thisclient); + else + pack_int32(*sock, thisclient); } - ssend(*sock, "\n"); + if(istelnet[*sock]) + ssend(*sock, "\n"); + else + pack_int32(*sock, -1); } if(excludeclients != NULL) From 14827420d63392f3d582ed9ec0abd4090af7b2b8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 23:06:09 -0500 Subject: [PATCH 134/168] Use ssend in getClientNames Signed-off-by: Mcat12 --- api_stats.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api_stats.c b/api_stats.c index edc2f4f4..9d7daee9 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1094,7 +1094,6 @@ void getClientsOverTime(int *sock) void getClientNames(int *sock) { - char server_message[SOCKETBUFFERLEN]; int i; // Get clients which the user doesn't want to see @@ -1124,8 +1123,7 @@ void getClientNames(int *sock) if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) continue; - sprintf(server_message,"%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); - ssend(*sock, server_message); + ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); } if(excludeclients != NULL) From 43716b4f2db2f9349f77177b21083d05f6f39fa0 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 23:08:44 -0500 Subject: [PATCH 135/168] Add socket implementation to getClientNames and use istelnet Signed-off-by: Mcat12 --- api_stats.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api_stats.c b/api_stats.c index 9d7daee9..23931bbd 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1123,7 +1123,13 @@ void getClientNames(int *sock) if(insetupVarsArray(clients[i].ip) || insetupVarsArray(clients[i].name)) continue; - ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); + if(istelnet[*sock]) + ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); + else { + pack_str32(*sock, clients[i].name); + pack_str32(*sock, clients[i].ip); + pack_int32(*sock, clients[i].count); + } } if(excludeclients != NULL) From da7c354b7185b756afca250904c668c9d0c36a46 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 7 Jan 2018 23:29:16 -0500 Subject: [PATCH 136/168] Add socket implementation to getUnknownQueries and use istelnet Signed-off-by: Mcat12 --- api.h | 1 + api_stats.c | 24 ++++++++++++++++++++++-- msgpack.c | 5 +++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/api.h b/api.h index fb37161b..86e3a8cd 100644 --- a/api.h +++ b/api.h @@ -41,6 +41,7 @@ bool isValidDomain(char *domain); // MessagePack serialization helpers void pack_eom(int sock); +void pack_bool(int sock, bool value); void pack_uint8(int sock, uint8_t value); void pack_uint64(int sock, uint64_t value); void pack_int32(int sock, int32_t value); diff --git a/api_stats.c b/api_stats.c index 23931bbd..8766e1c3 100644 --- a/api_stats.c +++ b/api_stats.c @@ -1158,10 +1158,30 @@ void getUnknownQueries(int *sock) validate_access("domains", queries[i].domainID, true, __LINE__, __FUNCTION__, __FILE__); validate_access("clients", queries[i].clientID, true, __LINE__, __FUNCTION__, __FILE__); + + char *client; + if(strlen(clients[queries[i].clientID].name) > 0) - ssend(*sock,"%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].name,queries[i].status,queries[i].complete ?"true":"false"); + client = clients[queries[i].clientID].name; else - ssend(*sock,"%i %i %i %s %s %s %i %s\n",queries[i].timestamp,i,queries[i].id,type,domains[queries[i].domainID].domain,clients[queries[i].clientID].ip,queries[i].status,queries[i].complete?"true":"false"); + client = clients[queries[i].clientID].ip; + + if(istelnet[*sock]) + ssend(*sock, "%i %i %i %s %s %s %i %s\n", queries[i].timestamp, i, queries[i].id, type, domains[queries[i].domainID].domain, client, queries[i].status, queries[i].complete ? "true" : "false"); + else { + pack_int32(*sock, queries[i].timestamp); + pack_int32(*sock, queries[i].id); + + // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) + pack_fixstr(*sock, type); + + // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) + pack_str32(*sock, domains[queries[i].domainID].domain); + pack_str32(*sock, client); + + pack_uint8(*sock, queries[i].status); + pack_bool(*sock, queries[i].complete); + } } if(debugclients) diff --git a/msgpack.c b/msgpack.c index f8c393f9..c3123895 100644 --- a/msgpack.c +++ b/msgpack.c @@ -38,6 +38,11 @@ uint64_t leToBe64(uint64_t value) { return (uint64_t) part1 << 32 | part2; } +void pack_bool(int sock, bool value) { + uint8_t packed = (uint8_t) (value ? 0xc3 : 0xc2); + swrite(sock, &packed, sizeof(packed)); +} + void pack_uint8(int sock, uint8_t value) { pack_basic(sock, 0xcc, &value, sizeof(value)); } From 30ae391e85128810ee3d7e9e60416d64da89d29c Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Mon, 8 Jan 2018 18:15:41 -0500 Subject: [PATCH 137/168] Let socket-test take in an optional command Example: ./socket-test travis ">stats" Signed-off-by: Mcat12 --- socket_client.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/socket_client.c b/socket_client.c index 6df0fc27..37abe99b 100644 --- a/socket_client.c +++ b/socket_client.c @@ -37,11 +37,22 @@ int main (int argc, char **argv) { // Set socket family to local socket (not an Internet socket) address.sun_family = AF_LOCAL; - // Set socket file location (respect special location on the CI system Travis) - if(argc == 2 && strcmp(argv[1], "travis") == 0) - strcpy(address.sun_path,"pihole-FTL.sock"); - else - strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); + char *command = ">stats"; + + int i; + for(i = 1; i < argc; i++) { + // Get command + if(strstr(argv[i], ">") == argv[i]) { + command = argv[i]; + continue; + } + + // Set socket file location (respect special location on the CI system Travis) + if(strcmp(argv[i], "travis") == 0) + strcpy(address.sun_path,"pihole-FTL.sock"); + else + strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); + } // Connect to the socket provided by pihole-FTL ret = connect(socketfd, (struct sockaddr *) &address, sizeof (address)); @@ -53,14 +64,13 @@ int main (int argc, char **argv) { printf("Connection established\n"); // As an example, we query the current statistics from FTL through the socket here - sprintf(buffer, ">stats"); + sprintf(buffer, command); send(socketfd, buffer, strlen (buffer), 0); // Try to receive data until either recv() fails or we see "--EOM--" while((size = recv(socketfd, buffer, BUF-1, 0)) > -1) { // Print received data to stdout - int i; for(i = 0; i < size; ++i) { printf("%02x ", (unsigned char) buffer[i]); } From 4ed20a4e8a64b4bc863dfdf94e9f9d73903ee83e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 9 Jan 2018 16:07:58 -0500 Subject: [PATCH 138/168] Don't show a warning if write() returns 0 (this isn't an error) Signed-off-by: Mcat12 --- socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/socket.c b/socket.c index 7887445e..88120640 100644 --- a/socket.c +++ b/socket.c @@ -181,7 +181,7 @@ void ssend(int sock, const char *format, ...) } void swrite(int sock, void *value, size_t size) { - if(!write(sock, value, size)) + if(write(sock, value, size) == -1) logg("WARNING: Socket write returned error code %i", errno); } From 37360bb3b16b0db75ff4b20d02035264b94e7609 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Tue, 9 Jan 2018 16:08:29 -0500 Subject: [PATCH 139/168] Send forward destinations names and IP addresses alongside overTime data Signed-off-by: Mcat12 --- api_stats.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/api_stats.c b/api_stats.c index 8766e1c3..2f4b0c6d 100644 --- a/api_stats.c +++ b/api_stats.c @@ -797,10 +797,27 @@ void getForwardDestinationsOverTime(int *sock) } } - // Send the number of forward destinations (number of items for each timestamp) + // Send the number of forward destinations (number of items for each timestamp), names, and IPs if(!istelnet[*sock]) { // Add one to include the local forwarded category pack_int32(*sock, counters.forwarded + 1); + + for(i = 0; i < counters.forwarded + 1; i++) { + char *name, *ip; + + if(i == counters.forwarded) { + name = "local"; + ip = "::1"; + } + else { + validate_access("forwarded", i, true, __LINE__, __FUNCTION__, __FILE__); + name = forwarded[i].name; + ip = forwarded[i].ip; + } + + pack_str32(*sock, name); + pack_str32(*sock, ip); + } } if(sendit > -1) From f02c4e81a5be84d3191d77eb603031a23fb200b8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 10 Jan 2018 22:41:03 -0500 Subject: [PATCH 140/168] Reimplement output for getList Adds `>getWhitelist`, `>getBlacklist`, and `getWildlist` Signed-off-by: Mcat12 --- api.h | 2 +- api_dns.c | 133 +++++++++++++++++++++++++----------------------------- request.c | 15 ++++++ 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/api.h b/api.h index 86e3a8cd..d6d7944d 100644 --- a/api.h +++ b/api.h @@ -30,7 +30,7 @@ void getDBstats(int *sock); void getUnknownQueries(int *sock); // DNS methods -void getList(int *sock, char type, char list_type); +void getList(int *sock, char list_type); void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); void getPiholeStatus(int *sock, char type); diff --git a/api_dns.c b/api_dns.c index e58580c1..0700921f 100644 --- a/api_dns.c +++ b/api_dns.c @@ -11,101 +11,92 @@ #include "FTL.h" #include "api.h" -void getList(int *sock, char type, char list_type) { +void getList(int *sock, char list_type) { FILE *fp; char *line = NULL; size_t size = 0; const char *file; - const char *name; if(list_type == WHITELIST) { file = files.whitelist; - name = "whitelist"; } else if(list_type == BLACKLIST) { file = files.blacklist; - name = "blacklist"; } else { file = files.wildcards; - name = "wildlist"; } -// sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"%s\":[", name); + if((fp = fopen(file, "r")) == NULL) + return; - if((fp = fopen(file, "r")) != NULL) - { - bool first = true; - bool skipEveryOther = false; - bool skip = true; - char *parsedLine; + bool skipEveryOther = false; + bool skip = true; + char *parsedLine; - // Check if both IPv4 and IPv6 are used. If so, skip every other line in if we're getting wildcard domains - if(list_type == WILDLIST) { - char *ipv4 = read_setupVarsconf("IPV4_ADDRESS"); - size_t ipv4_len = strlen(ipv4); - char *ipv6 = read_setupVarsconf("IPV6_ADDRESS"); - size_t ipv6_len = strlen(ipv6); + // Check if both IPv4 and IPv6 are used. If so, skip every other line in if we're getting wildcard domains + if(list_type == WILDLIST) { + char *ipv4 = read_setupVarsconf("IPV4_ADDRESS"); + size_t ipv4_len = strlen(ipv4); + char *ipv6 = read_setupVarsconf("IPV6_ADDRESS"); + size_t ipv6_len = strlen(ipv6); - if(ipv4_len > 0 && ipv6_len > 0) - skipEveryOther = true; - } + if(ipv4_len > 0 && ipv6_len > 0) + skipEveryOther = true; + } - while(getline(&line, &size, fp) != -1) { - // Skip empty lines - if(line[0] == '\n') + while(getline(&line, &size, fp) != -1) { + // Skip empty lines + if(line[0] == '\n') + continue; + + // If applicable, skip every other line + if(skipEveryOther) { + skip = !skip; + + if(skip) continue; - - // If applicable, skip every other line - if(skipEveryOther) { - skip = !skip; - - if(skip) - continue; - } - - if(!first) ssend(*sock, ","); - first = false; - - // Trim off the newline, if it exists - line[strcspn(line, "\r\n")] = 0; - - // Do more parsing if it's the wildcard list - if(list_type == WILDLIST) { - char *firstSlash = strstr(line, "/"); - - if(firstSlash == NULL) { - logg("Failed to parse wildcard line: %s", line); - continue; - } - - char *secondSlash = strstr(firstSlash+1, "/"); - - if(secondSlash == NULL) { - logg("Failed to parse wildcard line: %s", line); - continue; - } - - secondSlash[0] = 0; - parsedLine = firstSlash+1; - } - else - parsedLine = line; - - ssend(*sock, "\"%s\"", parsedLine); - } - // Free allocated memory - if(line != NULL) - { - free(line); - line = NULL; } - fclose(fp); + // Trim off the newline, if it exists + line[strcspn(line, "\r\n")] = 0; + + // Do more parsing if it's the wildcard list + if(list_type == WILDLIST) { + char *firstSlash = strstr(line, "/"); + + if(firstSlash == NULL) { + logg("Failed to parse wildcard line: %s", line); + continue; + } + + char *secondSlash = strstr(firstSlash+1, "/"); + + if(secondSlash == NULL) { + logg("Failed to parse wildcard line: %s", line); + continue; + } + + secondSlash[0] = 0; + parsedLine = firstSlash+1; + } + else + parsedLine = line; + + if(istelnet[*sock]) + ssend(*sock, "%s\n", parsedLine); + else + pack_str32(*sock, parsedLine); } - ssend(*sock, "]"); + // Free allocated memory + if(line != NULL) + { + free(line); + line = NULL; + } + + fclose(fp); } void getPiholeStatus(int *sock, char type) { diff --git a/request.c b/request.c index 3ff8a5a3..948d661c 100644 --- a/request.c +++ b/request.c @@ -108,6 +108,21 @@ void process_request(char *client_message, int *sock, char type) processed = true; getUnknownQueries(sock); } + else if(command(client_message, ">getWhitelist")) + { + processed = true; + getList(sock, WHITELIST); + } + else if(command(client_message, ">getBlacklist")) + { + processed = true; + getList(sock, BLACKLIST); + } + else if(command(client_message, ">getWildlist")) + { + processed = true; + getList(sock, WILDLIST); + } // Test only at the end if we want to quit or kill // so things can be processed before From b9780dc295f42f55867b345eb55f27ef682300e8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Wed, 10 Jan 2018 23:05:05 -0500 Subject: [PATCH 141/168] Reimplement output for getPiholeStatus Signed-off-by: Mcat12 --- api.h | 2 +- api_dns.c | 25 +++++++++++++++++++++---- request.c | 5 +++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/api.h b/api.h index d6d7944d..cce64da9 100644 --- a/api.h +++ b/api.h @@ -33,7 +33,7 @@ void getUnknownQueries(int *sock); void getList(int *sock, char list_type); void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); -void getPiholeStatus(int *sock, char type); +void getPiholeStatus(int *sock); // General API commands bool matchesRegex(char *regex_expression, char *input); diff --git a/api_dns.c b/api_dns.c index 0700921f..c1709194 100644 --- a/api_dns.c +++ b/api_dns.c @@ -99,10 +99,27 @@ void getList(int *sock, char list_type) { fclose(fp); } -void getPiholeStatus(int *sock, char type) { - int status = countlineswith("#addn-hosts=/etc/pihole/gravity.list", files.dnsmasqconfig); -// sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"status\":%i", status == 1 ? 0 : 1); +void getPiholeStatus(int *sock) { + if(istelnet[*sock]) { + char *status; + + switch(blockingstatus) + { + case 0: // Blocking disabled + status = "disabled"; + break; + case 1: // Blocking Enabled + status = "enabled"; + break; + default: // Unknown status + status = "unknown"; + break; + } + + ssend(*sock, "status: %s", status); + } + else + pack_uint8(*sock, blockingstatus); } void addList(int *sock, char type, char list_type, char *data) { diff --git a/request.c b/request.c index 948d661c..92908b33 100644 --- a/request.c +++ b/request.c @@ -123,6 +123,11 @@ void process_request(char *client_message, int *sock, char type) processed = true; getList(sock, WILDLIST); } + else if(command(client_message, ">status")) + { + processed = true; + getPiholeStatus(sock); + } // Test only at the end if we want to quit or kill // so things can be processed before From 7d877223f421e66c84726316e39323f8f0f8e8cc Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 11 Jan 2018 16:54:36 -0500 Subject: [PATCH 142/168] Remove getList Most DNS endpoints will be implemented in the API to keep FTL focused on providing stats. Signed-off-by: Mcat12 --- api.h | 1 - api_dns.c | 88 ------------------------------------------------------- request.c | 20 ------------- 3 files changed, 109 deletions(-) diff --git a/api.h b/api.h index cce64da9..d5cfdcb3 100644 --- a/api.h +++ b/api.h @@ -30,7 +30,6 @@ void getDBstats(int *sock); void getUnknownQueries(int *sock); // DNS methods -void getList(int *sock, char list_type); void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); void getPiholeStatus(int *sock); diff --git a/api_dns.c b/api_dns.c index c1709194..b4ec79c0 100644 --- a/api_dns.c +++ b/api_dns.c @@ -11,94 +11,6 @@ #include "FTL.h" #include "api.h" -void getList(int *sock, char list_type) { - FILE *fp; - char *line = NULL; - size_t size = 0; - const char *file; - - if(list_type == WHITELIST) { - file = files.whitelist; - } - else if(list_type == BLACKLIST) { - file = files.blacklist; - } - else { - file = files.wildcards; - } - - if((fp = fopen(file, "r")) == NULL) - return; - - bool skipEveryOther = false; - bool skip = true; - char *parsedLine; - - // Check if both IPv4 and IPv6 are used. If so, skip every other line in if we're getting wildcard domains - if(list_type == WILDLIST) { - char *ipv4 = read_setupVarsconf("IPV4_ADDRESS"); - size_t ipv4_len = strlen(ipv4); - char *ipv6 = read_setupVarsconf("IPV6_ADDRESS"); - size_t ipv6_len = strlen(ipv6); - - if(ipv4_len > 0 && ipv6_len > 0) - skipEveryOther = true; - } - - while(getline(&line, &size, fp) != -1) { - // Skip empty lines - if(line[0] == '\n') - continue; - - // If applicable, skip every other line - if(skipEveryOther) { - skip = !skip; - - if(skip) - continue; - } - - // Trim off the newline, if it exists - line[strcspn(line, "\r\n")] = 0; - - // Do more parsing if it's the wildcard list - if(list_type == WILDLIST) { - char *firstSlash = strstr(line, "/"); - - if(firstSlash == NULL) { - logg("Failed to parse wildcard line: %s", line); - continue; - } - - char *secondSlash = strstr(firstSlash+1, "/"); - - if(secondSlash == NULL) { - logg("Failed to parse wildcard line: %s", line); - continue; - } - - secondSlash[0] = 0; - parsedLine = firstSlash+1; - } - else - parsedLine = line; - - if(istelnet[*sock]) - ssend(*sock, "%s\n", parsedLine); - else - pack_str32(*sock, parsedLine); - } - - // Free allocated memory - if(line != NULL) - { - free(line); - line = NULL; - } - - fclose(fp); -} - void getPiholeStatus(int *sock) { if(istelnet[*sock]) { char *status; diff --git a/request.c b/request.c index 92908b33..3ff8a5a3 100644 --- a/request.c +++ b/request.c @@ -108,26 +108,6 @@ void process_request(char *client_message, int *sock, char type) processed = true; getUnknownQueries(sock); } - else if(command(client_message, ">getWhitelist")) - { - processed = true; - getList(sock, WHITELIST); - } - else if(command(client_message, ">getBlacklist")) - { - processed = true; - getList(sock, BLACKLIST); - } - else if(command(client_message, ">getWildlist")) - { - processed = true; - getList(sock, WILDLIST); - } - else if(command(client_message, ">status")) - { - processed = true; - getPiholeStatus(sock); - } // Test only at the end if we want to quit or kill // so things can be processed before From 3158f177014cf1d68d08cf9cdad599787aca37a7 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 11 Jan 2018 20:46:25 -0500 Subject: [PATCH 143/168] Remove getPiholeStatus Signed-off-by: Mcat12 --- api.h | 1 - api_dns.c | 23 ----------------------- 2 files changed, 24 deletions(-) diff --git a/api.h b/api.h index d5cfdcb3..8268bb4e 100644 --- a/api.h +++ b/api.h @@ -32,7 +32,6 @@ void getUnknownQueries(int *sock); // DNS methods void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); -void getPiholeStatus(int *sock); // General API commands bool matchesRegex(char *regex_expression, char *input); diff --git a/api_dns.c b/api_dns.c index b4ec79c0..60c605eb 100644 --- a/api_dns.c +++ b/api_dns.c @@ -11,29 +11,6 @@ #include "FTL.h" #include "api.h" -void getPiholeStatus(int *sock) { - if(istelnet[*sock]) { - char *status; - - switch(blockingstatus) - { - case 0: // Blocking disabled - status = "disabled"; - break; - case 1: // Blocking Enabled - status = "enabled"; - break; - default: // Unknown status - status = "unknown"; - break; - } - - ssend(*sock, "status: %s", status); - } - else - pack_uint8(*sock, blockingstatus); -} - void addList(int *sock, char type, char list_type, char *data) { // cJSON *input_root = cJSON_Parse(data); // cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); From cf2aff7b16231ea9ed56a6c76e86b6bb046682c4 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Thu, 11 Jan 2018 22:20:43 -0500 Subject: [PATCH 144/168] Remove addList Signed-off-by: Mcat12 --- api.h | 1 - api_dns.c | 57 ------------------------------------------------------- 2 files changed, 58 deletions(-) diff --git a/api.h b/api.h index 8268bb4e..00ee72ed 100644 --- a/api.h +++ b/api.h @@ -30,7 +30,6 @@ void getDBstats(int *sock); void getUnknownQueries(int *sock); // DNS methods -void addList(int *sock, char type, char list_type, char *data); void removeList(int *sock, char type, char list_type, char *client_message); // General API commands diff --git a/api_dns.c b/api_dns.c index 60c605eb..6116e5c0 100644 --- a/api_dns.c +++ b/api_dns.c @@ -11,63 +11,6 @@ #include "FTL.h" #include "api.h" -void addList(int *sock, char type, char list_type, char *data) { -// cJSON *input_root = cJSON_Parse(data); -// cJSON *domain_json = cJSON_GetObjectItemCaseSensitive(input_root, "domain"); - char *domain; - - // Validate domain -// if(!cJSON_IsString(domain_json)) { -// // No domain found -// sendAPIResponse(*sock, type, BAD_REQUEST); -// ssend(*sock, "\"status\":\"no_domain\""); -// return; -// } - -// domain = domain_json->valuestring; - - if(!isValidDomain(domain)) { - // Invalid domain -// sendAPIResponse(*sock, type, BAD_REQUEST); - ssend(*sock, "\"status\":\"invalid_domain\""); - return; - } - - // Get command - char *partial_command; - - if(list_type == WHITELIST) - partial_command = "sudo pihole -w -q "; - else if(list_type == BLACKLIST) - partial_command = "sudo pihole -b -q "; - else if(list_type == WILDLIST) - partial_command = "sudo pihole -wild -q "; - else { - logg("Invalid list type in addList"); - exit(EXIT_FAILURE); - } - - // Run command - char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); - strcpy(command, partial_command); - strcat(command, domain); - int return_code = system(command); - free(command); - - if(return_code == 0) { - // Successfully added to list -// sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"status\":\"success\""); - } - else { - // Failed to add to list -// sendAPIResponse(*sock, type, INTERNAL_ERROR); - ssend(*sock, "\"status\":\"unknown_error\""); - } - -// cJSON_Delete(input_root); -} - void removeList(int *sock, char type, char list_type, char *client_message) { char *domain = strrchr(client_message, '/'); From 071f924f70abf442d7341d4411e81d4d2c68d440 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 14:55:24 -0500 Subject: [PATCH 145/168] Remove the rest of the DNS commands and unused helpers They now live in the API Signed-off-by: Mcat12 --- api.c | 39 ------------------------ api.h | 7 ----- api_dns.c | 91 ------------------------------------------------------- 3 files changed, 137 deletions(-) delete mode 100644 api.c delete mode 100644 api_dns.c diff --git a/api.c b/api.c deleted file mode 100644 index a14b1a53..00000000 --- a/api.c +++ /dev/null @@ -1,39 +0,0 @@ -/* 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" - -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); -} diff --git a/api.h b/api.h index 00ee72ed..dacf5150 100644 --- a/api.h +++ b/api.h @@ -29,13 +29,6 @@ void getVersion(int *sock); void getDBstats(int *sock); void getUnknownQueries(int *sock); -// DNS methods -void removeList(int *sock, char type, char list_type, char *client_message); - -// General API commands -bool matchesRegex(char *regex_expression, char *input); -bool isValidDomain(char *domain); - // MessagePack serialization helpers void pack_eom(int sock); void pack_bool(int sock, bool value); diff --git a/api_dns.c b/api_dns.c deleted file mode 100644 index 6116e5c0..00000000 --- a/api_dns.c +++ /dev/null @@ -1,91 +0,0 @@ -/* 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 -* API /dns/ -* -* 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" - -void removeList(int *sock, char type, char list_type, char *client_message) { - char *domain = strrchr(client_message, '/'); - - // Validate domain - if(domain == NULL) { - // No domain found -// sendAPIResponse(*sock, type, NOT_FOUND); - ssend(*sock, "\"status\":\"not_found\""); - return; - } - - // Remove leading '/' - domain++; - - // Validate route - char *expected_route_start; - char *expected_route; - - if(list_type == WHITELIST) - expected_route_start = "/dns/whitelist/"; - else if(list_type == BLACKLIST) - expected_route_start = "/dns/blacklist/"; - else if(list_type == WILDLIST) - expected_route_start = "/dns/wildlist/"; - else { - logg("Invalid list type in removeList"); - exit(EXIT_FAILURE); - } - - expected_route = malloc((strlen(expected_route_start) + strlen(domain) + 1) * sizeof(char)); - strcpy(expected_route, expected_route_start); - strcat(expected_route, domain); - - if(!strstr(client_message, expected_route)) { - // Invalid route - free(expected_route); -// sendAPIResponse(*sock, type, NOT_FOUND); - ssend(*sock, "\"status\":\"not_found\""); - return; - } - - free(expected_route); - - if(!isValidDomain(domain)) { - // Invalid domain -// sendAPIResponse(*sock, type, BAD_REQUEST); - ssend(*sock, "\"status\":\"invalid_domain\""); - return; - } - - // Get command - char *partial_command; - - if(list_type == WHITELIST) - partial_command = "sudo pihole -w -q -d "; - else if(list_type == BLACKLIST) - partial_command = "sudo pihole -b -q -d "; - else - partial_command = "sudo pihole -wild -q -d "; - - // Run command - char *command = malloc((strlen(domain) + strlen(partial_command) + 1) * sizeof(char)); - strcpy(command, partial_command); - strcat(command, domain); - int return_code = system(command); - free(command); - - if(return_code == 0) { - // Successfully removed from list -// sendAPIResponse(*sock, type, OK); - ssend(*sock, "\"status\":\"success\""); - } - else { - // Failed to remove from list -// sendAPIResponse(*sock, type, INTERNAL_ERROR); - ssend(*sock, "\"status\":\"unknown_error\""); - } -} From 1983d3d11618d2f5dbaa075df4cfae17223e5a84 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 14:55:58 -0500 Subject: [PATCH 146/168] Rename api_stats.c to api.c Signed-off-by: Mcat12 --- api_stats.c => api.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename api_stats.c => api.c (100%) diff --git a/api_stats.c b/api.c similarity index 100% rename from api_stats.c rename to api.c From 700a21b0e1a934d00eeace097a52fc96d76ac55a Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 14:57:08 -0500 Subject: [PATCH 147/168] Update the Makefile with new API file structure Signed-off-by: Mcat12 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e550cf52..e662a53c 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ # Please see LICENSE file for your rights under this license. DEPS = FTL.h routines.h api.h version.h -OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o api_stats.o api_dns.o msgpack.o +OBJ = main.o structs.o log.o daemon.o parser.o signals.o socket.o request.o grep.o setupVars.o args.o flush.o threads.o gc.o config.o database.o api.o msgpack.o # Get git commit version and date GIT_BRANCH := $(shell git branch | sed -n 's/^\* //p') From 353e0cb212c96f09e38ac4a9a145785419e701a8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 14:58:02 -0500 Subject: [PATCH 148/168] Move command() up to the top of the file so it isn't as hidden Signed-off-by: Mcat12 --- request.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/request.c b/request.c index 3ff8a5a3..68539199 100644 --- a/request.c +++ b/request.c @@ -11,6 +11,10 @@ #include "FTL.h" #include "api.h" +bool command(char *client_message, const char* cmd) { + return strstr(client_message, cmd) != NULL; +} + void process_request(char *client_message, int *sock, char type) { char EOT[2]; @@ -147,7 +151,3 @@ void process_request(char *client_message, int *sock, char type) seom(*sock, type); } } - -bool command(char *client_message, const char* cmd) { - return strstr(client_message, cmd) != NULL; -} From 31c422eae621c735ada5a813d11af908fdc6ac81 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 17:37:44 -0500 Subject: [PATCH 149/168] Fix checkClientLimit Signed-off-by: Mcat12 --- socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/socket.c b/socket.c index ce75c4c5..8977935e 100644 --- a/socket.c +++ b/socket.c @@ -231,6 +231,7 @@ int checkClientLimit(int socket, char *ipAddr) { if(debugclients) logg("Client connected: %s, ID: %i", clientip[socket], socket); + return socket; } else { From 9b8b232a02859bb1993362cb8506d29957e17798 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Fri, 12 Jan 2018 17:44:48 -0500 Subject: [PATCH 150/168] Fix incorrect merging Signed-off-by: Mcat12 --- api.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api.c b/api.c index c174643a..53a8cf9a 100644 --- a/api.c +++ b/api.c @@ -264,9 +264,9 @@ void getTopDomains(char *client_message, int *sock) if(istelnet[*sock]) { if(audit && domains[j].wildcard) - ssend(*sock,"%i %i %s wildcard\n",i,domains[j].blockedcount,domains[j].domain); + ssend(*sock,"%i %i %s wildcard\n",n,domains[j].blockedcount,domains[j].domain); else - ssend(*sock,"%i %i %s\n",i,domains[j].blockedcount,domains[j].domain); + ssend(*sock,"%i %i %s\n",n,domains[j].blockedcount,domains[j].domain); } else { @@ -278,7 +278,7 @@ void getTopDomains(char *client_message, int *sock) else if(!blocked && showpermitted && (domains[j].count - domains[j].blockedcount) > 0) { if(istelnet[*sock]) - ssend(*sock,"%i %i %s\n",i,(domains[j].count - domains[j].blockedcount),domains[j].domain); + ssend(*sock,"%i %i %s\n",n,(domains[j].count - domains[j].blockedcount),domains[j].domain); else { pack_str32(*sock, domains[j].domain); @@ -374,7 +374,7 @@ void getTopClients(char *client_message, int *sock) if(includezeroclients || clients[j].count > 0) { if(istelnet[*sock]) - ssend(*sock,"%i %i %s %s\n",i,clients[j].count,clients[j].ip,clients[j].name); + ssend(*sock,"%i %i %s %s\n",n,clients[j].count,clients[j].ip,clients[j].name); else { pack_str32(*sock, clients[j].name); From c88af99ebdce978255abb8a963b57b94fe22bf8d Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 14 Jan 2018 17:30:55 -0500 Subject: [PATCH 151/168] Add `*.` to domains in getTopDomains if it's for audit and a wildcard Signed-off-by: Mcat12 --- api.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/api.c b/api.c index 53a8cf9a..5d3f6d61 100644 --- a/api.c +++ b/api.c @@ -261,17 +261,26 @@ void getTopDomains(char *client_message, int *sock) if(blocked && showblocked && domains[j].blockedcount > 0) { - if(istelnet[*sock]) + if(audit && domains[j].wildcard) { - if(audit && domains[j].wildcard) - ssend(*sock,"%i %i %s wildcard\n",n,domains[j].blockedcount,domains[j].domain); - else - ssend(*sock,"%i %i %s\n",n,domains[j].blockedcount,domains[j].domain); + if(istelnet[*sock]) + ssend(*sock, "%i %i %s wildcard\n", n, domains[j].blockedcount, domains[j].domain); + else { + char *fancyWildcard = calloc(2 + strlen(domains[j].domain), sizeof(char)); + sprintf(fancyWildcard, "*.%s", domains[j].domain); + pack_str32(*sock, fancyWildcard); + pack_int32(*sock, domains[j].blockedcount); + free(fancyWildcard); + } } else { - pack_str32(*sock, domains[j].domain); - pack_int32(*sock, domains[j].blockedcount); + if(istelnet[*sock]) + ssend(*sock, "%i %i %s\n", n, domains[j].blockedcount, domains[j].domain); + else { + pack_str32(*sock, domains[j].domain); + pack_int32(*sock, domains[j].blockedcount); + } } n++; } From cf0cdfed7d1379b7c5490cbb7d88c41f6619a880 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 20 Jan 2018 11:11:52 -0500 Subject: [PATCH 152/168] Reword api.c license header description Removed reference to the HTTP API. Signed-off-by: Mcat12 --- api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.c b/api.c index 18aeade2..07992a45 100644 --- a/api.c +++ b/api.c @@ -3,7 +3,7 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API /stats/ +* API Implementation * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ From 7c57b864438eef3b38dd560ead2e801ec2deedce Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 20 Jan 2018 11:14:42 -0500 Subject: [PATCH 153/168] Reword api.h license description header Signed-off-by: Mcat12 --- api.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.h b/api.h index dacf5150..0ba555b4 100644 --- a/api.h +++ b/api.h @@ -3,7 +3,7 @@ * Network-wide ad blocking via your own hardware. * * FTL Engine -* API commands and structures +* API commands and MessagePack helpers * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ From 1fd7ade5fe86e2793d18752141ae433fa778d227 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 20 Jan 2018 11:19:09 -0500 Subject: [PATCH 154/168] Add back detailed ssend error message Might have been lost in a merge. Signed-off-by: Mcat12 --- socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/socket.c b/socket.c index 329e1d87..862383d1 100644 --- a/socket.c +++ b/socket.c @@ -216,7 +216,7 @@ void ssend(int sock, const char *format, ...) va_end(args); if(ret > 0) if(!write(sock, buffer, strlen(buffer))) - logg("WARNING: Socket write returned error code %i", errno); + logg("WARNING: Socket write returned error %s (%i)", strerror(errno), errno); } void swrite(int sock, void *value, size_t size) { From 0aee819dc211c8862f75f06036d907f1069dcbe8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 20 Jan 2018 21:38:24 +0100 Subject: [PATCH 155/168] Implement some of the review comments Signed-off-by: DL6ER --- FTL.h | 3 --- request.c | 4 ++-- routines.h | 4 ++-- socket.c | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/FTL.h b/FTL.h index d20647b8..d256b102 100644 --- a/FTL.h +++ b/FTL.h @@ -23,7 +23,6 @@ #include #include #include -#include // struct sockaddr_in #include // char* inet_ntoa(struct in_addr in) @@ -211,8 +210,6 @@ typedef struct { enum { DATABASE_WRITE_TIMER, EXIT_TIMER }; enum { QUERIES, FORWARDED, CLIENTS, DOMAINS, OVERTIME, WILDCARD }; -enum { TELNET, SOCKET }; -enum { WHITELIST, BLACKLIST, WILDLIST }; enum { DNSSEC_UNSPECIFIED, DNSSEC_SECURE, DNSSEC_INSECURE, DNSSEC_BOGUS, DNSSEC_ABANDONED, DNSSEC_UNKNOWN }; logFileNamesStruct files; diff --git a/request.c b/request.c index 2db99f1a..8ff97e10 100644 --- a/request.c +++ b/request.c @@ -15,7 +15,7 @@ bool command(char *client_message, const char* cmd) { return strstr(client_message, cmd) != NULL; } -void process_request(char *client_message, int *sock, char type) +void process_request(char *client_message, int *sock) { char EOT[2]; EOT[0] = 0x04; @@ -148,6 +148,6 @@ void process_request(char *client_message, int *sock, char type) if(*sock != 0) { // Send EOM - seom(*sock, type); + seom(*sock); } } diff --git a/routines.h b/routines.h index 7aa659b4..e0303fb9 100644 --- a/routines.h +++ b/routines.h @@ -40,7 +40,7 @@ void memory_check(int which); void close_telnet_socket(void); void close_unix_socket(void); -void seom(int sock, char type); +void seom(int sock); void ssend(int sock, const char *format, ...); void swrite(int sock, void *value, size_t size); void *telnet_listening_thread_IPv4(void *args); @@ -50,7 +50,7 @@ void *socket_listening_thread(void *args); bool ipv6_available(void); bool bind_sockets(void); -void process_request(char *client_message, int *sock, char type); +void process_request(char *client_message, int *sock); bool command(char *client_message, const char* cmd); bool matchesEndpoint(char *client_message, const char *cmd); diff --git a/socket.c b/socket.c index d3266191..64d25abe 100644 --- a/socket.c +++ b/socket.c @@ -41,7 +41,7 @@ void saveport(void) } } -char bind_to_telnet_port_IPv4(char type, int *socketdescriptor) +char bind_to_telnet_port_IPv4(int *socketdescriptor) { // IPv4 socket *socketdescriptor = socket(AF_INET, SOCK_STREAM, 0); @@ -66,7 +66,7 @@ char bind_to_telnet_port_IPv4(char type, int *socketdescriptor) memset(&serv_addr4, 0, sizeof(serv_addr4)); serv_addr4.sin_family = AF_INET; - if(config.socket_listenlocal && type == SOCKET) + if(config.socket_listenlocal) serv_addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); else serv_addr4.sin_addr.s_addr = INADDR_ANY; @@ -90,7 +90,7 @@ char bind_to_telnet_port_IPv4(char type, int *socketdescriptor) return 1; } -char bind_to_telnet_port_IPv6(char type, int *socketdescriptor) +char bind_to_telnet_port_IPv6(int *socketdescriptor) { // IPv6 socket *socketdescriptor = socket(AF_INET6, SOCK_STREAM, 0); @@ -121,7 +121,7 @@ char bind_to_telnet_port_IPv6(char type, int *socketdescriptor) memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin6_family = AF_INET6; - if(config.socket_listenlocal && type == SOCKET) + if(config.socket_listenlocal) serv_addr.sin6_addr = in6addr_loopback; else serv_addr.sin6_addr = in6addr_any; @@ -199,9 +199,9 @@ void removeport(void) fclose(f); } -void seom(int sock, char type) +void seom(int sock) { - if(type == TELNET) + if(istelnet[sock]) ssend(sock, "---EOM---\n\n"); else pack_eom(sock); @@ -330,7 +330,7 @@ void *telnet_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_request(message, &sock, TELNET); + process_request(message, &sock); free(message); // Release thread lock @@ -395,7 +395,7 @@ void *socket_connection_handler_thread(void *socket_desc) // Requests should not be processed/answered when data is about to change enable_thread_lock(threadname); - process_request(message, &sock, SOCKET); + process_request(message, &sock); free(message); // Release thread lock @@ -431,12 +431,12 @@ void *socket_connection_handler_thread(void *socket_desc) bool bind_sockets(void) { // Initialize IPv4 telnet socket - bind_to_telnet_port_IPv4(SOCKET, &telnetfd4); + bind_to_telnet_port_IPv4(&telnetfd4); // Initialize IPv6 telnet socket // only if IPv6 interfaces are available if(ipv6_available()) - bind_to_telnet_port_IPv6(SOCKET, &telnetfd6); + bind_to_telnet_port_IPv6(&telnetfd6); saveport(); From df3b049edee93bad51225ec70f9ed56cf80b5c33 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 20 Jan 2018 21:41:12 +0100 Subject: [PATCH 156/168] Remove clientips[] Signed-off-by: DL6ER --- FTL.h | 1 - request.c | 7 ------- socket.c | 24 +++++------------------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/FTL.h b/FTL.h index d256b102..f1b5bc23 100644 --- a/FTL.h +++ b/FTL.h @@ -254,5 +254,4 @@ long int lastdbindex; bool travis; bool DBdeleteoldqueries; bool rereadgravity; -char * clientip[MAXCONNS]; bool istelnet[MAXCONNS]; diff --git a/request.c b/request.c index 8ff97e10..ad6c53d2 100644 --- a/request.c +++ b/request.c @@ -121,13 +121,6 @@ void process_request(char *client_message, int *sock) if(debugclients) logg("Client wants to disconnect, ID: %i",*sock); - // Free memory containing IP address of this client - if(clientip[*sock] != NULL) - { - free(clientip[*sock]); - clientip[*sock] = NULL; - } - close(*sock); *sock = 0; } diff --git a/socket.c b/socket.c index 64d25abe..6406d5b8 100644 --- a/socket.c +++ b/socket.c @@ -227,19 +227,17 @@ void swrite(int sock, void *value, size_t size) { logg("WARNING: Socket write returned error code %i", errno); } -int checkClientLimit(int socket, char *ipAddr) { +int checkClientLimit(int socket) { if(socket < MAXCONNS) { - clientip[socket] = strdup(ipAddr); - if(debugclients) - logg("Client connected: %s, ID: %i", clientip[socket], socket); + logg("Client connected: %i", socket); return socket; } else { if(debugclients) - logg("Client denied (at max capacity): %s, ID: %i", ipAddr, socket); + logg("Client denied (at max capacity of %i): %i", MAXCONNS, socket); close(socket); return -1; @@ -265,15 +263,13 @@ int listener(int sockfd, char type) memset(&in4_addr, 0, sizeof(in4_addr)); socklen = sizeof(un_addr); socket = accept(sockfd, (struct sockaddr *) &in4_addr, &socklen); - return checkClientLimit(socket, inet_ntoa(in4_addr.sin_addr)); + return checkClientLimit(socket); case 6: // Internet socket (IPv6) memset(&in6_addr, 0, sizeof(in6_addr)); socklen = sizeof(un_addr); - char str[INET6_ADDRSTRLEN]; socket = accept(sockfd, (struct sockaddr *) &in6_addr, &socklen); - inet_ntop(AF_INET6, &in6_addr.sin6_addr, str, INET6_ADDRSTRLEN); - return checkClientLimit(socket, str); + return checkClientLimit(socket); default: // Should not happen logg("Cannot listen on type %i connection, code error!", type); @@ -356,11 +352,6 @@ void *telnet_connection_handler_thread(void *socket_desc) close(sock); free(socket_desc); - if(clientip[sock] != NULL) { - free(clientip[sock]); - clientip[sock] = NULL; - } - return 0; } @@ -420,11 +411,6 @@ void *socket_connection_handler_thread(void *socket_desc) close(sock); free(socket_desc); - if(clientip[sock] != NULL) { - free(clientip[sock]); - clientip[sock] = NULL; - } - return 0; } From 996689fb456fdcd4f28a64d149487926859c8234 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 20 Jan 2018 21:42:00 +0100 Subject: [PATCH 157/168] Remove sleep from main() Signed-off-by: DL6ER --- main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/main.c b/main.c index ab3c8c4f..3dd65c46 100644 --- a/main.c +++ b/main.c @@ -70,7 +70,6 @@ int main (int argc, char* argv[]) { logg("Unable to open Pi-hole log processing thread. Exiting..."); killed = 1; } - sleepms(100); // Bind to sockets after initial log parsing bool telnet_ipv6 = bind_sockets(); @@ -98,7 +97,6 @@ int main (int argc, char* argv[]) { logg("Unable to open Unix socket listening thread. Exiting..."); killed = 1; } - sleepms(100); while(!killed) { @@ -187,7 +185,7 @@ int main (int argc, char* argv[]) { save_to_DB(); logg("Finished final database update"); } - + // Close sockets close_telnet_socket(); close_unix_socket(); From 9f5ae6242337ccd56ee4205bdb1bb01b5e8431b8 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sat, 20 Jan 2018 21:40:41 -0500 Subject: [PATCH 158/168] Fix indentation in msgpack.c Signed-off-by: Mcat12 --- msgpack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msgpack.c b/msgpack.c index c3123895..d8aa70c8 100644 --- a/msgpack.c +++ b/msgpack.c @@ -69,8 +69,8 @@ void pack_int64(int sock, int64_t value) { void pack_float(int sock, float value) { // Need to use memcpy to do a direct copy without reinterpreting the bytes. It should get optimized away. uint32_t bigEValue; - memcpy(&bigEValue, &value, sizeof(bigEValue)); - bigEValue = htonl(bigEValue); + memcpy(&bigEValue, &value, sizeof(bigEValue)); + bigEValue = htonl(bigEValue); pack_basic(sock, 0xca, &bigEValue, sizeof(bigEValue)); } From 173efff918ac201f7f296417f8caf8ae84e2af25 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:38:34 -0500 Subject: [PATCH 159/168] Don't use buffers in getStats Signed-off-by: Mcat12 --- api.c | 57 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/api.c b/api.c index 07992a45..2366162b 100644 --- a/api.c +++ b/api.c @@ -52,29 +52,20 @@ void getStats(int *sock) if(total > 0) percentage = 1e2*blocked/total; - // MAX_INT is 10 digits, +1 for the null terminator - char domains_blocked[11]; - char status[9]; + // Send domains being blocked + if(istelnet[*sock]) { + switch(blockingstatus) { + case 0: // Blocking disabled + ssend(*sock, "domains_being_blocked N/A\n"); + break; + default: // Blocking enabled or unknown + ssend(*sock, "domains_being_blocked %i\n", counters.gravity); + break; + } - switch(blockingstatus) - { - case 0: // Blocking disabled - if(istelnet[*sock]) - strncpy(domains_blocked, "N/A", 4); - else - strncpy(domains_blocked, "\"N/A\"", 6); - - strncpy(status, "disabled", 9); - break; - case 1: // Blocking Enabled - snprintf(domains_blocked, 11, "%i", counters.gravity); - strncpy(status, "enabled", 8); - break; - default: // Unknown status - snprintf(domains_blocked, 11, "%i", counters.gravity); - strncpy(status, "unknown", 8); - break; } + else + pack_int32(*sock, counters.gravity); // unique_clients: count only clients that have been active within the most recent 24 hours int i, activeclients = 0; @@ -86,17 +77,15 @@ void getStats(int *sock) } if(istelnet[*sock]) { - ssend(*sock, "domains_being_blocked %s\ndns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", - domains_blocked, total, blocked, percentage); + ssend(*sock, "dns_queries_today %i\nads_blocked_today %i\nads_percentage_today %f\n", + total, blocked, percentage); ssend(*sock, "unique_domains %i\nqueries_forwarded %i\nqueries_cached %i\n", counters.domains, counters.forwardedqueries, counters.cached); ssend(*sock, "clients_ever_seen %i\n", counters.clients); ssend(*sock, "unique_clients %i\n", activeclients); - ssend(*sock, "status %s\n", status); } else { - pack_int32(*sock, counters.gravity); pack_int32(*sock, total); pack_int32(*sock, blocked); pack_float(*sock, percentage); @@ -105,9 +94,25 @@ void getStats(int *sock) pack_int32(*sock, counters.cached); pack_int32(*sock, counters.clients); pack_int32(*sock, activeclients); - pack_uint8(*sock, blockingstatus); } + // Send status + if(istelnet[*sock]) { + switch(blockingstatus) { + case 0: // Blocking disabled + ssend(*sock, "status: disabled\n"); + break; + case 1: // Blocking enabled + ssend(*sock, "status: enabled\n"); + break; + default: // Unknown status + ssend(*sock, "status: unknown\n"); + break; + } + } + else + pack_uint8(*sock, blockingstatus); + if(debugclients) logg("Sent stats data to client, ID: %i", *sock); } From 76aff45eb5f7c65cbfe6cc9ebaecef7b1c426009 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:41:23 -0500 Subject: [PATCH 160/168] Clarify getOverTime comment Signed-off-by: Mcat12 --- api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.c b/api.c index 2366162b..1f68740d 100644 --- a/api.c +++ b/api.c @@ -121,7 +121,7 @@ void getOverTime(int *sock) { int i, j = 9999999; - // Get first time slot with total or blocked greater than zero (the array will go down over time due to the rolling window) + // Start with the first non-empty overTime slot for(i=0; i < counters.overTime; i++) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); From ad99f975fe2170c9ad569543a846eea527095700 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:43:03 -0500 Subject: [PATCH 161/168] Fix under-allocation in getTopDomains Signed-off-by: Mcat12 --- api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.c b/api.c index 1f68740d..443fd734 100644 --- a/api.c +++ b/api.c @@ -271,7 +271,7 @@ void getTopDomains(char *client_message, int *sock) if(istelnet[*sock]) ssend(*sock, "%i %i %s wildcard\n", n, domains[j].blockedcount, domains[j].domain); else { - char *fancyWildcard = calloc(2 + strlen(domains[j].domain), sizeof(char)); + char *fancyWildcard = calloc(3 + strlen(domains[j].domain), sizeof(char)); sprintf(fancyWildcard, "*.%s", domains[j].domain); pack_str32(*sock, fancyWildcard); pack_int32(*sock, domains[j].blockedcount); From 4e8027b74d7543afdf7e96b98caafa7c463f7a1f Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:53:29 -0500 Subject: [PATCH 162/168] Use float instead of double in getForwardDestinationsOverTime Signed-off-by: Mcat12 --- api.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api.c b/api.c index 443fd734..4f4a67f3 100644 --- a/api.c +++ b/api.c @@ -841,7 +841,7 @@ void getForwardDestinationsOverTime(int *sock) { for(i = sendit; i < counters.overTime; i++) { - double percentage; + float percentage; validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); if(istelnet[*sock]) @@ -896,7 +896,7 @@ void getForwardDestinationsOverTime(int *sock) // // To get the total percentage of a specific forward destination on the total // number of queries, we simply have to multiply a and b as done below: - percentage = 1e2 * thisforward / forwardedsum * (overTime[i].total - (overTime[i].cached + overTime[i].blocked)) / overTime[i].total; + percentage = (float) (1e2 * thisforward / forwardedsum * (overTime[i].total - (overTime[i].cached + overTime[i].blocked)) / overTime[i].total); } else percentage = 0.0; @@ -910,14 +910,14 @@ void getForwardDestinationsOverTime(int *sock) // Avoid floating point exceptions if(overTime[i].total > 0) // Forward count for destination "local" is cached + blocked normalized by total: - percentage = 1e2 * (overTime[i].cached + overTime[i].blocked) / overTime[i].total; + percentage = (float) (1e2 * (overTime[i].cached + overTime[i].blocked) / overTime[i].total); else percentage = 0.0; if(istelnet[*sock]) ssend(*sock, " %.2f\n", percentage); else - pack_float(*sock, (float) percentage); + pack_float(*sock, percentage); } } From be5815fbdadc659d6e983d4b9c8d02f8b30ce2b6 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:54:21 -0500 Subject: [PATCH 163/168] Use float instead of double in getQueryTypesOverTime Signed-off-by: Mcat12 --- api.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api.c b/api.c index 4f4a67f3..070302f3 100644 --- a/api.c +++ b/api.c @@ -955,20 +955,20 @@ void getQueryTypesOverTime(int *sock) { validate_access("overTime", i, true, __LINE__, __FUNCTION__, __FILE__); - double percentageIPv4 = 0.0, percentageIPv6 = 0.0; + float percentageIPv4 = 0.0, percentageIPv6 = 0.0; int sum = overTime[i].querytypedata[0] + overTime[i].querytypedata[1]; if(sum > 0) { - percentageIPv4 = 1e2*overTime[i].querytypedata[0] / sum; - percentageIPv6 = 1e2*overTime[i].querytypedata[1] / sum; + percentageIPv4 = (float) (1e2 * overTime[i].querytypedata[0] / sum); + percentageIPv6 = (float) (1e2 * overTime[i].querytypedata[1] / sum); } if(istelnet[*sock]) ssend(*sock, "%i %.2f %.2f\n", overTime[i].timestamp, percentageIPv4, percentageIPv6); else { pack_int32(*sock, overTime[i].timestamp); - pack_float(*sock, (float) percentageIPv4); - pack_float(*sock, (float) percentageIPv6); + pack_float(*sock, percentageIPv4); + pack_float(*sock, percentageIPv6); } } } From 9007af7dbb05df2f1ec4bcfb9ff7afaeb5b1053f Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 10:54:59 -0500 Subject: [PATCH 164/168] Fix under-allocation in getVersion Signed-off-by: Mcat12 --- api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.c b/api.c index 070302f3..538f14a9 100644 --- a/api.c +++ b/api.c @@ -1000,7 +1000,7 @@ void getVersion(int *sock) if(istelnet[*sock]) ssend(*sock, "version vDev-%s\ntag %s\nbranch %s\ndate %s\n", hash, tag, GIT_BRANCH, GIT_DATE); else { - char *hashVersion = calloc(5 + strlen(hash), sizeof(char)); + char *hashVersion = calloc(6 + strlen(hash), sizeof(char)); sprintf(hashVersion, "vDev-%s", hash); pack_str32(*sock, hashVersion); From 42bf9a4c6f29f7c3700299855643b909e075dd02 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 11:37:37 -0500 Subject: [PATCH 165/168] Fix status output in getStats Signed-off-by: Mcat12 --- api.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api.c b/api.c index 538f14a9..b599c46a 100644 --- a/api.c +++ b/api.c @@ -100,13 +100,13 @@ void getStats(int *sock) if(istelnet[*sock]) { switch(blockingstatus) { case 0: // Blocking disabled - ssend(*sock, "status: disabled\n"); + ssend(*sock, "status disabled\n"); break; case 1: // Blocking enabled - ssend(*sock, "status: enabled\n"); + ssend(*sock, "status enabled\n"); break; default: // Unknown status - ssend(*sock, "status: unknown\n"); + ssend(*sock, "status unknown\n"); break; } } From b6b7b5708ba29b29f5148922d800c0f57f11648c Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 12:46:46 -0500 Subject: [PATCH 166/168] Only allow up to 2GB strings to be sent Signed-off-by: Mcat12 --- msgpack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msgpack.c b/msgpack.c index d8aa70c8..24eaa362 100644 --- a/msgpack.c +++ b/msgpack.c @@ -92,8 +92,8 @@ void pack_str32(int sock, char *string) { // Make sure that the length is less than 4294967296 size_t length = strlen(string); - if(length >= 4294967296) { - logg("Tried to send a str32 longer than 4294967295 bytes!"); + if(length >= 2147483648) { + logg("Tried to send a str32 longer than 2147483647 bytes!"); exit(EXIT_FAILURE); } From 9bc056248f8191327288018ccb694617994eba91 Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 15:02:41 -0500 Subject: [PATCH 167/168] Use a static buffer for getallqueries string arguments Signed-off-by: Mcat12 --- api.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/api.c b/api.c index b599c46a..fa295c22 100644 --- a/api.c +++ b/api.c @@ -571,12 +571,20 @@ void getAllQueries(char *client_message, int *sock) } // Domain filtering? if(command(client_message, ">getallqueries-domain")) { - sscanf(client_message, ">getallqueries-domain %ms", &domainname); + // Get domain name we want to see only (limit length to 255 chars) + domainname = calloc(256, sizeof(char)); + sscanf(client_message, ">getallqueries-domain %255s", domainname); + if(debugclients) + logg("Showing only queries with domain %s", domainname); filterdomainname = true; } // Client filtering? if(command(client_message, ">getallqueries-client")) { - sscanf(client_message, ">getallqueries-client %ms", &clientname); + clientname = calloc(256, sizeof(char)); + // Get client name we want to see only (limit length to 255 chars) + sscanf(client_message, ">getallqueries-client %255s", clientname); + if(debugclients) + logg("Showing only queries with client %s", clientname); filterclientname = true; } From 5341b18ecf9caeee2ab5634719f6eb634301cf7e Mon Sep 17 00:00:00 2001 From: Mcat12 Date: Sun, 21 Jan 2018 15:40:58 -0500 Subject: [PATCH 168/168] Handle possible pack_fixstr or pack_str32 error Signed-off-by: Mcat12 --- api.c | 72 ++++++++++++++++++++++++++++++------------------- api.h | 4 +-- msgpack.c | 14 +++++++--- socket_client.c | 7 ++--- 4 files changed, 60 insertions(+), 37 deletions(-) diff --git a/api.c b/api.c index fa295c22..dbc6add3 100644 --- a/api.c +++ b/api.c @@ -273,7 +273,10 @@ void getTopDomains(char *client_message, int *sock) else { char *fancyWildcard = calloc(3 + strlen(domains[j].domain), sizeof(char)); sprintf(fancyWildcard, "*.%s", domains[j].domain); - pack_str32(*sock, fancyWildcard); + + if(!pack_str32(*sock, fancyWildcard)) + return; + pack_int32(*sock, domains[j].blockedcount); free(fancyWildcard); } @@ -283,7 +286,9 @@ void getTopDomains(char *client_message, int *sock) if(istelnet[*sock]) ssend(*sock, "%i %i %s\n", n, domains[j].blockedcount, domains[j].domain); else { - pack_str32(*sock, domains[j].domain); + if(!pack_str32(*sock, domains[j].domain)) + return; + pack_int32(*sock, domains[j].blockedcount); } } @@ -295,7 +300,9 @@ void getTopDomains(char *client_message, int *sock) ssend(*sock,"%i %i %s\n",n,(domains[j].count - domains[j].blockedcount),domains[j].domain); else { - pack_str32(*sock, domains[j].domain); + if(!pack_str32(*sock, domains[j].domain)) + return; + pack_int32(*sock, domains[j].count - domains[j].blockedcount); } n++; @@ -391,8 +398,9 @@ void getTopClients(char *client_message, int *sock) ssend(*sock,"%i %i %s %s\n",n,clients[j].count,clients[j].ip,clients[j].name); else { - pack_str32(*sock, clients[j].name); - pack_str32(*sock, clients[j].ip); + if(!pack_str32(*sock, clients[j].name) || !pack_str32(*sock, clients[j].ip)) + return; + pack_int32(*sock, clients[j].count); } n++; @@ -508,8 +516,9 @@ void getForwardDestinations(char *client_message, int *sock) ssend(*sock, "%i %.2f %s %s\n", i, percentage, ip, name); else { - pack_str32(*sock, name); - pack_str32(*sock, ip); + if(!pack_str32(*sock, name) || !pack_str32(*sock, ip)) + return; + pack_float(*sock, (float) percentage); } } @@ -710,11 +719,12 @@ void getAllQueries(char *client_message, int *sock) pack_int32(*sock, queries[i].timestamp); // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) - pack_fixstr(*sock, qtype); + if(!pack_fixstr(*sock, qtype)) + return; // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) - pack_str32(*sock, domains[queries[i].domainID].domain); - pack_str32(*sock, client); + if(!pack_str32(*sock, domains[queries[i].domainID].domain) || !pack_str32(*sock, client)) + return; pack_uint8(*sock, queries[i].status); pack_uint8(*sock, domains[queries[i].domainID].dnssec); @@ -762,8 +772,8 @@ void getRecentBlocked(char *client_message, int *sock) if(istelnet[*sock]) ssend(*sock,"%s\n", domains[queries[i].domainID].domain); - else - pack_str32(*sock, domains[queries[i].domainID].domain); + else if(!pack_str32(*sock, domains[queries[i].domainID].domain)) + return; } if(found >= num) @@ -840,8 +850,8 @@ void getForwardDestinationsOverTime(int *sock) ip = forwarded[i].ip; } - pack_str32(*sock, name); - pack_str32(*sock, ip); + if(!pack_str32(*sock, name) || !pack_str32(*sock, ip)) + return; } } @@ -994,10 +1004,11 @@ void getVersion(int *sock) if(istelnet[*sock]) ssend(*sock, "version %s\ntag %s\nbranch %s\ndate %s\n", GIT_VERSION, tag, GIT_BRANCH, GIT_DATE); else { - pack_str32(*sock, GIT_VERSION); - pack_str32(*sock, (char *) tag); - pack_str32(*sock, GIT_BRANCH); - pack_str32(*sock, GIT_DATE); + if(!pack_str32(*sock, GIT_VERSION) || + !pack_str32(*sock, (char *) tag) || + !pack_str32(*sock, GIT_BRANCH) || + !pack_str32(*sock, GIT_DATE)) + return; } } else { @@ -1011,10 +1022,11 @@ void getVersion(int *sock) char *hashVersion = calloc(6 + strlen(hash), sizeof(char)); sprintf(hashVersion, "vDev-%s", hash); - pack_str32(*sock, hashVersion); - pack_str32(*sock, (char *) tag); - pack_str32(*sock, GIT_BRANCH); - pack_str32(*sock, GIT_DATE); + if(!pack_str32(*sock, hashVersion) || + !pack_str32(*sock, (char *) tag) || + !pack_str32(*sock, GIT_BRANCH) || + !pack_str32(*sock, GIT_DATE)) + return; free(hashVersion); } @@ -1044,7 +1056,9 @@ void getDBstats(int *sock) else { pack_int32(*sock, get_number_of_queries_in_DB()); pack_int64(*sock, filesize); - pack_str32(*sock, (char *) sqlite3_libversion()); + + if(!pack_str32(*sock, (char *) sqlite3_libversion())) + return; } if(debugclients) @@ -1166,8 +1180,9 @@ void getClientNames(int *sock) if(istelnet[*sock]) ssend(*sock, "%i %i %s %s\n", i, clients[i].count, clients[i].ip, clients[i].name); else { - pack_str32(*sock, clients[i].name); - pack_str32(*sock, clients[i].ip); + if(!pack_str32(*sock, clients[i].name) || !pack_str32(*sock, clients[i].ip)) + return; + pack_int32(*sock, clients[i].count); } } @@ -1213,11 +1228,12 @@ void getUnknownQueries(int *sock) pack_int32(*sock, queries[i].id); // Use a fixstr because the length of qtype is always 4 (max is 31 for fixstr) - pack_fixstr(*sock, type); + if(!pack_fixstr(*sock, type)) + return; // Use str32 for domain and client because we have no idea how long they will be (max is 4294967295 for str32) - pack_str32(*sock, domains[queries[i].domainID].domain); - pack_str32(*sock, client); + if(!pack_str32(*sock, domains[queries[i].domainID].domain) || !pack_str32(*sock, client)) + return; pack_uint8(*sock, queries[i].status); pack_bool(*sock, queries[i].complete); diff --git a/api.h b/api.h index 0ba555b4..c34747f3 100644 --- a/api.h +++ b/api.h @@ -37,6 +37,6 @@ void pack_uint64(int sock, uint64_t value); void pack_int32(int sock, int32_t value); void pack_int64(int sock, int64_t value); void pack_float(int sock, float value); -void pack_fixstr(int sock, char *string); -void pack_str32(int sock, char *string); +bool pack_fixstr(int sock, char *string); +bool pack_str32(int sock, char *string); void pack_map16_start(int sock, uint16_t length); diff --git a/msgpack.c b/msgpack.c index 24eaa362..0fe622de 100644 --- a/msgpack.c +++ b/msgpack.c @@ -74,27 +74,31 @@ void pack_float(int sock, float value) { pack_basic(sock, 0xca, &bigEValue, sizeof(bigEValue)); } -void pack_fixstr(int sock, char *string) { +// Return true if successful +bool pack_fixstr(int sock, char *string) { // Make sure that the length is less than 32 size_t length = strlen(string); if(length >= 32) { logg("Tried to send a fixstr longer than 31 bytes!"); - exit(EXIT_FAILURE); + return false; } uint8_t format = (uint8_t) (0xA0 | length); swrite(sock, &format, sizeof(format)); swrite(sock, string, length); + + return true; } -void pack_str32(int sock, char *string) { +// Return true if successful +bool pack_str32(int sock, char *string) { // Make sure that the length is less than 4294967296 size_t length = strlen(string); if(length >= 2147483648) { logg("Tried to send a str32 longer than 2147483647 bytes!"); - exit(EXIT_FAILURE); + return false; } uint8_t format = 0xdb; @@ -102,6 +106,8 @@ void pack_str32(int sock, char *string) { uint32_t bigELength = htonl((uint32_t) length); swrite(sock, &bigELength, sizeof(bigELength)); swrite(sock, string, length); + + return true; } void pack_map16_start(int sock, uint16_t length) { diff --git a/socket_client.c b/socket_client.c index 37abe99b..5f6c7e17 100644 --- a/socket_client.c +++ b/socket_client.c @@ -16,6 +16,8 @@ #include #include #include +#include + #define BUF 1024 int main (int argc, char **argv) { @@ -38,6 +40,7 @@ int main (int argc, char **argv) { address.sun_family = AF_LOCAL; char *command = ">stats"; + strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); int i; for(i = 1; i < argc; i++) { @@ -50,15 +53,13 @@ int main (int argc, char **argv) { // Set socket file location (respect special location on the CI system Travis) if(strcmp(argv[i], "travis") == 0) strcpy(address.sun_path,"pihole-FTL.sock"); - else - strcpy(address.sun_path,"/var/run/pihole/FTL.sock"); } // Connect to the socket provided by pihole-FTL ret = connect(socketfd, (struct sockaddr *) &address, sizeof (address)); if (ret != 0) { - printf("Error establishing connection!\n"); + printf("Error establishing connection! %s\n", strerror(errno)); exit(EXIT_FAILURE); } printf("Connection established\n");