diff --git a/src/api/auth.c b/src/api/auth.c index d85811c9..4fd0015d 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -336,14 +336,18 @@ static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_ return 0; } -static void delete_session(const int user_id) +static bool delete_session(const int user_id) { // Skip if nothing to be done here if(user_id < 0 || user_id >= max_sessions) - return; + return false; + + const bool was_valid = auth_data[user_id].used; // Zero out this session (also sets valid to false == 0) memset(&auth_data[user_id], 0, sizeof(auth_data[user_id])); + + return was_valid; } void delete_all_sessions(void) @@ -354,24 +358,6 @@ void delete_all_sessions(void) static int send_api_auth_status(struct ftl_conn *api, const int user_id, const time_t now) { - if(user_id == API_AUTH_LOCALHOST) - { - log_debug(DEBUG_API, "API Auth status: OK (localhost does not need auth)"); - - cJSON *json = JSON_NEW_OBJECT(); - get_session_object(api, json, user_id, now); - JSON_SEND_OBJECT(json); - } - - if(user_id == API_AUTH_EMPTYPASS) - { - log_debug(DEBUG_API, "API Auth status: OK (empty password)"); - - cJSON *json = JSON_NEW_OBJECT(); - get_session_object(api, json, user_id, now); - JSON_SEND_OBJECT(json); - } - if(user_id > API_AUTH_UNAUTHORIZED && (api->method == HTTP_GET || api->method == HTTP_POST)) { log_debug(DEBUG_API, "API Auth status: OK"); @@ -388,17 +374,45 @@ static int send_api_auth_status(struct ftl_conn *api, const int user_id, const t get_session_object(api, json, user_id, now); JSON_SEND_OBJECT(json); } - else if(user_id > API_AUTH_UNAUTHORIZED && api->method == HTTP_DELETE) + else if(api->method == HTTP_DELETE) { - log_debug(DEBUG_API, "API Auth status: Logout, asking to delete cookie"); + if(user_id > API_AUTH_UNAUTHORIZED) + { + log_debug(DEBUG_API, "API Auth status: Logout, asking to delete cookie"); - // Revoke client authentication. This slot can be used by a new client afterwards. - delete_session(user_id); + strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); + + // Revoke client authentication. This slot can be used by a new client afterwards. + const int code = delete_session(user_id) ? 204 : 404; + + // Send empty reply with appropriate HTTP status code + send_http_code(api, "application/json; charset=utf-8", code, ""); + return code; + } + else + { + log_debug(DEBUG_API, "API Auth status: Logout, but not authenticated"); + + cJSON *json = JSON_NEW_OBJECT(); + get_session_object(api, json, user_id, now); + JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized + } + } + else if(user_id == API_AUTH_LOCALHOST) + { + log_debug(DEBUG_API, "API Auth status: OK (localhost does not need auth)"); - strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers)); cJSON *json = JSON_NEW_OBJECT(); get_session_object(api, json, user_id, now); - JSON_SEND_OBJECT_CODE(json, 410); // 410 Gone + JSON_SEND_OBJECT(json); + } + else if(user_id == API_AUTH_EMPTYPASS) + { + log_debug(DEBUG_API, "API Auth status: OK (empty password)"); + + cJSON *json = JSON_NEW_OBJECT(); + get_session_object(api, json, user_id, now); + JSON_SEND_OBJECT(json); } else { @@ -563,7 +577,7 @@ int api_auth(struct ftl_conn *api) { // Expired slow, mark as unused if(auth_data[i].used && - auth_data[i].valid_until < now) + auth_data[i].valid_until < now) { log_debug(DEBUG_API, "API: Session of client %u (%s) expired, freeing...", i, auth_data[i].remote_addr); @@ -634,6 +648,11 @@ int api_auth(struct ftl_conn *api) "Rate-limiting login attempts", NULL); } + else if(result == NO_PASSWORD_SET) + { + // No password set + log_debug(DEBUG_API, "API: Trying to auth with password but none set: '%s'", password); + } else { log_debug(DEBUG_API, "API: Password incorrect: '%s'", password); @@ -667,9 +686,9 @@ int api_auth_session_delete(struct ftl_conn *api) return send_json_error(api, 400, "bad_request", "Session ID not in use", NULL); // Delete session - delete_session(uid); + const int code = delete_session(uid) ? 204 : 404; - // Send empty reply with code 204 No Content - send_http_code(api, "application/json; charset=utf-8", 204, ""); - return 204; + // Send empty reply with appropriate HTTP status code + send_http_code(api, "application/json; charset=utf-8", code, ""); + return code; } diff --git a/src/api/config.c b/src/api/config.c index ac01cfb2..ade2896f 100644 --- a/src/api/config.c +++ b/src/api/config.c @@ -294,7 +294,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct } if(!set_and_check_password(conf_item, elem->valuestring)) - return "Failed to create password hash (verification failed), password remains unchanged"; + return "password hash verification failed"; break; } @@ -904,7 +904,7 @@ static int api_config_put_delete(struct ftl_conn *api) key, true); } - // Check if this entry does already exist in the array + // Check if this entry exists in the array int idx = 0; for(; idx < cJSON_GetArraySize(new_item->v.json); idx++) { @@ -938,13 +938,12 @@ static int api_config_put_delete(struct ftl_conn *api) if(found) { // Remove item from array + found = true; cJSON_DeleteItemFromArray(new_item->v.json, idx); } else { // Item not found - message = "Item not found"; - hint = "Can only delete existing items"; break; } } @@ -964,13 +963,16 @@ static int api_config_put_delete(struct ftl_conn *api) // Release allocated memory free_config_path(requested_path); - // Error 404 if not found - if(!found || message != NULL) + // Error 404 if config element not found + if(!found) + { + cJSON *json = JSON_NEW_OBJECT(); + JSON_SEND_OBJECT_CODE(json, 404); + } + + // Error 400 if unique item already present + if(message != NULL) { - // For any other error, a more specific message will have been added - // above - if(!message) - message = "No item specified"; return send_json_error(api, 400, "bad_request", message, diff --git a/src/api/dhcp.c b/src/api/dhcp.c index 2d94a3ee..91df5129 100644 --- a/src/api/dhcp.c +++ b/src/api/dhcp.c @@ -85,16 +85,18 @@ int api_dhcp_leases_DELETE(struct ftl_conn *api) // Send empty reply with code 204 No Content return send_json_error(api, 400, - "bad_request", + "bad_request", "The provided IPv4 address is invalid", - api->item); + api->item); } // Delete lease log_debug(DEBUG_API, "Deleting DHCP lease for address %s", api->item); - FTL_unlink_DHCP_lease(api->item); + const bool found = FTL_unlink_DHCP_lease(api->item); - // Send empty reply with code 204 No Content + // Send empty reply with codes: + // - 204 No Content (if a lease was deleted) + // - 404 Not Found (if no lease was found) cJSON *json = JSON_NEW_OBJECT(); - JSON_SEND_OBJECT_CODE(json, 204); + JSON_SEND_OBJECT_CODE(json, found ? 204 : 404); } \ No newline at end of file diff --git a/src/api/docs/content/specs/auth.yaml b/src/api/docs/content/specs/auth.yaml index 1a665f43..dabe9e5d 100644 --- a/src/api/docs/content/specs/auth.yaml +++ b/src/api/docs/content/specs/auth.yaml @@ -118,21 +118,27 @@ components: - Authentication operationId: "delete_groups" description: | - A logout attempt without a valid session will result in a `401 Unauthorized` error. + This endpoint can be used to delete the current session. It will + invalidate the session token and the CSRF token. The session can be + extended before its expiration by performing any authenticated action. + By default, the session lasts for 5 minutes. It can be invalidated by + either logging out or deleting the session. Additionally, the session + becomes invalid when the password is altered or a new application + password is created. - A session that was not created due to a login cannot be deleted (e.g., empty API password). + You can also delete a session by its ID using the `DELETE /auth/session/{id}` endpoint. + + Note that you cannot delete the current session if you have not + authenticated (e.g., no password has been set on your Pi-hole). responses: - '200': - description: OK (session not deletable) + '204': + description: No Content (deleted) + '404': + description: Not Found (no session active) content: application/json: schema: - allOf: - - $ref: 'auth.yaml#/components/schemas/session' - - $ref: 'common.yaml#/components/schemas/took' - examples: - no_login_required: - $ref: 'auth.yaml#/components/examples/no_login_required' + $ref: 'common.yaml#/components/schemas/took' '401': description: Unauthorized content: @@ -141,17 +147,6 @@ components: allOf: - $ref: 'common.yaml#/components/errors/unauthorized' - $ref: 'common.yaml#/components/schemas/took' - '410': - description: Gone - content: - application/json: - schema: - allOf: - - $ref: 'auth.yaml#/components/schemas/session' - - $ref: 'common.yaml#/components/schemas/took' - examples: - login_failed: - $ref: 'auth.yaml#/components/examples/login_failed' session_list: get: summary: List of all current sessions @@ -213,6 +208,12 @@ components: responses: '204': description: No Content (deleted) + '404': + description: Not Found (session not found) + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad Request content: diff --git a/src/api/docs/content/specs/clients.yaml b/src/api/docs/content/specs/clients.yaml index 0228696c..72f0a471 100644 --- a/src/api/docs/content/specs/clients.yaml +++ b/src/api/docs/content/specs/clients.yaml @@ -95,6 +95,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -233,6 +239,12 @@ components: responses: '204': description: Items deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: diff --git a/src/api/docs/content/specs/config.yaml b/src/api/docs/content/specs/config.yaml index 90b107dd..91ea040b 100644 --- a/src/api/docs/content/specs/config.yaml +++ b/src/api/docs/content/specs/config.yaml @@ -121,7 +121,7 @@ components: examples: invalid_path_depth: $ref: 'config.yaml#/components/examples/errors/bad_request/invalid_path_depth' - item_not_found: + item_already_present: $ref: 'config.yaml#/components/examples/errors/bad_request/item_already_present' '401': description: Unauthorized @@ -144,6 +144,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -155,8 +161,8 @@ components: examples: invalid_path_depth: $ref: 'config.yaml#/components/examples/errors/bad_request/invalid_path_depth' - item_not_found: - $ref: 'config.yaml#/components/examples/errors/bad_request/item_not_found' + item_already_present: + $ref: 'config.yaml#/components/examples/errors/bad_request/item_already_present' '401': description: Unauthorized content: @@ -795,13 +801,6 @@ components: key: "bad_request" message: "Invalid path depth" hint: "Use, e.g., DELETE /config/dnsmasq/upstreams/127.0.0.1 to remove \"127.0.0.1\" from config.dns.upstreams" - item_not_found: - summary: Item to be deleted does not exist - value: - error: - key: "bad_request" - message: "Item not found" - hint: "Can only delete existing items" item_already_present: summary: Item to be added exists already value: diff --git a/src/api/docs/content/specs/dhcp.yaml b/src/api/docs/content/specs/dhcp.yaml index bde0ef54..80cae62e 100644 --- a/src/api/docs/content/specs/dhcp.yaml +++ b/src/api/docs/content/specs/dhcp.yaml @@ -40,6 +40,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: diff --git a/src/api/docs/content/specs/domains.yaml b/src/api/docs/content/specs/domains.yaml index 115232dc..1ce7213f 100644 --- a/src/api/docs/content/specs/domains.yaml +++ b/src/api/docs/content/specs/domains.yaml @@ -128,6 +128,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -251,6 +257,12 @@ components: responses: '204': description: Items deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: diff --git a/src/api/docs/content/specs/groups.yaml b/src/api/docs/content/specs/groups.yaml index 20e61ae6..6712c599 100644 --- a/src/api/docs/content/specs/groups.yaml +++ b/src/api/docs/content/specs/groups.yaml @@ -63,6 +63,9 @@ components: - $ref: 'groups.yaml#/components/schemas/groups/get' # identical to GET - $ref: 'groups.yaml#/components/schemas/lists_processed' - $ref: 'common.yaml#/components/schemas/took' + headers: + Location: + $ref: 'common.yaml#/components/headers/Location' '400': description: Bad request content: @@ -94,6 +97,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -193,18 +202,14 @@ components: - "item": "test1" - "item": "test2" responses: - '201': - description: Created item + '204': + description: Items deleted + '404': + description: Item not found content: application/json: schema: - allOf: - - $ref: 'groups.yaml#/components/schemas/groups/get' # identical to GET - - $ref: 'groups.yaml#/components/schemas/lists_processed' - - $ref: 'common.yaml#/components/schemas/took' - headers: - Location: - $ref: 'common.yaml#/components/headers/Location' + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: diff --git a/src/api/docs/content/specs/info.yaml b/src/api/docs/content/specs/info.yaml index 65f118ea..78391afe 100644 --- a/src/api/docs/content/specs/info.yaml +++ b/src/api/docs/content/specs/info.yaml @@ -218,10 +218,16 @@ components: parameters: - $ref: 'info.yaml#/components/parameters/message_id' description: | - *Note:* There will be no content on success. You may specify multiple IDs to delete multiple messages at once (comma-separated in the path like `1,2,3`) + You may specify multiple IDs to delete multiple messages at once (comma-separated in the path like `1,2,3`) responses: '204': description: Item deleted + '404': + description: Not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -235,6 +241,14 @@ components: $ref: 'info.yaml#/components/examples/errors/messages/uri_error' bad_request: $ref: 'info.yaml#/components/examples/errors/messages/bad_request' + '401': + description: Unauthorized + content: + application/json: + schema: + allOf: + - $ref: 'common.yaml#/components/errors/unauthorized' + - $ref: 'common.yaml#/components/schemas/took' messages_count: get: summary: Get count of Pi-hole diagnosis messages diff --git a/src/api/docs/content/specs/lists.yaml b/src/api/docs/content/specs/lists.yaml index b5019702..90df09ac 100644 --- a/src/api/docs/content/specs/lists.yaml +++ b/src/api/docs/content/specs/lists.yaml @@ -93,6 +93,12 @@ components: responses: '204': description: Item deleted + '404': + description: Item not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: @@ -189,18 +195,14 @@ components: schema: $ref: 'lists.yaml#/components/schemas/lists/post' responses: - '201': - description: Created item + '204': + description: Items deleted + '404': + description: Item not found content: application/json: schema: - allOf: - - $ref: 'lists.yaml#/components/schemas/lists/get' - - $ref: 'lists.yaml#/components/schemas/lists_processed' - - $ref: 'common.yaml#/components/schemas/took' - headers: - Location: - $ref: 'common.yaml#/components/headers/Location' + $ref: 'common.yaml#/components/schemas/took' '400': description: Bad request content: diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index 00f74afb..1c497a65 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -93,6 +93,20 @@ components: responses: '204': description: No Content (deleted) + '404': + description: Not found + content: + application/json: + schema: + $ref: 'common.yaml#/components/schemas/took' + '400': + description: Bad request + content: + application/json: + schema: + allOf: + - $ref: 'common.yaml#/components/errors/bad_request' + - $ref: 'common.yaml#/components/schemas/took' '401': description: Unauthorized content: diff --git a/src/api/info.c b/src/api/info.c index 61398084..7158cd01 100644 --- a/src/api/info.c +++ b/src/api/info.c @@ -940,15 +940,18 @@ static int api_info_messages_DELETE(struct ftl_conn *api) } // Delete message with this ID from the database - delete_message(ids); + int deleted = 0; + delete_message(ids, &deleted); // Free memory free(id); cJSON_free(ids); - // Send empty reply with code 204 No Content + // Send empty reply with codes: + // - 204 No Content (if any items were deleted) + // - 404 Not Found (if no items were deleted) cJSON *json = JSON_NEW_OBJECT(); - JSON_SEND_OBJECT_CODE(json, 204); + JSON_SEND_OBJECT_CODE(json, deleted > 0 ? 204 : 404); } int api_info_messages(struct ftl_conn *api) diff --git a/src/api/list.c b/src/api/list.c index 8b371e95..467ea03b 100644 --- a/src/api/list.c +++ b/src/api/list.c @@ -705,7 +705,8 @@ static int api_list_remove(struct ftl_conn *api, } // From here on, we can assume the JSON payload is valid - if(gravityDB_delFromTable(listtype, array, &sql_msg)) + unsigned int deleted = 0u; + if(gravityDB_delFromTable(listtype, array, &deleted, &sql_msg)) { // Inform the resolver that it needs to reload gravity set_event(RELOAD_GRAVITY); @@ -714,9 +715,11 @@ static int api_list_remove(struct ftl_conn *api, if(allocated_json) cJSON_free(array); - // Send empty reply with code 204 No Content + // Send empty reply with codes: + // - 204 No Content (if any items were deleted) + // - 404 Not Found (if no items were deleted) cJSON *json = JSON_NEW_OBJECT(); - JSON_SEND_OBJECT_CODE(json, 204); + JSON_SEND_OBJECT_CODE(json, deleted > 0u ? 204 : 404); } else { diff --git a/src/api/network.c b/src/api/network.c index 9d26ddc2..532a985e 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -440,7 +440,8 @@ static int api_network_devices_DELETE(struct ftl_conn *api) // Delete row from network table by ID const char *sql_msg = NULL; - if(!networkTable_deleteDevice(db, device_id, &sql_msg)) + int deleted = 0; + if(!networkTable_deleteDevice(db, device_id, &deleted, &sql_msg)) { // Add SQL message (may be NULL = not available) return send_json_error(api, 500, @@ -452,9 +453,11 @@ static int api_network_devices_DELETE(struct ftl_conn *api) // Close database dbclose(&db); - // Send empty reply with code 204 No Content + // Send empty reply with codes: + // - 204 No Content (if any items were deleted) + // - 404 Not Found (if no items were deleted) cJSON *json = JSON_NEW_OBJECT(); - JSON_SEND_OBJECT_CODE(json, 204); + JSON_SEND_OBJECT_CODE(json, deleted > 0 ? 204 : 404); } int api_network_devices(struct ftl_conn *api) diff --git a/src/config/cli.c b/src/config/cli.c index a7868342..effaae96 100644 --- a/src/config/cli.c +++ b/src/config/cli.c @@ -160,8 +160,9 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru // Get password hash as allocated string (an empty string is hashed to an empty string) char *pwhash = strlen(value) > 0 ? create_password(value) : strdup(""); - // Verify that the password hash is valid - if(verify_password(value, pwhash, false) != PASSWORD_CORRECT) + // Verify that the password hash is either valid or empty + const enum password_result status = verify_password(value, pwhash, false); + if(status != PASSWORD_CORRECT && status != NO_PASSWORD_SET) { log_err("Failed to create password hash (verification failed), password remains unchanged"); free(pwhash); diff --git a/src/config/password.c b/src/config/password.c index 727d1908..d97df6d2 100644 --- a/src/config/password.c +++ b/src/config/password.c @@ -328,6 +328,7 @@ enum password_result verify_login(const char *password) log_debug(DEBUG_API, "App password correct"); return APPPASSWORD_CORRECT; } + // Return result return pw; } @@ -336,7 +337,7 @@ enum password_result verify_password(const char *password, const char *pwhash, c { // No password set if(pwhash == NULL || pwhash[0] == '\0') - return PASSWORD_CORRECT; + return NO_PASSWORD_SET; // No password supplied if(password == NULL || password[0] == '\0') @@ -606,8 +607,9 @@ bool set_and_check_password(struct conf_item *conf_item, const char *password) // Get password hash as allocated string (an empty string is hashed to an empty string) char *pwhash = strlen(password) > 0 ? create_password(password) : strdup(""); - // Verify that the password hash is valid - if(verify_password(password, pwhash, false) != PASSWORD_CORRECT) + // Verify that the password hash is valid or that no password is set + const enum password_result status = verify_password(password, pwhash, false); + if(status != PASSWORD_CORRECT && status != NO_PASSWORD_SET) { free(pwhash); log_warn("Failed to create password hash (verification failed), password remains unchanged"); diff --git a/src/config/password.h b/src/config/password.h index a4cc96b6..063e5dcf 100644 --- a/src/config/password.h +++ b/src/config/password.h @@ -26,6 +26,7 @@ enum password_result { PASSWORD_INCORRECT = 0, PASSWORD_CORRECT = 1, APPPASSWORD_CORRECT = 2, + NO_PASSWORD_SET = 3, PASSWORD_RATE_LIMITED = -1 } __attribute__((packed)); diff --git a/src/database/gravity-db.c b/src/database/gravity-db.c index 92c320b9..f137039a 100644 --- a/src/database/gravity-db.c +++ b/src/database/gravity-db.c @@ -1792,8 +1792,9 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, return okay; } -bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON* array, const char **message) +bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON* array, unsigned int *deleted, const char **message) { + // Return early if database is not available if(gravity_db == NULL) { *message = "Database not available"; @@ -2004,6 +2005,9 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON* break; } + + // Add number of deleted rows + *deleted += sqlite3_changes(gravity_db); } // Drop temporary table diff --git a/src/database/gravity-db.h b/src/database/gravity-db.h index 7d47892d..a77deb72 100644 --- a/src/database/gravity-db.h +++ b/src/database/gravity-db.h @@ -69,7 +69,7 @@ bool gravityDB_readTableGetRow(const enum gravity_list_type listtype, tablerow * void gravityDB_readTableFinalize(void); bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row, const char **message, const enum http_method method); -bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON* array, const char **message); +bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON* array, unsigned int *deleted, const char **message); bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups, const tablerow *row, const char **message); diff --git a/src/database/message-table.c b/src/database/message-table.c index 3461b200..55c36a24 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -378,7 +378,7 @@ end_of_add_message: // Close database connection return rowid; } -bool delete_message(cJSON *ids) +bool delete_message(cJSON *ids, int *deleted) { // Return early if database is known to be broken if(FTLDBerror()) @@ -413,6 +413,10 @@ bool delete_message(cJSON *ids) log_err("SQL error (%i): %s", sqlite3_errcode(db), sqlite3_errmsg(db)); return false; } + + // Add to deleted count + *deleted += sqlite3_changes(db); + sqlite3_reset(res); sqlite3_clear_bindings(res); } diff --git a/src/database/message-table.h b/src/database/message-table.h index 196b406a..14956bf5 100644 --- a/src/database/message-table.h +++ b/src/database/message-table.h @@ -16,7 +16,7 @@ int count_messages(const bool filter_dnsmasq_warnings); bool format_messages(cJSON *array); bool create_message_table(sqlite3 *db); -bool delete_message(cJSON *ids); +bool delete_message(cJSON *ids, int *deleted); bool flush_message_table(void); void logg_regex_warning(const char *type, const char *warning, const int dbindex, const char *regex); void logg_subnet_warning(const char *ip, const int matching_count, const char *matching_ids, diff --git a/src/database/network-table.c b/src/database/network-table.c index 8c0e9a1f..c76de35c 100644 --- a/src/database/network-table.c +++ b/src/database/network-table.c @@ -2425,7 +2425,7 @@ void networkTable_readIPsFinalize(sqlite3_stmt *read_stmt) sqlite3_finalize(read_stmt); } -bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message) +bool networkTable_deleteDevice(sqlite3 *db, const int id, int *deleted, const char **message) { // First step: Delete all associated IPs of this device // Prepare SQLite statement @@ -2462,6 +2462,9 @@ bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message) return false; } + // Check if we deleted any rows + *deleted += sqlite3_changes(db); + // Finalize statement sqlite3_finalize(stmt); @@ -2498,6 +2501,9 @@ bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message) return false; } + // Check if we deleted any rows + *deleted += sqlite3_changes(db); + // Finalize statement sqlite3_finalize(stmt); diff --git a/src/database/network-table.h b/src/database/network-table.h index 1cb4697d..8da0fa3f 100644 --- a/src/database/network-table.h +++ b/src/database/network-table.h @@ -52,6 +52,6 @@ bool networkTable_readIPs(sqlite3 *db, sqlite3_stmt **read_stmt, const int id, c bool networkTable_readIPsGetRecord(sqlite3_stmt *read_stmt, network_addresses_record *network_addresses, const char **message); void networkTable_readIPsFinalize(sqlite3_stmt *read_stmt); -bool networkTable_deleteDevice(sqlite3 *db, const int id, const char **message); +bool networkTable_deleteDevice(sqlite3 *db, const int id, int *deleted, const char **message); #endif //NETWORKTABLE_H diff --git a/src/dnsmasq/dnsmasq.c b/src/dnsmasq/dnsmasq.c index ef4ab3e9..2dd8102d 100644 --- a/src/dnsmasq/dnsmasq.c +++ b/src/dnsmasq/dnsmasq.c @@ -94,7 +94,7 @@ int main_dnsmasq (int argc, char **argv) sigaction(SIGUSR1, &sigact, NULL); sigaction(SIGUSR2, &sigact, NULL); sigaction(SIGHUP, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); + sigaction(SIGUSR6, &sigact, NULL); // Pi-hole modification sigaction(SIGALRM, &sigact, NULL); sigaction(SIGCHLD, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); @@ -1330,7 +1330,7 @@ static void sig_handler(int sig) event = EVENT_CHILD; else if (sig == SIGALRM) event = EVENT_ALARM; - else if (sig == SIGTERM) + else if (sig == SIGUSR6) // Pi-hole modified event = EVENT_TERM; else if (sig == SIGUSR1) event = EVENT_DUMP; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index a110af4c..311ce2c8 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -3263,6 +3263,7 @@ bool FTL_unlink_DHCP_lease(const char *ipaddr) #endif else { + // Invalid IP address or no lease found return false; } diff --git a/src/signals.c b/src/signals.c index 9f769739..9c638445 100644 --- a/src/signals.c +++ b/src/signals.c @@ -311,11 +311,94 @@ static void SIGRT_handler(int signum, siginfo_t *si, void *unused) // Parse neighbor cache set_event(PARSE_NEIGHBOR_CACHE); } + // else if(rtsig == 6) + // { + // // Signal internally used to signal dnsmasq it has to stop + // } // Restore errno before returning back to previous context errno = _errno; } +static void SIGTERM_handler(int signum, siginfo_t *si, void *unused) +{ + // Ignore SIGTERM outside of the main process (TCP forks) + if(mpid != getpid()) + return; + + // Get PID and UID of the process that sent the terminating signal + const pid_t kill_pid = si->si_pid; + const uid_t kill_uid = si->si_uid; + + // Get name of the process that sent the terminating signal + char kill_name[256] = { 0 }; + char kill_exe [256] = { 0 }; + snprintf(kill_exe, sizeof(kill_exe), "/proc/%ld/cmdline", (long int)kill_pid); + FILE *fp = fopen(kill_exe, "r"); + if(fp != NULL) + { + // Successfully opened file + size_t read = 0; + // Read line from file + if((read = fread(kill_name, sizeof(char), sizeof(kill_name), fp)) > 0) + { + // Successfully read line + + // cmdline contains the command-line arguments as a set + // of strings separated by null bytes ('\0'), with a + // further null byte after the last string. Hence, we + // need to replace all null bytes with spaces for + // displaying it below + for(unsigned int i = 0; i < min((size_t)read, sizeof(kill_name)); i++) + { + if(kill_name[i] == '\0') + kill_name[i] = ' '; + } + + // Remove any trailing spaces + for(unsigned int i = read - 1; i > 0; i--) + { + if(kill_name[i] == ' ') + kill_name[i] = '\0'; + else + break; + } + } + else + { + // Failed to read line + strcpy(kill_name, "N/A"); + } + } + else + { + // Failed to open file + strcpy(kill_name, "N/A"); + } + + // Get username of the process that sent the terminating signal + char kill_user[256] = { 0 }; + struct passwd *pwd = getpwuid(kill_uid); + if(pwd != NULL) + { + // Successfully obtained username + strncpy(kill_user, pwd->pw_name, sizeof(kill_user)); + } + else + { + // Failed to obtain username + strcpy(kill_user, "N/A"); + } + + // Log who sent the signal + log_info("Asked to terminate by \"%s\" (PID %ld, user %s UID %ld)", + kill_name, (long int)kill_pid, + kill_user, (long int)kill_uid); + + // Terminate dnsmasq to stop DNS service + raise(SIGUSR6); +} + // Register ordinary signals handler void handle_signals(void) { @@ -337,6 +420,13 @@ void handle_signals(void) } } + // Also catch SIGTERM + struct sigaction SIGaction = { 0 }; + SIGaction.sa_flags = SA_SIGINFO; + sigemptyset(&SIGaction.sa_mask); + SIGaction.sa_sigaction = &SIGTERM_handler; + sigaction(SIGTERM, &SIGaction, NULL); + // Log start time of FTL FTLstarttime = time(NULL); } @@ -351,8 +441,12 @@ void handle_realtime_signals(void) // Catch all real-time signals for(int signum = SIGRTMIN; signum <= SIGRTMAX; signum++) { - struct sigaction SIGACTION; - memset(&SIGACTION, 0, sizeof(struct sigaction)); + if(signum == SIGUSR6) + // Skip SIGUSR6 as it is used internally to signify + // dnsmasq to stop + continue; + + struct sigaction SIGACTION = { 0 }; SIGACTION.sa_flags = SA_SIGINFO; sigemptyset(&SIGACTION.sa_mask); SIGACTION.sa_sigaction = &SIGRT_handler; diff --git a/src/signals.h b/src/signals.h index 78b2d282..4a08e4b9 100644 --- a/src/signals.h +++ b/src/signals.h @@ -12,6 +12,8 @@ #include "enums.h" +#define SIGUSR6 (SIGRTMIN + 6) + // defined in dnsmasq/dnsmasq.h extern volatile char FTL_terminate; diff --git a/src/tools/gravity-parseList.c b/src/tools/gravity-parseList.c index 61856134..fc73dafa 100644 --- a/src/tools/gravity-parseList.c +++ b/src/tools/gravity-parseList.c @@ -542,8 +542,6 @@ end_of_parseList: // Print newline puts(""); } - // Print final newline - puts(""); } // Free memory diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index e1304a37..f3966f08 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -200,7 +200,10 @@ }) #define JSON_SEND_OBJECT_CODE(object, code)({ \ - cJSON_AddNumberToObject(object, "took", double_time() - api->now);\ + if((code) != 204) \ + { \ + cJSON_AddNumberToObject(object, "took", double_time() - api->now); \ + } \ char *json_string = json_formatter(object); \ if(json_string == NULL) \ { \