diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 98a96acd..acf9a60e 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -15,8 +15,8 @@ set(sources dns.c dns.h ftl.c - ftl/config.c - ftl/network.c + config.c + network.c history.c list.c queries.c diff --git a/src/api/api.c b/src/api/api.c index 3506d296..42ff89af 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -35,10 +35,6 @@ static struct { { "/api/ftl/logs/dns", api_ftl_logs_dns, { false, false } }, { "/api/ftl/sysinfo", api_ftl_sysinfo, { false, false } }, { "/api/ftl/dbinfo", api_ftl_dbinfo, { false, false } }, - { "/api/ftl/config", api_ftl_config, { false, false } }, - { "/api/ftl/gateway", api_ftl_gateway, { false, false } }, - { "/api/ftl/interfaces", api_ftl_interfaces, { false, false } }, - { "/api/ftl/network", api_ftl_network, { false, false } }, { "/api/ftl/endpoints", api_ftl_endpoints, { false, false } }, { "/api/history/clients", api_history_clients, { false, false } }, { "/api/history", api_history, { false, false } }, @@ -63,6 +59,10 @@ static struct { { "/api/version", api_version, { false, false } }, { "/api/auth", api_auth, { false, false } }, { "/api/settings/web", api_settings_web, { false, false } }, + { "/api/config", api_config, { false, false } }, + { "/api/network/gateway", api_network_gateway, { false, false } }, + { "/api/network/interfaces", api_network_interfaces, { false, false } }, + { "/api/network/devices", api_network_devices, { false, false } }, { "/api/docs", api_docs, { false, false } }, }; @@ -130,9 +130,7 @@ static int api_ftl_endpoints(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } cJSON *json = JSON_NEW_OBJECT(); cJSON *endpoints = JSON_NEW_ARRAY(); diff --git a/src/api/api.h b/src/api/api.h index ac44b5a1..707a6601 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -50,12 +50,12 @@ int api_ftl_dbinfo(struct ftl_conn *api); int api_ftl_sysinfo(struct ftl_conn *api); int get_ftl_obj(struct ftl_conn *api, cJSON *ftl, const bool is_locked); int get_system_obj(struct ftl_conn *api, cJSON *system); -int api_ftl_config(struct ftl_conn *api); -int api_ftl_gateway(struct ftl_conn *api); -int api_ftl_interfaces(struct ftl_conn *api); -int api_ftl_network(struct ftl_conn *api); +int api_config(struct ftl_conn *api); // Network methods +int api_network_gateway(struct ftl_conn *api); +int api_network_interfaces(struct ftl_conn *api); +int api_network_devices(struct ftl_conn *api); // DNS methods int api_dns_blocking(struct ftl_conn *api); diff --git a/src/api/ftl/config.c b/src/api/config.c similarity index 99% rename from src/api/ftl/config.c rename to src/api/config.c index b409b124..b2f1a8a3 100644 --- a/src/api/ftl/config.c +++ b/src/api/config.c @@ -37,13 +37,11 @@ // Interate through directories #include -int api_ftl_config(struct ftl_conn *api) +int api_config(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } cJSON *config_j = JSON_NEW_OBJECT(); JSON_ADD_NUMBER_TO_OBJECT(config_j, "debug",config.debug); /* TODO: Split into individual fields */ diff --git a/src/api/dns.c b/src/api/dns.c index f9c0a52e..c6cae91c 100644 --- a/src/api/dns.c +++ b/src/api/dns.c @@ -51,9 +51,7 @@ static int set_blocking(struct ftl_conn *api) { // Verify requesting client is allowed to access this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } if (api->payload.json == NULL) { return send_json_error(api, 400, @@ -125,9 +123,7 @@ int api_dns_cache(struct ftl_conn *api) { // Verify requesting client is allowed to access this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } struct cache_info ci = { 0 }; get_dnsmasq_cache_info(&ci); diff --git a/src/api/docs/content/specs/config.yaml b/src/api/docs/content/specs/config.yaml new file mode 100644 index 00000000..578dc216 --- /dev/null +++ b/src/api/docs/content/specs/config.yaml @@ -0,0 +1,238 @@ +openapi: 3.0.2 +components: + paths: + config: + get: + summary: Get info about the config of your Pi-hole + tags: + - "FTL information" + operationId: "get_config" + description: | + This API hook returns infos about the config of your Pi-hole. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'config.yaml#/components/schemas/config' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' + schemas: + config: + type: object + properties: + config: + type: object + properties: + debug: + type: integer + example: 0 + dns: + type: object + properties: + CNAMEdeepInspect: + type: boolean + example: true + blockESNI: + type: boolean + example: true + EDN0ECS: + type: boolean + example: true + ignoreLocalhost: + type: boolean + example: false + showDNSSEC: + type: boolean + example: true + analyzeAAAA: + type: boolean + example: true + analyzeOnlyAandAAAA: + type: boolean + example: false + piholePTR: + type: string + example: "PI.HOLE" + replyWhenBusy: + type: string + example: "ALLOW" + blockTTL: + type: integer + example: 2 + blockingmode: + type: string + example: "NULL" + port: + type: integer + example: 53 + specialDomains: + type: object + properties: + mozillaCanary: + type: boolean + example: true + iCloudPrivateRelay: + type: boolean + example: true + reply: + type: object + properties: + host: + type: object + properties: + ipv4: + type: string + example: "" + ipv6: + type: string + example: "" + blocking: + type: object + properties: + ipv4: + type: string + example: "" + ipv6: + type: string + example: "" + rateLimit: + type: object + properties: + count: + type: integer + example: 1000 + interval: + type: integer + example: 60 + resolver: + type: object + properties: + resolveIPv4: + type: boolean + example: true + resolveIPv6: + type: boolean + example: true + networkNames: + type: boolean + example: true + refreshNames: + type: string + example: "IPV4_ONLY" + database: + type: object + properties: + DBimport: + type: boolean + example: true + DBexport: + type: boolean + example: true + maxHistory: + type: integer + example: 86400 + maxDBdays: + type: integer + example: 365 + DBinterval: + type: integer + example: 60 + network: + type: object + properties: + parseARPcache: + type: boolean + example: true + expire: + type: integer + example: 365 + http: + type: object + properties: + localAPIauth: + type: boolean + example: true + prettyJSON: + type: boolean + example: false + sessionTimeout: + type: integer + example: 300 + domain: + type: string + example: "pi.hole" + acl: + type: string + example: "+0.0.0.0/0" + port: + type: string + example: "8080,[::]:8080" + paths: + type: object + properties: + webroot: + type: string + example: "/var/www/html" + webhome: + type: string + example: "/admin/" + files: + type: object + properties: + log: + type: string + example: "/var/log/pihole/FTL.log" + pid: + type: string + example: "/run/pihole-FTL.pid" + database: + type: string + example: "/etc/pihole/pihole-FTL.db" + gravity: + type: string + example: "/etc/pihole/gravity.db" + macvendor: + type: string + example: "/etc/pihole/macvendor.db" + setupVars: + type: string + example: "/etc/pihole/setupVars.conf" + http_info: + type: string + example: "/var/log/pihole/HTTP_info.log" + ph7_error: + type: string + example: "/var/log/pihole/PH7_error.log" + misc: + type: object + properties: + nice: + type: integer + example: -10 + delay_startup: + type: integer + example: 0 + addr2line: + type: boolean + example: true + privacylevel: + type: integer + example: 0 + check: + type: object + properties: + load: + type: boolean + example: true + shmem: + type: integer + example: 90 + disk: + type: integer + example: 90 diff --git a/src/api/docs/content/specs/ftl.yaml b/src/api/docs/content/specs/ftl.yaml index 74889362..b2ff9a41 100644 --- a/src/api/docs/content/specs/ftl.yaml +++ b/src/api/docs/content/specs/ftl.yaml @@ -113,69 +113,6 @@ components: application/json: schema: $ref: 'common.yaml#/components/errors/unauthorized' - gateway: - get: - summary: Get info about the gateway of your Pi-hole - tags: - - "FTL information" - operationId: "get_gateway" - description: | - This API hook returns infos about the gateway of your Pi-hole. - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: 'ftl.yaml#/components/schemas/gateway' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: 'common.yaml#/components/errors/unauthorized' - interfaces: - get: - summary: Get info about the interfaces of your Pi-hole - tags: - - "FTL information" - operationId: "get_interfaces" - description: | - This API hook returns infos about the networking interfaces of your Pi-hole. - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: 'ftl.yaml#/components/schemas/interfaces' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: 'common.yaml#/components/errors/unauthorized' - config: - get: - summary: Get info about the config of your Pi-hole - tags: - - "FTL information" - operationId: "get_config" - description: | - This API hook returns infos about the config of your Pi-hole. - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: 'ftl.yaml#/components/schemas/config' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: 'common.yaml#/components/errors/unauthorized' schemas: client: remote_addr: @@ -482,316 +419,6 @@ components: items: type: string example: ["/api/dns/blocking", "/api/dns/cache", "/api/dns/port", "/api/domains", "/api/groups", "/api/lists"] - gateway: - type: object - properties: - address: - type: string - description: Address of the gateway - example: "192.168.0.1" - interface: - type: string - description: Interface of your Pi-hole connected to the gateway - example: "eth0" - interfaces: - type: object - properties: - interfaces: - type: array - description: Interface information - items: - type: object - properties: - name: - type: string - nullable: true - description: Interface name - carrier: - type: boolean - description: If the interface is connected - speed: - type: integer - description: Speed of the interface in Mbit/s (-1 if not applicable) - tx: - type: object - properties: - num: - type: number - description: Number of transmitted data since boot - unit: - type: string - description: Unit of transmitted data since boot - rx: - type: object - properties: - num: - type: number - description: Number of received data since boot - unit: - type: string - description: Unit of received data since boot - ipv4: - type: array - nullable: true - description: Array of associated IPv4 addresses - items: - type: string - ipv6: - type: array - nullable: true - description: Array of associated IPv6 addresses - items: - type: string - example: - - name: "eth0" - default: true - carrier: true - speed: 1000 - tx: - num: 10.4 - unit: "MB" - rx: - num: 8.1 - unit: "MB" - ipv4: ["192.168.0.123"] - ipv6: ["fe80::1234:5678:9abc:def0", "2001:db8::1234:5678:9abc:def0"] - - name: "wlan0" - default: false - carrier: false - speed: -1 - tx: - num: 0 - unit: "B" - rx: - num: 0 - unit: "B" - ipv4: [] - ipv6: [] - - name: "wg0" - default: false - carrier: true - speed: -1 - tx: - num: 170.3 - unit: "kB" - rx: - num: 222.3 - unit: "kB" - ipv4: ["10.1.0.1"] - ipv6: ["fd00:4711::1"] - config: - type: object - properties: - config: - type: object - properties: - debug: - type: integer - example: 0 - dns: - type: object - properties: - CNAMEdeepInspect: - type: boolean - example: true - blockESNI: - type: boolean - example: true - EDN0ECS: - type: boolean - example: true - ignoreLocalhost: - type: boolean - example: false - showDNSSEC: - type: boolean - example: true - analyzeAAAA: - type: boolean - example: true - analyzeOnlyAandAAAA: - type: boolean - example: false - piholePTR: - type: string - example: "PI.HOLE" - replyWhenBusy: - type: string - example: "ALLOW" - blockTTL: - type: integer - example: 2 - blockingmode: - type: string - example: "NULL" - port: - type: integer - example: 53 - specialDomains: - type: object - properties: - mozillaCanary: - type: boolean - example: true - iCloudPrivateRelay: - type: boolean - example: true - reply: - type: object - properties: - host: - type: object - properties: - ipv4: - type: string - example: "" - ipv6: - type: string - example: "" - blocking: - type: object - properties: - ipv4: - type: string - example: "" - ipv6: - type: string - example: "" - rateLimit: - type: object - properties: - count: - type: integer - example: 1000 - interval: - type: integer - example: 60 - resolver: - type: object - properties: - resolveIPv4: - type: boolean - example: true - resolveIPv6: - type: boolean - example: true - networkNames: - type: boolean - example: true - refreshNames: - type: string - example: "IPV4_ONLY" - database: - type: object - properties: - DBimport: - type: boolean - example: true - DBexport: - type: boolean - example: true - maxHistory: - type: integer - example: 86400 - maxDBdays: - type: integer - example: 365 - DBinterval: - type: integer - example: 60 - network: - type: object - properties: - parseARPcache: - type: boolean - example: true - expire: - type: integer - example: 365 - http: - type: object - properties: - localAPIauth: - type: boolean - example: true - prettyJSON: - type: boolean - example: false - sessionTimeout: - type: integer - example: 300 - domain: - type: string - example: "pi.hole" - acl: - type: string - example: "+0.0.0.0/0" - port: - type: string - example: "8080,[::]:8080" - paths: - type: object - properties: - webroot: - type: string - example: "/var/www/html" - webhome: - type: string - example: "/admin/" - files: - type: object - properties: - log: - type: string - example: "/var/log/pihole/FTL.log" - pid: - type: string - example: "/run/pihole-FTL.pid" - database: - type: string - example: "/etc/pihole/pihole-FTL.db" - gravity: - type: string - example: "/etc/pihole/gravity.db" - macvendor: - type: string - example: "/etc/pihole/macvendor.db" - setupVars: - type: string - example: "/etc/pihole/setupVars.conf" - http_info: - type: string - example: "/var/log/pihole/HTTP_info.log" - ph7_error: - type: string - example: "/var/log/pihole/PH7_error.log" - misc: - type: object - properties: - nice: - type: integer - example: -10 - delay_startup: - type: integer - example: 0 - addr2line: - type: boolean - example: true - privacylevel: - type: integer - example: 0 - check: - type: object - properties: - load: - type: boolean - example: true - shmem: - type: integer - example: 90 - disk: - type: integer - example: 90 parameters: logs: diff --git a/src/api/docs/content/specs/main.yaml b/src/api/docs/content/specs/main.yaml index 18b85e4c..90dd8a1f 100644 --- a/src/api/docs/content/specs/main.yaml +++ b/src/api/docs/content/specs/main.yaml @@ -15,7 +15,6 @@ info: Most (but not all) endpoints require authentication. API endpoints requiring authentication will fail with code `401 Unauthorized` when used outside a valid session. servers: - - url: http://pi.hole:8080/api - url: 'http://{url}:{port}/{path}' variables: url: @@ -45,6 +44,8 @@ tags: description: Methods used to manage lists on your Pi-hole - name: "FTL information" description: Methods used to gather advanced data from your Pi-hole + - name: "Network information" + description: Methods used to gather advanced information about your network paths: /auth: @@ -110,14 +111,17 @@ paths: /ftl/endpoints: $ref: 'ftl.yaml#/components/paths/endpoints' - /ftl/gateway: - $ref: 'ftl.yaml#/components/paths/gateway' + /config: + $ref: 'config.yaml#/components/paths/config' - /ftl/interfaces: - $ref: 'ftl.yaml#/components/paths/interfaces' + /network/devices: + $ref: 'network.yaml#/components/paths/devices' - /ftl/config: - $ref: 'ftl.yaml#/components/paths/config' + /network/gateway: + $ref: 'network.yaml#/components/paths/gateway' + + /network/interfaces: + $ref: 'network.yaml#/components/paths/interfaces' /version: $ref: 'version.yaml#/components/paths/version' diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml new file mode 100644 index 00000000..9995e978 --- /dev/null +++ b/src/api/docs/content/specs/network.yaml @@ -0,0 +1,244 @@ + +openapi: 3.0.2 +components: + paths: + gateway: + get: + summary: Get info about the gateway of your Pi-hole + tags: + - "Network information" + operationId: "get_gateway" + description: | + This API hook returns infos about the gateway of your Pi-hole. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'network.yaml#/components/schemas/gateway' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' + interfaces: + get: + summary: Get info about the interfaces of your Pi-hole + tags: + - "Network information" + operationId: "get_interfaces" + description: | + This API hook returns infos about the networking interfaces of your Pi-hole. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'network.yaml#/components/schemas/interfaces' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' + devices: + get: + summary: Get info about the devices in your local network as seen by your Pi-hole + tags: + - "Network information" + operationId: "get_network" + description: | + This API hook returns infos about the devices in your local network as seen by your Pi-hole. By default, this number of shown devices is limited to 10. Devices are ordered by when your Pi-hole has received the last query from this device (most recent first) + parameters: + - $ref: 'network.yaml#/components/parameters/devices/max_devices' + - $ref: 'network.yaml#/components/parameters/devices/max_addresses' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'network.yaml#/components/schemas/devices' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' + schemas: + gateway: + type: object + properties: + address: + type: string + description: Address of the gateway + example: "192.168.0.1" + interface: + type: string + description: Interface of your Pi-hole connected to the gateway + example: "eth0" + interfaces: + type: object + properties: + interfaces: + type: array + description: Interface information + items: + type: object + properties: + name: + type: string + nullable: true + description: Interface name + carrier: + type: boolean + description: If the interface is connected + speed: + type: integer + description: Speed of the interface in Mbit/s (-1 if not applicable) + tx: + type: object + properties: + num: + type: number + description: Number of transmitted data since boot + unit: + type: string + description: Unit of transmitted data since boot + rx: + type: object + properties: + num: + type: number + description: Number of received data since boot + unit: + type: string + description: Unit of received data since boot + ipv4: + type: array + nullable: true + description: Array of associated IPv4 addresses + items: + type: string + ipv6: + type: array + nullable: true + description: Array of associated IPv6 addresses + items: + type: string + example: + - name: "eth0" + default: true + carrier: true + speed: 1000 + tx: + num: 10.4 + unit: "MB" + rx: + num: 8.1 + unit: "MB" + ipv4: ["192.168.0.123"] + ipv6: ["fe80::1234:5678:9abc:def0", "2001:db8::1234:5678:9abc:def0"] + - name: "wlan0" + default: false + carrier: false + speed: -1 + tx: + num: 0 + unit: "B" + rx: + num: 0 + unit: "B" + ipv4: [] + ipv6: [] + - name: "wg0" + default: false + carrier: true + speed: -1 + tx: + num: 170.3 + unit: "kB" + rx: + num: 222.3 + unit: "kB" + ipv4: ["10.1.0.1"] + ipv6: ["fd00:4711::1"] + devices: + type: object + properties: + devices: + type: array + description: Array of devices + items: + type: object + properties: + id: + type: integer + description: Device network table ID + example: 1 + hwaddr: + type: string + description: MAC address of this device + example: 00:11:22:33:44:55 + interface: + type: string + description: Interface this device is connected to + example: enp2s0 + firstSeen: + type: interger + description: Unix timestamp when this device was first seen by your Pi-hole + example: 1664623620 + lastQuery: + type: interger + description: Unix timestamp when your Pi-hole received the last query from this device + example: 1664688620 + numQueries: + type: integer + description: Total number of queries your Pi-hole has received from this device + example: 585462 + macVendor: + type: string + description: Vendor name associated with the device's MAC address + example: "Digital Data Communications Asia Co.,Ltd" + ips: + type: array + items: + type: object + properties: + ip: + type: string + description: Associated IP address (can be IPv4 or IPv6) + example: "192.168.1.51" + name: + type: string + description: Associated hostname (can be null) + example: ubuntu-server + lastSeen: + description: Unix timestamp when your Pi-hole has seen this address the last time + example: 1664688620 + nameUpdated: + description: Unix timestamp when device updated its hostname the last time + example: 1664688620 + + + parameters: + devices: + max_devices: + in: query + description: (Optional) Maximum number of devices to show + name: max_devices + schema: + type: integer + required: false + example: 10 + max_addresses: + in: query + description: (Optional) Maximum number of addresses to show per device + name: max_addresses + schema: + type: integer + required: false + example: 3 \ No newline at end of file diff --git a/src/api/ftl.c b/src/api/ftl.c index 9c228abc..cd4987fb 100644 --- a/src/api/ftl.c +++ b/src/api/ftl.c @@ -32,10 +32,6 @@ #include "../config/config.h" // struct clientsData #include "../datastructure.h" -// Routing information and flags -#include -// Interate through directories -#include int api_ftl_client(struct ftl_conn *api) { @@ -70,9 +66,7 @@ int api_ftl_logs_dns(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } unsigned int start = 0u; if(api->request->query_string != NULL) @@ -134,9 +128,7 @@ int api_ftl_dbinfo(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } cJSON *json = JSON_NEW_OBJECT(); @@ -417,9 +409,7 @@ int get_ftl_obj(struct ftl_conn *api, cJSON *ftl, const bool is_locked) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } cJSON *database = JSON_NEW_OBJECT(); @@ -475,9 +465,7 @@ int api_ftl_sysinfo(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } cJSON *json = JSON_NEW_OBJECT(); @@ -498,300 +486,3 @@ int api_ftl_sysinfo(struct ftl_conn *api) JSON_ADD_ITEM_TO_OBJECT(json, "ftl", ftl); JSON_SEND_OBJECT(json); } - -static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw) -{ - // Get IPv4 default route gateway and associated interface - long dest_r = 0, gw_r = 0; - int flags = 0, metric = 0, minmetric = __INT_MAX__; - char iface_r[IF_NAMESIZE] = { 0 }; - char buf[1024] = { 0 }; - - FILE *file; - if((file = fopen("/proc/net/route", "r"))) - { - // Parse /proc/net/route - the kernel's IPv4 routing table - while(fgets(buf, sizeof(buf), file)) - { - if(sscanf(buf, "%s %lx %lx %x %*i %*i %i", iface_r, &dest_r, &gw_r, &flags, &metric) != 5) - continue; - - // Only anaylze routes which are UP and whose - // destinations are a gateway - if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY)) - continue; - - // Only analyze "catch all" routes (destination 0.0.0.0) - if(dest_r != 0) - continue; - - // Store default gateway, overwrite if we find a route with - // a lower metric - if(metric < minmetric) - { - minmetric = metric; - *gw = gw_r; - strcpy(iface, iface_r); - - log_debug(DEBUG_API, "Reading interfaces: flags: %i, addr: %s, iface: %s, metric: %i, minmetric: %i", - flags, inet_ntoa(*(struct in_addr *) gw), iface, metric, minmetric); - } - } - fclose(file); - } - else - log_err("Cannot read /proc/net/route: %s", strerror(errno)); - - // Return success based on having found the default gateway's address - return gw != 0; -} - -int api_ftl_gateway(struct ftl_conn *api) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { - return send_json_unauthorized(api); - } - - in_addr_t gw = 0; - char iface[IF_NAMESIZE] = { 0 }; - - // Get default interface - getDefaultInterface(iface, &gw); - - // Generate JSON response - cJSON *json = JSON_NEW_OBJECT(); - const char *gwaddr = inet_ntoa(*(struct in_addr *) &gw); - JSON_COPY_STR_TO_OBJECT(json, "address", gwaddr); - JSON_REF_STR_IN_OBJECT(json, "interface", iface); - JSON_SEND_OBJECT(json); -} - -int api_ftl_interfaces(struct ftl_conn *api) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { - return send_json_unauthorized(api); - } - - cJSON *json = JSON_NEW_OBJECT(); - - // Get interface with default route - in_addr_t gw = 0; - char default_iface[IF_NAMESIZE] = { 0 }; - getDefaultInterface(default_iface, &gw); - - // Enumerate and list interfaces - // Loop over interfaces and extract information - DIR *dfd; - FILE *f; - struct dirent *dp; - size_t tx_sum = 0, rx_sum = 0; - char fname[64 + IF_NAMESIZE] = { 0 }; - char readbuffer[1024] = { 0 }; - - // Open /sys/class/net directory - if ((dfd = opendir("/sys/class/net")) == NULL) - { - log_err("API: Cannot access /sys/class/net"); - return 500; - } - - // Get IP addresses of all interfaces on this machine - struct ifaddrs *ifap = NULL; - if(getifaddrs(&ifap) == -1) - log_err("API: Cannot get interface addresses: %s", strerror(errno)); - - cJSON *interfaces = JSON_NEW_ARRAY(); - // Walk /sys/class/net directory - while ((dp = readdir(dfd)) != NULL) - { - // Skip "." and ".." - if(!dp->d_name || strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) - continue; - - // Create new interface record - cJSON *iface = JSON_NEW_OBJECT(); - - // Extract interface name - const char *iface_name = dp->d_name; - JSON_COPY_STR_TO_OBJECT(iface, "name", iface_name); - - // Is this the default interface? - const bool is_default_iface = strcmp(iface_name, default_iface) == 0; - JSON_ADD_BOOL_TO_OBJECT(iface, "default", is_default_iface); - - // Extract carrier status - bool carrier = false; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/carrier", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fgets(readbuffer, sizeof(readbuffer)-1, f) != NULL) - carrier = readbuffer[0] == '1'; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - JSON_ADD_BOOL_TO_OBJECT(iface, "carrier", carrier); - - // Extract link speed (may not be possible, e.g., for WiFi devices with dynamic link speeds) - int speed = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/speed", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%i", &(speed)) != 1) - speed = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - JSON_ADD_NUMBER_TO_OBJECT(iface, "speed", speed); - - // Get total transmitted bytes - ssize_t tx_bytes = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/tx_bytes", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%zi", &(tx_bytes)) != 1) - tx_bytes = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - - // Format transmitted bytes - double tx = 0.0; - char tx_unit[3] = { 0 }; - format_memory_size(tx_unit, tx_bytes, &tx); - if(tx_unit[0] != '\0') - tx_unit[1] = 'B'; - - // Add transmitted bytes to interface record - cJSON *tx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); - JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); - JSON_ADD_ITEM_TO_OBJECT(iface, "tx", tx_json); - - // Get total received bytes - ssize_t rx_bytes = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/rx_bytes", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%zi", &(rx_bytes)) != 1) - rx_bytes = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - - // Format received bytes - double rx = 0.0; - char rx_unit[3] = { 0 }; - format_memory_size(rx_unit, rx_bytes, &rx); - if(rx_unit[0] != '\0') - rx_unit[1] = 'B'; - - // Add received bytes to JSON object - cJSON *rx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); - JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); - JSON_ADD_ITEM_TO_OBJECT(iface, "rx", rx_json); - - // Get IP address(es) of this interface - if(ifap) - { - // Walk through linked list of interface addresses - cJSON *ipv4 = JSON_NEW_ARRAY(); - cJSON *ipv6 = JSON_NEW_ARRAY(); - for(struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) - { - // Skip interfaces without an address and those - // not matching the current interface - if(ifa->ifa_addr == NULL || strcmp(ifa->ifa_name, iface_name) != 0) - continue; - - // If we reach this point, we found the correct interface - const sa_family_t family = ifa->ifa_addr->sa_family; - char host[NI_MAXHOST] = { 0 }; - if(family == AF_INET || family == AF_INET6) - { - // Get IP address - const int s = getnameinfo(ifa->ifa_addr, - (family == AF_INET) ? - sizeof(struct sockaddr_in) : - sizeof(struct sockaddr_in6), - host, NI_MAXHOST, - NULL, 0, NI_NUMERICHOST); - if (s != 0) - { - log_warn("API: getnameinfo() failed: %s\n", gai_strerror(s)); - continue; - } - - if(family == AF_INET) - { - JSON_COPY_STR_TO_ARRAY(ipv4, host); - } - else if(family == AF_INET6) - { - JSON_COPY_STR_TO_ARRAY(ipv6, host); - } - } - } - JSON_ADD_ITEM_TO_OBJECT(iface, "ipv4", ipv4); - JSON_ADD_ITEM_TO_OBJECT(iface, "ipv6", ipv6); - } - - // Sum up transmitted and received bytes - tx_sum += tx_bytes; - rx_sum += rx_bytes; - - // Add interface to array - JSON_ADD_ITEM_TO_ARRAY(interfaces, iface); - } - - freeifaddrs(ifap); - - cJSON *sum = JSON_NEW_OBJECT(); - JSON_COPY_STR_TO_OBJECT(sum, "name", "sum"); - JSON_ADD_BOOL_TO_OBJECT(sum, "carrier", true); - JSON_ADD_NUMBER_TO_OBJECT(sum, "speed", 0); - - // Format transmitted bytes - double tx = 0.0; - char tx_unit[3] = { 0 }; - format_memory_size(tx_unit, tx_sum, &tx); - if(tx_unit[0] != '\0') - tx_unit[1] = 'B'; - - // Add transmitted bytes to interface record - cJSON *tx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); - JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); - JSON_ADD_ITEM_TO_OBJECT(sum, "tx", tx_json); - - // Format received bytes - double rx = 0.0; - char rx_unit[3] = { 0 }; - format_memory_size(rx_unit, rx_sum, &rx); - if(rx_unit[0] != '\0') - rx_unit[1] = 'B'; - - // Add received bytes to JSON object - cJSON *rx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); - JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); - JSON_ADD_ITEM_TO_OBJECT(sum, "rx", rx_json); - - cJSON *ipv4 = JSON_NEW_ARRAY(); - cJSON *ipv6 = JSON_NEW_ARRAY(); - JSON_ADD_ITEM_TO_OBJECT(sum, "ipv4", ipv4); - JSON_ADD_ITEM_TO_OBJECT(sum, "ipv6", ipv6); - - // Add interface to array - JSON_ADD_ITEM_TO_ARRAY(interfaces, sum); - JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces); - JSON_SEND_OBJECT(json); -} diff --git a/src/api/ftl/network.c b/src/api/ftl/network.c deleted file mode 100644 index e92263e6..00000000 --- a/src/api/ftl/network.c +++ /dev/null @@ -1,115 +0,0 @@ -/* Pi-hole: A black hole for Internet advertisements -* (c) 2019 Pi-hole, LLC (https://pi-hole.net) -* Network-wide ad blocking via your own hardware. -* -* FTL Engine -* API Implementation /api/network -* -* This file is copyright under the latest version of the EUPL. -* Please see LICENSE file for your rights under this license. */ - -#include "FTL.h" -#include "webserver/http-common.h" -#include "webserver/json_macros.h" -#include "api/api.h" -// networkrecord -#include "database/network-table.h" -// dbopen() -#include "database/common.h" - -int api_ftl_network(struct ftl_conn *api) -{ - // Verify requesting client is allowed to see this ressource - if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { - return send_json_unauthorized(api); - } - - // Open pihole-FTL.db database file - sqlite3_stmt *device_stmt = NULL, *ip_stmt = NULL; - sqlite3 *db = dbopen(false); - if(db == NULL) - { - log_warn("Failed to open database in networkTable_readDevices()"); - return false; - } - - const char *sql_msg = NULL; - if(!networkTable_readDevices(db, &device_stmt, &sql_msg)) - { - // Add SQL message (may be NULL = not available) - return send_json_error(api, 500, - "database_error", - "Could not read network details from database table", - sql_msg); - } - - // Read record for a single device - cJSON *json = JSON_NEW_ARRAY(); - network_record network; - while(networkTable_readDevicesGetRecord(device_stmt, &network, &sql_msg)) - { - cJSON *item = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(item, "id", network.id); - JSON_COPY_STR_TO_OBJECT(item, "hwaddr", network.hwaddr); - JSON_COPY_STR_TO_OBJECT(item, "interface", network.iface); - JSON_ADD_NUMBER_TO_OBJECT(item, "firstSeen", network.firstSeen); - JSON_ADD_NUMBER_TO_OBJECT(item, "lastQuery", network.lastQuery); - JSON_ADD_NUMBER_TO_OBJECT(item, "numQueries", network.numQueries); - JSON_COPY_STR_TO_OBJECT(item, "macVendor", network.macVendor); - - // Build array of all IP addresses known associated to this client - cJSON *ips = JSON_NEW_ARRAY(); - if(networkTable_readIPs(db, &ip_stmt, network.id, &sql_msg)) - { - // Walk known IP addresses + names - network_addresses_record network_address; - while(networkTable_readIPsGetRecord(ip_stmt, &network_address, &sql_msg)) - { - cJSON *ip = JSON_NEW_OBJECT(); - JSON_COPY_STR_TO_OBJECT(ip, "ip", network_address.ip); - JSON_COPY_STR_TO_OBJECT(ip, "name", network_address.name); - JSON_ADD_NUMBER_TO_OBJECT(ip, "lastSeen", network_address.lastSeen); - JSON_ADD_NUMBER_TO_OBJECT(ip, "nameUpdated", network_address.nameUpdated); - JSON_ADD_ITEM_TO_ARRAY(ips, ip); - } - - // Possible error handling - if(sql_msg != NULL) - { - cJSON_Delete(ips); - cJSON_Delete(json); - return send_json_error(api, 500, - "database_error", - "Could not read network details from database table (getting IP records)", - sql_msg); - } - - // Finalize sub-query - networkTable_readIPsFinalize(ip_stmt); - } - - // Add array of IP addresses to device - JSON_ADD_ITEM_TO_OBJECT(item, "ips", ips); - - // Add device to array of all devices - JSON_ADD_ITEM_TO_ARRAY(json, item); - } - - if(sql_msg != NULL) - { - cJSON_Delete(json); - return send_json_error(api, 500, - "database_error", - "Could not read network details from database table (step)", - sql_msg); - } - - // Finalize query - networkTable_readDevicesFinalize(device_stmt); - - dbclose(&db); - - // Return data to user - JSON_SEND_OBJECT(json); -} diff --git a/src/api/history.c b/src/api/history.c index a210fecd..68524a97 100644 --- a/src/api/history.c +++ b/src/api/history.c @@ -88,9 +88,7 @@ int api_history_clients(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Find minimum ID to send for(int slot = 0; slot < OVERTIME_SLOTS; slot++) diff --git a/src/api/list.c b/src/api/list.c index f47949a6..68355dae 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -353,9 +353,7 @@ int api_list(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource char payload[MAX_PAYLOAD_BYTES] = { 0 }; if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } enum gravity_list_type listtype; bool can_modify = false; diff --git a/src/api/network.c b/src/api/network.c new file mode 100644 index 00000000..f90daa94 --- /dev/null +++ b/src/api/network.c @@ -0,0 +1,426 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2019 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* API Implementation /api/network +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "webserver/http-common.h" +#include "webserver/json_macros.h" +#include "api/api.h" +// Routing information and flags +#include +// Interate through directories +#include +// networkrecord +#include "database/network-table.h" +// dbopen() +#include "database/common.h" + +static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw) +{ + // Get IPv4 default route gateway and associated interface + long dest_r = 0, gw_r = 0; + int flags = 0, metric = 0, minmetric = __INT_MAX__; + char iface_r[IF_NAMESIZE] = { 0 }; + char buf[1024] = { 0 }; + + FILE *file; + if((file = fopen("/proc/net/route", "r"))) + { + // Parse /proc/net/route - the kernel's IPv4 routing table + while(fgets(buf, sizeof(buf), file)) + { + if(sscanf(buf, "%s %lx %lx %x %*i %*i %i", iface_r, &dest_r, &gw_r, &flags, &metric) != 5) + continue; + + // Only anaylze routes which are UP and whose + // destinations are a gateway + if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY)) + continue; + + // Only analyze "catch all" routes (destination 0.0.0.0) + if(dest_r != 0) + continue; + + // Store default gateway, overwrite if we find a route with + // a lower metric + if(metric < minmetric) + { + minmetric = metric; + *gw = gw_r; + strcpy(iface, iface_r); + + log_debug(DEBUG_API, "Reading interfaces: flags: %i, addr: %s, iface: %s, metric: %i, minmetric: %i", + flags, inet_ntoa(*(struct in_addr *) gw), iface, metric, minmetric); + } + } + fclose(file); + } + else + log_err("Cannot read /proc/net/route: %s", strerror(errno)); + + // Return success based on having found the default gateway's address + return gw != 0; +} + +int api_network_gateway(struct ftl_conn *api) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) + return send_json_unauthorized(api); + + in_addr_t gw = 0; + char iface[IF_NAMESIZE] = { 0 }; + + // Get default interface + getDefaultInterface(iface, &gw); + + // Generate JSON response + cJSON *json = JSON_NEW_OBJECT(); + const char *gwaddr = inet_ntoa(*(struct in_addr *) &gw); + JSON_COPY_STR_TO_OBJECT(json, "address", gwaddr); + JSON_REF_STR_IN_OBJECT(json, "interface", iface); + JSON_SEND_OBJECT(json); +} + +int api_network_interfaces(struct ftl_conn *api) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) + return send_json_unauthorized(api); + + cJSON *json = JSON_NEW_OBJECT(); + + // Get interface with default route + in_addr_t gw = 0; + char default_iface[IF_NAMESIZE] = { 0 }; + getDefaultInterface(default_iface, &gw); + + // Enumerate and list interfaces + // Loop over interfaces and extract information + DIR *dfd; + FILE *f; + struct dirent *dp; + size_t tx_sum = 0, rx_sum = 0; + char fname[64 + IF_NAMESIZE] = { 0 }; + char readbuffer[1024] = { 0 }; + + // Open /sys/class/net directory + if ((dfd = opendir("/sys/class/net")) == NULL) + { + log_err("API: Cannot access /sys/class/net"); + return 500; + } + + // Get IP addresses of all interfaces on this machine + struct ifaddrs *ifap = NULL; + if(getifaddrs(&ifap) == -1) + log_err("API: Cannot get interface addresses: %s", strerror(errno)); + + cJSON *interfaces = JSON_NEW_ARRAY(); + // Walk /sys/class/net directory + while ((dp = readdir(dfd)) != NULL) + { + // Skip "." and ".." + if(!dp->d_name || strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + continue; + + // Create new interface record + cJSON *iface = JSON_NEW_OBJECT(); + + // Extract interface name + const char *iface_name = dp->d_name; + JSON_COPY_STR_TO_OBJECT(iface, "name", iface_name); + + // Is this the default interface? + const bool is_default_iface = strcmp(iface_name, default_iface) == 0; + JSON_ADD_BOOL_TO_OBJECT(iface, "default", is_default_iface); + + // Extract carrier status + bool carrier = false; + snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/carrier", iface_name); + if((f = fopen(fname, "r")) != NULL) + { + if(fgets(readbuffer, sizeof(readbuffer)-1, f) != NULL) + carrier = readbuffer[0] == '1'; + fclose(f); + } + else + log_err("Cannot read %s: %s", fname, strerror(errno)); + JSON_ADD_BOOL_TO_OBJECT(iface, "carrier", carrier); + + // Extract link speed (may not be possible, e.g., for WiFi devices with dynamic link speeds) + int speed = -1; + snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/speed", iface_name); + if((f = fopen(fname, "r")) != NULL) + { + if(fscanf(f, "%i", &(speed)) != 1) + speed = -1; + fclose(f); + } + else + log_err("Cannot read %s: %s", fname, strerror(errno)); + JSON_ADD_NUMBER_TO_OBJECT(iface, "speed", speed); + + // Get total transmitted bytes + ssize_t tx_bytes = -1; + snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/tx_bytes", iface_name); + if((f = fopen(fname, "r")) != NULL) + { + if(fscanf(f, "%zi", &(tx_bytes)) != 1) + tx_bytes = -1; + fclose(f); + } + else + log_err("Cannot read %s: %s", fname, strerror(errno)); + + // Format transmitted bytes + double tx = 0.0; + char tx_unit[3] = { 0 }; + format_memory_size(tx_unit, tx_bytes, &tx); + if(tx_unit[0] != '\0') + tx_unit[1] = 'B'; + + // Add transmitted bytes to interface record + cJSON *tx_json = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); + JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); + JSON_ADD_ITEM_TO_OBJECT(iface, "tx", tx_json); + + // Get total received bytes + ssize_t rx_bytes = -1; + snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/rx_bytes", iface_name); + if((f = fopen(fname, "r")) != NULL) + { + if(fscanf(f, "%zi", &(rx_bytes)) != 1) + rx_bytes = -1; + fclose(f); + } + else + log_err("Cannot read %s: %s", fname, strerror(errno)); + + // Format received bytes + double rx = 0.0; + char rx_unit[3] = { 0 }; + format_memory_size(rx_unit, rx_bytes, &rx); + if(rx_unit[0] != '\0') + rx_unit[1] = 'B'; + + // Add received bytes to JSON object + cJSON *rx_json = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); + JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); + JSON_ADD_ITEM_TO_OBJECT(iface, "rx", rx_json); + + // Get IP address(es) of this interface + if(ifap) + { + // Walk through linked list of interface addresses + cJSON *ipv4 = JSON_NEW_ARRAY(); + cJSON *ipv6 = JSON_NEW_ARRAY(); + for(struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) + { + // Skip interfaces without an address and those + // not matching the current interface + if(ifa->ifa_addr == NULL || strcmp(ifa->ifa_name, iface_name) != 0) + continue; + + // If we reach this point, we found the correct interface + const sa_family_t family = ifa->ifa_addr->sa_family; + char host[NI_MAXHOST] = { 0 }; + if(family == AF_INET || family == AF_INET6) + { + // Get IP address + const int s = getnameinfo(ifa->ifa_addr, + (family == AF_INET) ? + sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), + host, NI_MAXHOST, + NULL, 0, NI_NUMERICHOST); + if (s != 0) + { + log_warn("API: getnameinfo() failed: %s\n", gai_strerror(s)); + continue; + } + + if(family == AF_INET) + { + JSON_COPY_STR_TO_ARRAY(ipv4, host); + } + else if(family == AF_INET6) + { + JSON_COPY_STR_TO_ARRAY(ipv6, host); + } + } + } + JSON_ADD_ITEM_TO_OBJECT(iface, "ipv4", ipv4); + JSON_ADD_ITEM_TO_OBJECT(iface, "ipv6", ipv6); + } + + // Sum up transmitted and received bytes + tx_sum += tx_bytes; + rx_sum += rx_bytes; + + // Add interface to array + JSON_ADD_ITEM_TO_ARRAY(interfaces, iface); + } + + freeifaddrs(ifap); + + cJSON *sum = JSON_NEW_OBJECT(); + JSON_COPY_STR_TO_OBJECT(sum, "name", "sum"); + JSON_ADD_BOOL_TO_OBJECT(sum, "carrier", true); + JSON_ADD_NUMBER_TO_OBJECT(sum, "speed", 0); + + // Format transmitted bytes + double tx = 0.0; + char tx_unit[3] = { 0 }; + format_memory_size(tx_unit, tx_sum, &tx); + if(tx_unit[0] != '\0') + tx_unit[1] = 'B'; + + // Add transmitted bytes to interface record + cJSON *tx_json = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); + JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); + JSON_ADD_ITEM_TO_OBJECT(sum, "tx", tx_json); + + // Format received bytes + double rx = 0.0; + char rx_unit[3] = { 0 }; + format_memory_size(rx_unit, rx_sum, &rx); + if(rx_unit[0] != '\0') + rx_unit[1] = 'B'; + + // Add received bytes to JSON object + cJSON *rx_json = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); + JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); + JSON_ADD_ITEM_TO_OBJECT(sum, "rx", rx_json); + + cJSON *ipv4 = JSON_NEW_ARRAY(); + cJSON *ipv6 = JSON_NEW_ARRAY(); + JSON_ADD_ITEM_TO_OBJECT(sum, "ipv4", ipv4); + JSON_ADD_ITEM_TO_OBJECT(sum, "ipv6", ipv6); + + // Add interface to array + JSON_ADD_ITEM_TO_ARRAY(interfaces, sum); + JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces); + JSON_SEND_OBJECT(json); +} + +int api_network_devices(struct ftl_conn *api) +{ + // Verify requesting client is allowed to see this ressource + if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) + return send_json_unauthorized(api); + + // Does the user request a custom number of devices to be included? + unsigned int device_count = 10; + get_uint_var(api->request->query_string, "device_count", &device_count); + + // Does the user request a custom number of addresses per device to be included? + unsigned int address_count = 3; + get_uint_var(api->request->query_string, "address_count", &address_count); + + // Open pihole-FTL.db database file + sqlite3_stmt *device_stmt = NULL, *ip_stmt = NULL; + sqlite3 *db = dbopen(false); + if(db == NULL) + { + log_warn("Failed to open database in networkTable_readDevices()"); + return false; + } + + const char *sql_msg = NULL; + if(!networkTable_readDevices(db, &device_stmt, &sql_msg)) + { + // Add SQL message (may be NULL = not available) + return send_json_error(api, 500, + "database_error", + "Could not read network details from database table", + sql_msg); + } + + // Read record for a single device + cJSON *devices = JSON_NEW_ARRAY(); + network_record network; + unsigned int device_counter = 1; + while(networkTable_readDevicesGetRecord(device_stmt, &network, &sql_msg)) + { + cJSON *item = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(item, "id", network.id); + JSON_COPY_STR_TO_OBJECT(item, "hwaddr", network.hwaddr); + JSON_COPY_STR_TO_OBJECT(item, "interface", network.iface); + JSON_ADD_NUMBER_TO_OBJECT(item, "firstSeen", network.firstSeen); + JSON_ADD_NUMBER_TO_OBJECT(item, "lastQuery", network.lastQuery); + JSON_ADD_NUMBER_TO_OBJECT(item, "numQueries", network.numQueries); + JSON_COPY_STR_TO_OBJECT(item, "macVendor", network.macVendor); + + // Build array of all IP addresses known associated to this client + cJSON *ips = JSON_NEW_ARRAY(); + if(networkTable_readIPs(db, &ip_stmt, network.id, &sql_msg)) + { + // Walk known IP addresses + names + network_addresses_record network_address; + unsigned int address_counter = 1; + while(networkTable_readIPsGetRecord(ip_stmt, &network_address, &sql_msg)) + { + cJSON *ip = JSON_NEW_OBJECT(); + JSON_COPY_STR_TO_OBJECT(ip, "ip", network_address.ip); + JSON_COPY_STR_TO_OBJECT(ip, "name", network_address.name); + JSON_ADD_NUMBER_TO_OBJECT(ip, "lastSeen", network_address.lastSeen); + JSON_ADD_NUMBER_TO_OBJECT(ip, "nameUpdated", network_address.nameUpdated); + JSON_ADD_ITEM_TO_ARRAY(ips, ip); + if(++address_counter > address_count) + break; + } + + // Possible error handling + if(sql_msg != NULL) + { + cJSON_Delete(ips); + cJSON_Delete(devices); + return send_json_error(api, 500, + "database_error", + "Could not read network details from database table (getting IP records)", + sql_msg); + } + + // Finalize sub-query + networkTable_readIPsFinalize(ip_stmt); + + if(++device_counter > device_count) + break; + } + + // Add array of IP addresses to device + JSON_ADD_ITEM_TO_OBJECT(item, "ips", ips); + + // Add device to array of all devices + JSON_ADD_ITEM_TO_ARRAY(devices, item); + } + + if(sql_msg != NULL) + { + cJSON_Delete(devices); + return send_json_error(api, 500, + "database_error", + "Could not read network details from database table (step)", + sql_msg); + } + + // Finalize query + networkTable_readDevicesFinalize(device_stmt); + dbclose(&db); + + // Return data to user + cJSON *json = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(json, "devices", devices); + JSON_SEND_OBJECT(json); +} diff --git a/src/api/queries.c b/src/api/queries.c index 71ac2ae5..aa6931ca 100644 --- a/src/api/queries.c +++ b/src/api/queries.c @@ -71,9 +71,7 @@ int api_queries_suggestions(struct ftl_conn *api) int rc; // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Lock shared memory lock_shm(); @@ -206,9 +204,7 @@ int api_queries(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Lock shared memory lock_shm(); diff --git a/src/api/stats.c b/src/api/stats.c index dcf8fe53..02c1b917 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -142,9 +142,7 @@ int api_stats_top_domains(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Exit before processing any data if requested via config setting if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS) @@ -300,9 +298,7 @@ int api_stats_top_clients(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Exit before processing any data if requested via config setting if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS_CLIENTS) @@ -429,9 +425,7 @@ int api_stats_upstreams(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Lock shared memory lock_shm(); @@ -539,9 +533,7 @@ int api_stats_query_types(struct ftl_conn *api) { // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } lock_shm(); @@ -566,9 +558,7 @@ int api_stats_recentblocked(struct ftl_conn *api) // Verify requesting client is allowed to see this ressource if(check_client_auth(api) == API_AUTH_UNAUTHORIZED) - { return send_json_unauthorized(api); - } // Exit before processing any data if requested via config setting if(config.misc.privacylevel >= PRIVACY_HIDE_DOMAINS) diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index 39101a4f..d221c43d 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -103,6 +103,8 @@ int send_http_internal_error(struct ftl_conn *api) bool get_bool_var(const char *source, const char *var, bool *boolean) { char buffer[16] = { 0 }; + if(!source) + return false; if(GET_VAR(var, buffer, source) > 0) { *boolean = (strcasecmp(buffer, "true") == 0); @@ -204,6 +206,8 @@ bool get_int_var_msg(const char *source, const char *var, int *num, const char * bool get_int_var(const char *source, const char *var, int *num) { const char *msg = NULL; + if(!source) + return false; return get_int_var_msg(source, var, num, &msg); } @@ -232,12 +236,16 @@ bool get_uint_var_msg(const char *source, const char *var, unsigned int *num, co bool get_uint_var(const char *source, const char *var, unsigned int *num) { const char *msg = NULL; + if(!source) + return false; return get_uint_var_msg(source, var, num, &msg); } bool get_double_var_msg(const char *source, const char *var, double *num, const char **msg) { char buffer[128] = { 0 }; + if(!source) + return false; if(GET_VAR(var, buffer, source) < 1) { // Parameter not found @@ -271,6 +279,8 @@ bool get_double_var_msg(const char *source, const char *var, double *num, const bool get_double_var(const char *source, const char *var, double *num) { const char *msg = NULL; + if(!source) + return false; return get_double_var_msg(source, var, num, &msg); } diff --git a/test/api/checkAPI.py b/test/api/checkAPI.py index 6fb5006e..4339e967 100644 --- a/test/api/checkAPI.py +++ b/test/api/checkAPI.py @@ -40,9 +40,9 @@ except Exception as e: exit(1) # Resolve a reference -def resolveSingleReference(ref: str, k: str): +def resolveSingleReference(ref_str: str, k: str): # Read and parse the referenced file - ref = ref.partition("#") + ref = ref_str.partition("#") if len(ref[0]) == 0: # Empty references are not allowed raise Exception("Empty reference, always specify a file in the API specification") @@ -57,12 +57,12 @@ def resolveSingleReference(ref: str, k: str): # Reduce to what we want to import for x in ref[2].split("/"): if len(x) > 0: + #if x not in refYML: refYML = refYML[x] return refYML except Exception as e: - print("Exception when reading " + file + ": " + e) - print("Tried to read" + ref + " in:\n" + json.dumps(refYML, indent=2)) - print("Tried to resolve " + k + " pointing to " + ref) + print("Exception when reading " + file + ": " + str(e)) + print("Tried to resolve " + ref_str + " in:\n" + json.dumps(refYML, indent=2)) exit(1) # Recursively resolve references, this can take a few seconds