diff --git a/.github/openapi/redocly-agent-api.yaml b/.github/openapi/redocly-agent-api.yaml new file mode 100644 index 000000000..4726099a3 --- /dev/null +++ b/.github/openapi/redocly-agent-api.yaml @@ -0,0 +1,18 @@ +extends: + - recommended + +rules: + # The agent API is a single-endpoint API (POST /api/server.php) that + # dispatches on the "action" field in the body. These rules are relaxed + # because they don't apply to this non-REST, non-JSON:API design. + info-license: off + info-contact: off + operation-tag-defined: off + security-defined: off + # The agent API always returns HTTP 200 — errors are in the JSON body + # (response:"ERROR" + message). No 4XX/5XX HTTP status codes are used. + operation-4xx-response: off + # No servers section (the spec is served at the same host as the API) + no-empty-servers: off + # The agent API uses application/json, not application/vnd.api+json + no-server-example.com: off diff --git a/.github/openapi/spectral-agent-api.yml b/.github/openapi/spectral-agent-api.yml new file mode 100644 index 000000000..758a1355e --- /dev/null +++ b/.github/openapi/spectral-agent-api.yml @@ -0,0 +1,23 @@ +extends: + - spectral:oas + +formats: + - oas3.1 + +# Relaxed ruleset for the Hashtopolis agent API. +# +# The agent API is a single-endpoint JSON API (POST /api/server.php) that +# dispatches on the "action" field in the request body. It uses +# application/json (not application/vnd.api+json), so the JSON:API ruleset +# (spectral-jsonapi.yml) does NOT apply. This ruleset extends the base +# Spectral OAS rules and disables rules that don't fit the agent API design. + +rules: + # Auth is via token in the JSON body, not via HTTP security schemes. + oas3-api-servers: off + oas3-operation-security-defined: off + # The single endpoint doesn't use tags. + operation-tag-defined: off + # The API doesn't define contact or license info. + info-contact: off + info-license: off diff --git a/.github/workflows/openapi-lint.yml b/.github/workflows/openapi-lint.yml index 02c46a469..5490fe402 100644 --- a/.github/workflows/openapi-lint.yml +++ b/.github/workflows/openapi-lint.yml @@ -32,9 +32,19 @@ jobs: key: ${{ runner.os }}-npm-openapi-spectral6.15.1-redocly2.24.0 - name: Install OpenAPI tooling run: npm install -g @stoplight/spectral-cli@6.15.1 @redocly/cli@2.24.0 --min-release-age=14 - - name: Download OpenAPI schema - run: wget -q http://localhost:8080/api/v2/openapi.json -O openapi.json - - name: Lint OpenAPI schema with Redocly - run: redocly lint openapi.json - - name: Lint OpenAPI schema with Spectral - run: spectral lint openapi.json --ruleset .github/openapi/spectral-jsonapi.yml -D + + # --- v2 API (JSON:API) --- + - name: Download v2 OpenAPI schema + run: wget -q http://localhost:8080/api/v2/openapi.json -O openapi-v2.json + - name: Lint v2 OpenAPI schema with Redocly + run: redocly lint openapi-v2.json + - name: Lint v2 OpenAPI schema with Spectral + run: spectral lint openapi-v2.json --ruleset .github/openapi/spectral-jsonapi.yml -D + + # --- Agent API (JSON, not JSON:API) --- + - name: Download Agent API OpenAPI schema + run: wget -q http://localhost:8080/api/server_openapi.php -O openapi-agent.json + - name: Lint Agent API OpenAPI schema with Redocly + run: redocly lint openapi-agent.json --config .github/openapi/redocly-agent-api.yaml + - name: Lint Agent API OpenAPI schema with Spectral + run: spectral lint openapi-agent.json --ruleset .github/openapi/spectral-agent-api.yml -D diff --git a/ci/apiv2/test_agent_protocol.py b/ci/apiv2/test_agent_protocol.py index e9525800f..387ff6034 100644 --- a/ci/apiv2/test_agent_protocol.py +++ b/ci/apiv2/test_agent_protocol.py @@ -339,10 +339,21 @@ def test_login_invalid_token(self): self.assertEqual(parse_envelope(body)['message'], "Invalid token!") def test_login_missing_fields(self): - """Login without required fields (token, clientSignature) returns 'Invalid login query!'.""" + """Login without any fields (no token, no clientSignature) returns 'Invalid token!' (middleware handles missing token for PSR-7 controllers).""" + code, body = agent_request({"action": "login"}) + assert_error_envelope(self, body, "login") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") + + def test_login_invalid_token_takes_priority_over_missing_fields(self): + """When the token is present but invalid, 'Invalid token!' takes priority over missing-field errors. + + The TokenAuthMiddleware checks the token before the controller validates + other fields, so an invalid token with a missing clientSignature returns + 'Invalid token!' rather than 'Invalid login query!'. + """ code, body = agent_request({"action": "login", "token": "x"}) assert_error_envelope(self, body, "login") - self.assertEqual(parse_envelope(body)['message'], "Invalid login query!") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") # --------------------------------------------------------------------------- @@ -658,10 +669,10 @@ def test_get_task_assigned(self): self.assertIn(resp['benchType'], ("speed", "run")) def test_get_task_missing_fields(self): - """Sending getTask without a token returns 'Invalid task query!'.""" + """Sending getTask without a token returns 'Invalid token!' (middleware handles missing token for PSR-7 controllers).""" code, body = agent_request({"action": "getTask"}) assert_error_envelope(self, body, "getTask") - self.assertEqual(parse_envelope(body)['message'], "Invalid task query!") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") def test_get_task_invalid_token(self): """Sending getTask with a bogus token returns 'Invalid token!'.""" @@ -1586,11 +1597,10 @@ def test_send_progress_state_cracked(self): self.assertIsInstance(resp['skipped'], int) def test_send_progress_missing_fields(self): - """Sending sendProgress without required fields returns 'Invalid progress query!'.""" - dummy = self._dummy() - code, body = agent_request({"action": "sendProgress", "token": dummy.token}) + """Sending sendProgress without required fields returns 'Invalid token!' (middleware handles missing token for PSR-7 controllers).""" + code, body = agent_request({"action": "sendProgress"}) assert_error_envelope(self, body, "sendProgress") - self.assertEqual(parse_envelope(body)['message'], "Invalid progress query!") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") # --------------------------------------------------------------------------- @@ -1608,10 +1618,10 @@ def test_get_health_check_none_available(self): self.assertEqual(parse_envelope(body)['message'], "No health check available for this agent!") def test_get_health_check_missing_fields(self): - """Sending getHealthCheck without a token returns 'Invalid get health check query!'.""" + """Sending getHealthCheck without a token returns 'Invalid token!' (middleware handles missing token for PSR-7 controllers).""" code, body = agent_request({"action": "getHealthCheck"}) assert_error_envelope(self, body, "getHealthCheck") - self.assertEqual(parse_envelope(body)['message'], "Invalid get health check query!") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") def test_get_health_check_invalid_token(self): """Sending getHealthCheck with a bogus token returns 'Invalid token!'.""" @@ -1768,7 +1778,7 @@ def test_deregister_invalid_token(self): self.assertEqual(parse_envelope(body)['message'], "Invalid token!") def test_deregister_missing_fields(self): - """Sending deregister without a token returns 'Invalid de-registering query!'.""" + """Sending deregister without a token returns 'Invalid token!' (middleware handles missing token for PSR-7 controllers).""" code, body = agent_request({"action": "deregister"}) assert_error_envelope(self, body, "deregister") - self.assertEqual(parse_envelope(body)['message'], "Invalid de-registering query!") + self.assertEqual(parse_envelope(body)['message'], "Invalid token!") diff --git a/ci/phpunit/agentapi/ActionRegistryTest.php b/ci/phpunit/agentapi/ActionRegistryTest.php new file mode 100644 index 000000000..8e7d56f1c --- /dev/null +++ b/ci/phpunit/agentapi/ActionRegistryTest.php @@ -0,0 +1,117 @@ +assertContains($action, $registered, "Action '$action' should be registered"); + } + $this->assertCount(19, $registered, 'All 19 actions should be registered'); + } + + /** + * Each action string maps to the correct handler class. + */ + public function testGetHandlerReturnsCorrectClass(): void { + $this->assertEquals(TestConnectionAction::class, ActionRegistry::getHandler(PActions::TEST_CONNECTION)); + $this->assertEquals(RegisterAgentAction::class, ActionRegistry::getHandler(PActions::REGISTER)); + $this->assertEquals(UpdateInformationAction::class, ActionRegistry::getHandler(PActions::UPDATE_CLIENT_INFORMATION)); + $this->assertEquals(LoginAction::class, ActionRegistry::getHandler(PActions::LOGIN)); + $this->assertEquals(CheckClientVersionAction::class, ActionRegistry::getHandler(PActions::CHECK_CLIENT_VERSION)); + $this->assertEquals(DownloadBinaryAction::class, ActionRegistry::getHandler(PActions::DOWNLOAD_BINARY)); + $this->assertEquals(ClientErrorAction::class, ActionRegistry::getHandler(PActions::CLIENT_ERROR)); + $this->assertEquals(GetFileAction::class, ActionRegistry::getHandler(PActions::GET_FILE)); + $this->assertEquals(GetHashlistAction::class, ActionRegistry::getHandler(PActions::GET_HASHLIST)); + $this->assertEquals(GetTaskAction::class, ActionRegistry::getHandler(PActions::GET_TASK)); + $this->assertEquals(GetChunkAction::class, ActionRegistry::getHandler(PActions::GET_CHUNK)); + $this->assertEquals(SendKeyspaceAction::class, ActionRegistry::getHandler(PActions::SEND_KEYSPACE)); + $this->assertEquals(SendBenchmarkAction::class, ActionRegistry::getHandler(PActions::SEND_BENCHMARK)); + $this->assertEquals(SendProgressAction::class, ActionRegistry::getHandler(PActions::SEND_PROGRESS)); + $this->assertEquals(GetFileStatusAction::class, ActionRegistry::getHandler(PActions::GET_FILE_STATUS)); + $this->assertEquals(GetHealthCheckAction::class, ActionRegistry::getHandler(PActions::GET_HEALTH_CHECK)); + $this->assertEquals(SendHealthCheckAction::class, ActionRegistry::getHandler(PActions::SEND_HEALTH_CHECK)); + $this->assertEquals(GetFoundAction::class, ActionRegistry::getHandler(PActions::GET_FOUND)); + $this->assertEquals(DeregisterAction::class, ActionRegistry::getHandler(PActions::DEREGISTER)); + } + + /** + * An unknown action string returns null. + */ + public function testGetHandlerReturnsNullForUnknownAction(): void { + $this->assertNull(ActionRegistry::getHandler('nonexistent')); + $this->assertNull(ActionRegistry::getHandler('')); + $this->assertNull(ActionRegistry::getHandler('INV')); + } + + /** + * A null action returns null. + */ + public function testGetHandlerReturnsNullForNullAction(): void { + $this->assertNull(ActionRegistry::getHandler(null)); + } + + /** + * getActions returns a list of all registered action strings. + */ + public function testGetActionsReturnsList(): void { + $actions = ActionRegistry::getActions(); + $this->assertCount(19, $actions); + } +} diff --git a/ci/phpunit/agentapi/AgentEnvelopeTest.php b/ci/phpunit/agentapi/AgentEnvelopeTest.php new file mode 100644 index 000000000..d1ec1215c --- /dev/null +++ b/ci/phpunit/agentapi/AgentEnvelopeTest.php @@ -0,0 +1,68 @@ +assertEquals(PActions::TEST_CONNECTION, $envelope[PResponse::ACTION]); + $this->assertEquals(PValues::SUCCESS, $envelope[PResponse::RESPONSE]); + } + + /** + * A success envelope with no extra fields has exactly 2 keys. + */ + public function testSuccessEnvelopeNoExtraFields(): void { + $envelope = AgentEnvelope::success(PActions::TEST_CONNECTION); + $this->assertCount(2, $envelope); + } + + /** + * A success envelope with extra fields includes them alongside the base keys. + */ + public function testSuccessEnvelopeWithExtraFields(): void { + $envelope = AgentEnvelope::success(PActions::REGISTER, ['token' => 'abc123']); + $this->assertEquals(PActions::REGISTER, $envelope[PResponse::ACTION]); + $this->assertEquals(PValues::SUCCESS, $envelope[PResponse::RESPONSE]); + $this->assertEquals('abc123', $envelope['token']); + $this->assertCount(3, $envelope); + } + + /** + * An error envelope has action, response='ERROR', and a message. + */ + public function testErrorEnvelopeShape(): void { + $envelope = AgentEnvelope::error(PActions::LOGIN, 'Invalid token!'); + $this->assertEquals(PActions::LOGIN, $envelope[PResponse::ACTION]); + $this->assertEquals(PValues::ERROR, $envelope[PResponse::RESPONSE]); + $this->assertEquals('Invalid token!', $envelope[PResponseErrorMessage::MESSAGE]); + $this->assertCount(3, $envelope); + } + + /** + * The 'INV' action string is used for unknown/missing actions. + */ + public function testErrorEnvelopeWithInvAction(): void { + $envelope = AgentEnvelope::error('INV', 'Invalid query!'); + $this->assertEquals('INV', $envelope[PResponse::ACTION]); + $this->assertEquals(PValues::ERROR, $envelope[PResponse::RESPONSE]); + $this->assertEquals('Invalid query!', $envelope[PResponseErrorMessage::MESSAGE]); + } +} diff --git a/ci/phpunit/agentapi/AgentErrorHandlerTest.php b/ci/phpunit/agentapi/AgentErrorHandlerTest.php new file mode 100644 index 000000000..2dc0cb91a --- /dev/null +++ b/ci/phpunit/agentapi/AgentErrorHandlerTest.php @@ -0,0 +1,106 @@ +assertEquals(200, $response->getStatusCode()); + } + + /** + * invResponse() sets Content-Type to application/json. + */ + public function testInvResponseContentTypeIsJson(): void { + $response = AgentErrorHandler::invResponse(); + $this->assertEquals('application/json', $response->getHeaderLine('Content-Type')); + } + + /** + * invResponse() with no arguments uses the default message 'Invalid query!'. + */ + public function testInvResponseDefaultMessage(): void { + $response = AgentErrorHandler::invResponse(); + $body = json_decode((string)$response->getBody(), true); + $this->assertEquals('INV', $body[PResponse::ACTION]); + $this->assertEquals(PValues::ERROR, $body[PResponse::RESPONSE]); + $this->assertEquals('Invalid query!', $body[PResponseErrorMessage::MESSAGE]); + } + + /** + * invResponse() with a custom message includes it in the envelope. + */ + public function testInvResponseCustomMessage(): void { + $response = AgentErrorHandler::invResponse('Something went wrong'); + $body = json_decode((string)$response->getBody(), true); + $this->assertEquals('INV', $body[PResponse::ACTION]); + $this->assertEquals(PValues::ERROR, $body[PResponse::RESPONSE]); + $this->assertEquals('Something went wrong', $body[PResponseErrorMessage::MESSAGE]); + } + + /** + * errorResponse() returns a 200 PSR-7 response. + */ + public function testErrorResponseReturnsHttp200(): void { + $response = AgentErrorHandler::errorResponse('login', 'Invalid token!'); + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * errorResponse() sets Content-Type to application/json. + */ + public function testErrorResponseContentTypeIsJson(): void { + $response = AgentErrorHandler::errorResponse('login', 'Invalid token!'); + $this->assertEquals('application/json', $response->getHeaderLine('Content-Type')); + } + + /** + * errorResponse() echoes the action string and includes the message. + */ + public function testErrorResponseShape(): void { + $response = AgentErrorHandler::errorResponse('login', 'Invalid token!'); + $body = json_decode((string)$response->getBody(), true); + $this->assertEquals('login', $body[PResponse::ACTION]); + $this->assertEquals(PValues::ERROR, $body[PResponse::RESPONSE]); + $this->assertEquals('Invalid token!', $body[PResponseErrorMessage::MESSAGE]); + $this->assertCount(3, $body); + } + + /** + * The INV response body is valid JSON that decodes to the expected envelope. + */ + public function testInvResponseBodyIsValidJson(): void { + $response = AgentErrorHandler::invResponse(); + $raw = (string)$response->getBody(); + $decoded = json_decode($raw, true); + $this->assertNotNull($decoded, "Response body should be valid JSON: $raw"); + $this->assertIsArray($decoded); + } + + /** + * The error response body is valid JSON that decodes to the expected envelope. + */ + public function testErrorResponseBodyIsValidJson(): void { + $response = AgentErrorHandler::errorResponse('getTask', 'Invalid token!'); + $raw = (string)$response->getBody(); + $decoded = json_decode($raw, true); + $this->assertNotNull($decoded, "Response body should be valid JSON: $raw"); + $this->assertIsArray($decoded); + } +} diff --git a/ci/phpunit/dba/MassUpdateSetTest.php b/ci/phpunit/dba/MassUpdateSetTest.php index 1a847b6b2..66b7e92f8 100644 --- a/ci/phpunit/dba/MassUpdateSetTest.php +++ b/ci/phpunit/dba/MassUpdateSetTest.php @@ -20,6 +20,12 @@ public function testGetMatchValue(): void { $this->assertEquals('key1', $set->getMatchValue()); } + /** Verify getMatchValue returns the match int. */ + public function testGetMatchValueInt(): void { + $set = new MassUpdateSet(42, 'val1'); + $this->assertSame(42, $set->getMatchValue()); + } + /** Verify getUpdateValue returns the update string. */ public function testGetUpdateValueString(): void { $set = new MassUpdateSet('key1', 'val1'); diff --git a/doc/protocol.pdf b/doc/protocol.pdf index 7fbdbddc3..a9ee80a6a 100755 Binary files a/doc/protocol.pdf and b/doc/protocol.pdf differ diff --git a/doc/protocol.tex b/doc/protocol.tex index a383e7f93..8ccfe9e62 100755 --- a/doc/protocol.tex +++ b/doc/protocol.tex @@ -10,6 +10,8 @@ \section*{Introduction} The communication between Hashtopolis Clients (Agents) and Server is always in JSON formatted values. When sending a request to the server, it should be a POST containing the JSON data. + A machine-readable OpenAPI 3.1 specification of this protocol is available at \texttt{/api/server\_openapi.php} on the running server. It is generated from the server's source code and always reflects the current implementation. The examples in this document remain the canonical human-readable reference. + \section*{Errors} In case of an error with the query which the client sends to the server, the response will have following format with the corresponding action which was requested and the error message which should help in getting information about this error. \begin{verbatim} diff --git a/phpstan.neon b/phpstan.neon index d76f06f9c..09d5999e4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,7 @@ parameters: paths: - src/inc/apiv2 + - src/inc/agentapi - src/dba - src/inc - ci/phpunit diff --git a/src/api/server.php b/src/api/server.php index 492aa1499..b1d91d2be 100755 --- a/src/api/server.php +++ b/src/api/server.php @@ -1,167 +1,20 @@ sendErrorResponse("INV", "Invalid query!"); -} -else { - $api->execute($QUERY); -} - +AgentApiApp::create()->run(); diff --git a/src/api/server_openapi.php b/src/api/server_openapi.php new file mode 100644 index 000000000..b930f3629 --- /dev/null +++ b/src/api/server_openapi.php @@ -0,0 +1,17 @@ +matchValue = $matchValue; $this->updateValue = $updateValue; } - function getMatchValue(): string { + function getMatchValue(): mixed { return $this->matchValue; } diff --git a/src/inc/agentapi/AgentApiApp.php b/src/inc/agentapi/AgentApiApp.php new file mode 100644 index 000000000..90dade51e --- /dev/null +++ b/src/inc/agentapi/AgentApiApp.php @@ -0,0 +1,90 @@ +add(new TokenAuthMiddleware()); + $app->add(new AgentBodyParserMiddleware()); + + $errorMiddleware = $app->addErrorMiddleware(true, true, true); + $errorMiddleware->setDefaultErrorHandler( + function ( + Request $request, + Throwable $exception, + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails, + ): ResponseInterface { + return AgentErrorHandler::invResponse(); + }, + ); + $app->addRoutingMiddleware(); + + $app->post('/api/server.php', function (Request $request, Response $response): ResponseInterface { + $body = $request->getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + $action = $body[PQuery::ACTION] ?? null; + + DServerLog::log(DServerLog::TRACE, 'Received from ' . Util::getIP() . ': ' . json_encode($body)); + + $handlerClass = ActionRegistry::getHandler($action); + if ($handlerClass === null) { + return AgentErrorHandler::invResponse(); + } + + /** @var AgentAction $controller */ + $controller = new $handlerClass(); + try { + return $controller($request, $response); + } catch (Exception $e) { + return AgentErrorHandler::errorResponse($action, $e->getMessage()); + } catch (Throwable $e) { + error_log("AgentApi [$action]: " . $e->getMessage()); + return AgentErrorHandler::errorResponse($action, 'Internal server error'); + } + }); + + return $app; + } +} diff --git a/src/inc/agentapi/auth/TokenAuthMiddleware.php b/src/inc/agentapi/auth/TokenAuthMiddleware.php new file mode 100644 index 000000000..7635a0b72 --- /dev/null +++ b/src/inc/agentapi/auth/TokenAuthMiddleware.php @@ -0,0 +1,77 @@ +getParsedBody(); + $action = is_array($body) ? ($body[PQuery::ACTION] ?? null) : null; + + if (in_array($action, self::NO_AUTH_ACTIONS, true)) { + return $handler->handle($request); + } + + if ($action === null || ActionRegistry::getHandler($action) === null) { + return $handler->handle($request); + } + + $token = is_array($body) ? ($body[PQuery::TOKEN] ?? null) : null; + if ($token === null) { + return AgentErrorHandler::errorResponse($action, 'Invalid token!'); + } + + $qF = new QueryFilter(Agent::TOKEN, $token, '='); + $agent = Factory::getAgentFactory()->filter([Factory::FILTER => [$qF]], true); + if ($agent === null) { + DServerLog::log(DServerLog::WARNING, 'Agent from ' . Util::getIP() . ' sent invalid token!'); + return AgentErrorHandler::errorResponse($action, 'Invalid token!'); + } + + return $handler->handle($request->withAttribute(AgentAction::AGENT_ATTRIBUTE, $agent)); + } +} diff --git a/src/inc/agentapi/common/ActionRegistry.php b/src/inc/agentapi/common/ActionRegistry.php new file mode 100644 index 000000000..773d216ee --- /dev/null +++ b/src/inc/agentapi/common/ActionRegistry.php @@ -0,0 +1,74 @@ + */ + private static array $map = [ + PActions::TEST_CONNECTION => TestConnectionAction::class, + PActions::REGISTER => RegisterAgentAction::class, + PActions::UPDATE_CLIENT_INFORMATION => UpdateInformationAction::class, + PActions::LOGIN => LoginAction::class, + PActions::CHECK_CLIENT_VERSION => CheckClientVersionAction::class, + PActions::DOWNLOAD_BINARY => DownloadBinaryAction::class, + PActions::CLIENT_ERROR => ClientErrorAction::class, + PActions::GET_FILE => GetFileAction::class, + PActions::GET_HASHLIST => GetHashlistAction::class, + PActions::GET_TASK => GetTaskAction::class, + PActions::GET_CHUNK => GetChunkAction::class, + PActions::SEND_KEYSPACE => SendKeyspaceAction::class, + PActions::SEND_BENCHMARK => SendBenchmarkAction::class, + PActions::SEND_PROGRESS => SendProgressAction::class, + PActions::GET_FILE_STATUS => GetFileStatusAction::class, + PActions::GET_HEALTH_CHECK => GetHealthCheckAction::class, + PActions::SEND_HEALTH_CHECK => SendHealthCheckAction::class, + PActions::GET_FOUND => GetFoundAction::class, + PActions::DEREGISTER => DeregisterAction::class, + ]; + + /** + * @return class-string|null The handler class name, or null if the action + * is unknown / missing. + */ + public static function getHandler(?string $action): ?string { + if ($action === null) { + return null; + } + return self::$map[$action] ?? null; + } + + /** @return list All registered action strings. */ + public static function getActions(): array { + return array_keys(self::$map); + } +} diff --git a/src/inc/agentapi/common/AgentAction.php b/src/inc/agentapi/common/AgentAction.php new file mode 100644 index 000000000..cc4dc39fc --- /dev/null +++ b/src/inc/agentapi/common/AgentAction.php @@ -0,0 +1,34 @@ +getAttribute(AgentAction::AGENT_ATTRIBUTE)``. + */ + public const AGENT_ATTRIBUTE = 'agent'; + + /** + * @param Request $request + * @param Response $response + * @return ResponseInterface + * @throws Exception + */ + public function __invoke(Request $request, Response $response): ResponseInterface; +} diff --git a/src/inc/agentapi/common/AgentBodyParserMiddleware.php b/src/inc/agentapi/common/AgentBodyParserMiddleware.php new file mode 100644 index 000000000..ecb777ce5 --- /dev/null +++ b/src/inc/agentapi/common/AgentBodyParserMiddleware.php @@ -0,0 +1,35 @@ +getParsedBody()``. + */ +final class AgentBodyParserMiddleware implements MiddlewareInterface { + public function process(Request $request, RequestHandler $handler): ResponseInterface { + if ($request->getParsedBody() === null) { + $raw = file_get_contents('php://input'); + $parsed = json_decode($raw, true); + if (!is_array($parsed)) { + $parsed = []; + } + $request = $request->withParsedBody($parsed); + } + return $handler->handle($request); + } +} diff --git a/src/inc/agentapi/common/AgentEnvelope.php b/src/inc/agentapi/common/AgentEnvelope.php new file mode 100644 index 000000000..1503ba3e6 --- /dev/null +++ b/src/inc/agentapi/common/AgentEnvelope.php @@ -0,0 +1,48 @@ +, "response":"SUCCESS", ...}``. + * + * @param string $action The action string to echo back. + * @param array $fields Additional fields to include. + * @return array + */ + public static function success(string $action, array $fields = []): array { + return array_merge( + [PResponse::ACTION => $action, PResponse::RESPONSE => PValues::SUCCESS], + $fields, + ); + } + + /** + * Build an error envelope: ``{"action":, "response":"ERROR", "message":}``. + * + * @param string $action The action string to echo back (or ``"INV"`` for + * unknown / missing actions). + * @param string $message The error message. + * @return array + */ + public static function error(string $action, string $message): array { + return [ + PResponse::ACTION => $action, + PResponse::RESPONSE => PValues::ERROR, + PResponseErrorMessage::MESSAGE => $message, + ]; + } +} diff --git a/src/inc/agentapi/common/AgentResponseTrait.php b/src/inc/agentapi/common/AgentResponseTrait.php new file mode 100644 index 000000000..e23b55883 --- /dev/null +++ b/src/inc/agentapi/common/AgentResponseTrait.php @@ -0,0 +1,55 @@ + $fields + */ + private function success(Response $response, string $action, array $fields = []): ResponseInterface { + $envelope = AgentEnvelope::success($action, $fields); + $response = $response->withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode($envelope)); + return $response; + } + + /** + * Build and write an error envelope to the response. + */ + private function error(Response $response, string $action, string $message): ResponseInterface { + $envelope = AgentEnvelope::error($action, $message); + $response = $response->withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode($envelope)); + return $response; + } + + /** + * Update the agent's last-IP / last-action / last-time fields. + * @throws Exception + */ + private function updateAgent(Agent $agent, string $action): Agent { + return Factory::getAgentFactory()->mset($agent, [ + Agent::LAST_IP => Util::getIP(), + Agent::LAST_ACT => $action, + Agent::LAST_TIME => time(), + ]); + } +} diff --git a/src/inc/agentapi/error/AgentErrorHandler.php b/src/inc/agentapi/error/AgentErrorHandler.php new file mode 100644 index 000000000..f9cede803 --- /dev/null +++ b/src/inc/agentapi/error/AgentErrorHandler.php @@ -0,0 +1,47 @@ +withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode([ + PResponse::ACTION => 'INV', + PResponse::RESPONSE => PValues::ERROR, + PResponseErrorMessage::MESSAGE => $message, + ])); + return $response; + } + + /** + * Build an error envelope for a specific action string. + */ + public static function errorResponse(string $action, string $message): ResponseInterface { + $response = new Response(); + $response = $response->withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode([ + PResponse::ACTION => $action, + PResponse::RESPONSE => PValues::ERROR, + PResponseErrorMessage::MESSAGE => $message, + ])); + return $response; + } +} diff --git a/src/inc/agentapi/model/CheckClientVersionAction.php b/src/inc/agentapi/model/CheckClientVersionAction.php new file mode 100644 index 000000000..9116f5493 --- /dev/null +++ b/src/inc/agentapi/model/CheckClientVersionAction.php @@ -0,0 +1,73 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryCheckClientVersion::VERSION]) || !isset($body[PQueryCheckClientVersion::TYPE])) { + return $this->error($response, PActions::CHECK_CLIENT_VERSION, 'Invalid version check query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $version = $body[PQueryCheckClientVersion::VERSION]; + $type = $body[PQueryCheckClientVersion::TYPE]; + + $qF = new QueryFilter(AgentBinary::BINARY_TYPE, $type, '='); + $result = Factory::getAgentBinaryFactory()->filter([Factory::FILTER => $qF], true); + if ($result === null) { + DServerLog::log(DServerLog::WARNING, 'Agent ' . $agent->getId() . ' sent unknown client type: ' . $type); + return $this->error($response, PActions::CHECK_CLIENT_VERSION, 'Type not found!'); + } + + $agent = $this->updateAgent($agent, PActions::CHECK_CLIENT_VERSION); + if (Comparator::greaterThan($result->getVersion(), $version)) { + DServerLog::log(DServerLog::DEBUG, 'Agent ' . $agent->getId() . ' got notified about client update'); + return $this->success($response, PActions::CHECK_CLIENT_VERSION, [ + PResponseClientUpdate::VERSION => PValuesUpdateVersion::NEW_VERSION, + PResponseClientUpdate::URL => Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . '/agents.php?download=' . $result->getId(), + ]); + } + return $this->success($response, PActions::CHECK_CLIENT_VERSION, [ + PResponseClientUpdate::VERSION => PValuesUpdateVersion::UP_TO_DATE, + ]); + } +} diff --git a/src/inc/agentapi/model/ClientErrorAction.php b/src/inc/agentapi/model/ClientErrorAction.php new file mode 100644 index 000000000..7f4e25c4e --- /dev/null +++ b/src/inc/agentapi/model/ClientErrorAction.php @@ -0,0 +1,96 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryClientError::TASK_ID]) || !isset($body[PQueryClientError::MESSAGE])) { + return $this->error($response, PActions::CLIENT_ERROR, 'Invalid error query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $task = Factory::getTaskFactory()->get($body[PQueryClientError::TASK_ID]); + if ($task === null) { + DServerLog::log(DServerLog::WARNING, 'Agent ' . $agent->getId() . ' tried to send error for invalid task!'); + return $this->error($response, PActions::CLIENT_ERROR, 'Invalid task!'); + } + + $qF1 = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($assignment === null) { + DServerLog::log(DServerLog::WARNING, 'Agent ' . $agent->getId() . ' tried to send error for task he is not assigned to!'); + return $this->error($response, PActions::CLIENT_ERROR, 'Agent is not assigned to this task!'); + } + + DServerLog::log(DServerLog::INFO, 'Agent ' . $agent->getId() . ' sent error: ' . $body[PQueryClientError::MESSAGE]); + $whitelist = explode(',', SConfig::getInstance()->getVal(DConfig::HC_ERROR_IGNORE)); + foreach ($whitelist as $w) { + $w = trim($w); + if (str_contains($body[PQueryClientError::MESSAGE], $w)) { + return $this->success($response, PActions::CLIENT_ERROR); + } + } + + if ($agent->getIgnoreErrors() <= DAgentIgnoreErrors::IGNORE_SAVE) { + $chunkId = null; + if (isset($body[PQueryClientError::CHUNK_ID])) { + $chunkId = intval($body[PQueryClientError::CHUNK_ID]); + } + $error = new AgentError(null, $agent->getId(), $task->getId(), $chunkId, time(), $body[PQueryClientError::MESSAGE]); + Factory::getAgentErrorFactory()->save($error); + + $payload = new DataSet([DPayloadKeys::AGENT => $agent, DPayloadKeys::AGENT_ERROR => $body[PQueryClientError::MESSAGE]]); + NotificationHandler::checkNotifications(DNotificationType::AGENT_ERROR, $payload); + NotificationHandler::checkNotifications(DNotificationType::OWN_AGENT_ERROR, $payload); + } + + if ($agent->getIgnoreErrors() == DAgentIgnoreErrors::NO) { + $agent = Factory::getAgentFactory()->set($agent, Agent::IS_ACTIVE, 0); + } + + $this->updateAgent($agent, PActions::CLIENT_ERROR); + return $this->success($response, PActions::CLIENT_ERROR); + } +} diff --git a/src/inc/agentapi/model/DeregisterAction.php b/src/inc/agentapi/model/DeregisterAction.php new file mode 100644 index 000000000..9ddb89c18 --- /dev/null +++ b/src/inc/agentapi/model/DeregisterAction.php @@ -0,0 +1,55 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN])) { + return $this->error($response, PActions::DEREGISTER, 'Invalid de-registering query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + if (!SConfig::getInstance()->getVal(DConfig::ALLOW_DEREGISTER)) { + return $this->error($response, PActions::DEREGISTER, 'De-registration is not allowed on this server!'); + } + try { + AgentUtils::delete($agent->getId(), null); + } + catch (HTException $e) { + return $this->error($response, PActions::DEREGISTER, 'Error occured during de-registration: ' . $e->getMessage()); + } + return $this->success($response, PActions::DEREGISTER); + } +} diff --git a/src/inc/agentapi/model/DownloadBinaryAction.php b/src/inc/agentapi/model/DownloadBinaryAction.php new file mode 100644 index 000000000..7026effe6 --- /dev/null +++ b/src/inc/agentapi/model/DownloadBinaryAction.php @@ -0,0 +1,98 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryDownloadBinary::BINARY_TYPE])) { + return $this->error($response, PActions::DOWNLOAD_BINARY, 'Invalid download query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $agent = $this->updateAgent($agent, PActions::DOWNLOAD_BINARY); + + switch ($body[PQueryDownloadBinary::BINARY_TYPE]) { + case PValuesDownloadBinaryType::EXTRACTOR: + DServerLog::log(DServerLog::TRACE, 'Agent ' . $agent->getId() . ' downloaded 7zr binary'); + $filename = '7zr' . Util::getFileExtension($agent->getOs()); + $path = Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . '/static/' . $filename; + return $this->success($response, PActions::DOWNLOAD_BINARY, [ + PResponseBinaryDownload::EXECUTABLE => $path, + ]); + case PValuesDownloadBinaryType::UFTPD: + DServerLog::log(DServerLog::TRACE, 'Agent ' . $agent->getId() . ' downloaded uftpd binary'); + $filename = 'uftpd' . Util::getFileExtension($agent->getOs()); + $path = Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . '/static/' . $filename; + return $this->success($response, PActions::DOWNLOAD_BINARY, [ + PResponseBinaryDownload::EXECUTABLE => $path, + ]); + case PValuesDownloadBinaryType::CRACKER: + $crackerBinary = Factory::getCrackerBinaryFactory()->get($body[PQueryDownloadBinary::BINARY_VERSION_ID]); + if ($crackerBinary === null) { + return $this->error($response, PActions::DOWNLOAD_BINARY, 'Invalid cracker binary type id!'); + } + $crackerBinaryType = Factory::getCrackerBinaryTypeFactory()->get($crackerBinary->getCrackerBinaryTypeId()); + DServerLog::log(DServerLog::TRACE, 'Agent ' . $agent->getId() . ' downloaded cracker binary ' . $crackerBinary->getId()); + $ext = Util::getFileExtension($agent->getOs()); + return $this->success($response, PActions::DOWNLOAD_BINARY, [ + PResponseBinaryDownload::URL => $crackerBinary->getDownloadUrl(), + PResponseBinaryDownload::NAME => $crackerBinaryType->getTypeName(), + PResponseBinaryDownload::EXECUTABLE => $crackerBinary->getBinaryName() . $ext, + ]); + case PValuesDownloadBinaryType::PRINCE: + case PValuesDownloadBinaryType::PREPROCESSOR: + $preprocessor = Factory::getPreprocessorFactory()->get($body[PQueryDownloadBinary::PREPROCESSOR_ID]); + if ($preprocessor === null) { + return $this->error($response, PActions::DOWNLOAD_BINARY, 'Invalid preprocessor id!'); + } + DServerLog::log(DServerLog::TRACE, 'Agent ' . $agent->getId() . ' downloaded preprocessor ' . $preprocessor->getId()); + $ext = Util::getFileExtension($agent->getOs()); + return $this->success($response, PActions::DOWNLOAD_BINARY, [ + PResponseBinaryDownload::URL => $preprocessor->getUrl(), + PResponseBinaryDownload::NAME => $preprocessor->getName(), + PResponseBinaryDownload::EXECUTABLE => $preprocessor->getBinaryName() . $ext, + PResponseBinaryDownload::KEYSPACE_CMD => $preprocessor->getKeyspaceCommand(), + PResponseBinaryDownload::SKIP_CMD => $preprocessor->getSkipCommand(), + PResponseBinaryDownload::LIMIT_CMD => $preprocessor->getLimitCommand(), + ]); + default: + DServerLog::log(DServerLog::WARNING, 'Agent ' . $agent->getId() . ' requested invalid binary download: ' . $body[PQueryDownloadBinary::BINARY_TYPE]); + return $this->error($response, PActions::DOWNLOAD_BINARY, 'Unknown download type!'); + } + } +} diff --git a/src/inc/agentapi/model/GetChunkAction.php b/src/inc/agentapi/model/GetChunkAction.php new file mode 100644 index 000000000..ce598d6cf --- /dev/null +++ b/src/inc/agentapi/model/GetChunkAction.php @@ -0,0 +1,224 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryGetChunk::TASK_ID])) { + return $this->error($response, PActions::GET_CHUNK, 'Invalid chunk query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + $agent = $this->updateAgent($agent, PActions::GET_CHUNK); + + DServerLog::log(DServerLog::DEBUG, 'Requesting a chunk...', [$agent]); + + if (HealthUtils::checkNeeded($agent)) { + DServerLog::log(DServerLog::DEBUG, 'Notifying agent about health check', [$agent]); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::HEALTH_CHECK, + ]); + } + + $task = Factory::getTaskFactory()->get($body[PQueryGetChunk::TASK_ID]); + if ($task === null) { + DServerLog::log(DServerLog::WARNING, 'Requested chunk on invalid task!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Invalid task ID!'); + } + + $qF1 = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($assignment === null) { + DServerLog::log(DServerLog::WARNING, 'Requested chunk on task it is not assigned to!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'You are not assigned to this task!'); + } + + if ($task->getKeyspace() == 0) { + DServerLog::log(DServerLog::TRACE, 'Need to measure keyspace!', [$agent, $task]); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::KEYSPACE_REQUIRED, + ]); + } + + if ($assignment->getBenchmark() == 0 && $task->getIsSmall() == 0 && $task->getStaticChunks() == DTaskStaticChunking::NORMAL) { + DServerLog::log(DServerLog::TRACE, 'Need to run a benchmark!', [$agent, $task]); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::BENCHMARK_REQUIRED, + ]); + } + + if ($agent->getIsActive() == 0) { + DServerLog::log(DServerLog::TRACE, 'Agent is inactive!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Agent is inactive!'); + } + + $lockFile = Lock::CHUNKING . $task->getId(); + LockUtils::get($lockFile); + DServerLog::log(DServerLog::TRACE, 'Retrieved lock for chunking!', [$agent]); + + $task = Factory::getTaskFactory()->get($task->getId()); + Factory::getAgentFactory()->getDB()->beginTransaction(); + DServerLog::log(DServerLog::DEBUG, 'Checking task...', [$agent, $task]); + $task = TaskUtils::checkTask($task, $agent); + + if ($task === null) { + DServerLog::log(DServerLog::DEBUG, 'Task is fully dispatched', [$agent]); + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::FULLY_DISPATCHED, + ]); + } + + DServerLog::log(DServerLog::TRACE, 'Search for best task...', [$agent]); + $bestTask = TaskUtils::getBestTask($agent); + if ($bestTask === null) { + DServerLog::log(DServerLog::TRACE, 'No best task available! (Probably because permissions changed)', [$agent]); + if (!AccessUtils::agentCanAccessTask($agent, $task)) { + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::INFO, 'Not allowed to work on requested task', [$agent, $task]); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Not allowed to work on this task!'); + } + if (TaskUtils::isSaturatedByOtherAgents($task, $agent)) { + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Task already saturated by other agents, no other task available!'); + } + } + + if (TaskUtils::isSaturatedByOtherAgents($task, $agent)) { + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Task already saturated by other agents, other tasks available!'); + } + + DServerLog::log(DServerLog::TRACE, 'Determine important task', [$agent, $task, $bestTask]); + $bestTask = TaskUtils::getImportantTask($bestTask, $task); + + if ($bestTask->getId() != $task->getId()) { + Factory::getAgentFactory()->getDB()->commit(); + DServerLog::log(DServerLog::INFO, 'Task with higher priority available!', [$agent]); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->error($response, PActions::GET_CHUNK, 'Task with higher priority available!'); + } + + DServerLog::log(DServerLog::DEBUG, 'Searching existing chunk...', [$agent, $task]); + $qF1 = new QueryFilter(Chunk::PROGRESS, 10000, '<'); + $qF2 = new QueryFilter(Chunk::TASK_ID, $task->getId(), '='); + $oF = new OrderFilter(Chunk::SKIP, 'ASC'); + $chunks = Factory::getChunkFactory()->filter([Factory::FILTER => [$qF1, $qF2], Factory::ORDER => $oF]); + $qF1 = new QueryFilter(Chunk::PROGRESS, null, '='); + /** @var Chunk[] $chunks */ + $chunks = array_merge($chunks, Factory::getChunkFactory()->filter([Factory::FILTER => [$qF1, $qF2], Factory::ORDER => $oF])); + + foreach ($chunks as $chunk) { + if ($chunk->getAgentId() == $agent->getId()) { + DServerLog::log(DServerLog::DEBUG, 'Found chunk of same agent which is not done yet.', [$agent, $task, $chunk]); + $result = $this->sendChunk($response, ChunkUtils::handleExistingChunk($chunk, $task, $assignment)); + if ($result !== null) { + return $result; + } + } + $timeoutTime = time() - SConfig::getInstance()->getVal(DConfig::CHUNK_TIMEOUT); + if ($chunk->getState() == DHashcatStatus::ABORTED || $chunk->getState() == DHashcatStatus::STATUS_ABORTED_RUNTIME || max($chunk->getDispatchTime(), $chunk->getSolveTime()) < $timeoutTime) { + DServerLog::log(DServerLog::DEBUG, 'Found existing chunk which is not done yet', [$agent, $task, $chunk]); + $result = $this->sendChunk($response, ChunkUtils::handleExistingChunk($chunk, $task, $assignment)); + if ($result !== null) { + return $result; + } + } + } + + DServerLog::log(DServerLog::DEBUG, 'Create new chunk for agent', [$agent, $task]); + $chunk = ChunkUtils::createNewChunk($task, $assignment); + if ($chunk === null) { + DServerLog::log(DServerLog::DEBUG, 'Could not create a chunk, task is fully dispatched', [$agent, $task]); + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release($lockFile); + DServerLog::log(DServerLog::TRACE, 'Released lock for chunking!', [$agent]); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::FULLY_DISPATCHED, + ]); + } + + DServerLog::log(DServerLog::DEBUG, 'Sending new chunk to agent', [$agent, $task, $chunk]); + $result = $this->sendChunk($response, $chunk); + return $result ?? $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::FULLY_DISPATCHED, + ]); + } + + /** + * Commit the DB transaction, release the lock, and return the OK chunk + * response. Returns null if the chunk is null (caller should continue + * to the next chunk or create a new one). + * @throws Exception + */ + private function sendChunk(Response $response, ?Chunk $chunk): ?ResponseInterface { + if ($chunk === null) { + return null; + } + Factory::getAgentFactory()->getDB()->commit(); + LockUtils::release(Lock::CHUNKING . $chunk->getTaskId()); + return $this->success($response, PActions::GET_CHUNK, [ + PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::OK, + PResponseGetChunk::CHUNK_ID => (int)$chunk->getId(), + PResponseGetChunk::KEYSPACE_SKIP => (int)$chunk->getSkip(), + PResponseGetChunk::KEYSPACE_LENGTH => (int)$chunk->getLength(), + ]); + } +} diff --git a/src/inc/agentapi/model/GetFileAction.php b/src/inc/agentapi/model/GetFileAction.php new file mode 100644 index 000000000..518e73ca1 --- /dev/null +++ b/src/inc/agentapi/model/GetFileAction.php @@ -0,0 +1,91 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryGetFile::TASK_ID]) || !isset($body[PQueryGetFile::FILENAME])) { + return $this->error($response, PActions::GET_FILE, 'Invalid file query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + DServerLog::log(DServerLog::DEBUG, 'Requesting file ' . $body[PQueryGetFile::FILENAME], [$agent]); + + $task = Factory::getTaskFactory()->get($body[PQueryGetFile::TASK_ID]); + if ($task === null) { + return $this->error($response, PActions::GET_FILE, 'Invalid task!'); + } + + $qF1 = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + $qF2 = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($assignment === null) { + return $this->error($response, PActions::GET_FILE, 'Client is not assigned to this task!'); + } + + $file = $body[PQueryGetFile::FILENAME]; + $qF = new QueryFilter(File::FILENAME, $file, '='); + $file = Factory::getFileFactory()->filter([Factory::FILTER => $qF], true); + if ($file === null) { + return $this->error($response, PActions::GET_FILE, 'Invalid file!'); + } + + $qF1 = new QueryFilter(FileTask::TASK_ID, $task->getId(), '='); + $qF2 = new QueryFilter(FileTask::FILE_ID, $file->getId(), '='); + $taskFile = Factory::getFileTaskFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($taskFile === null) { + DServerLog::log(DServerLog::WARNING, 'Agent requested file not used in the task!', [$agent, $file, $task]); + return $this->error($response, PActions::GET_FILE, 'This file is not used for the specified task!'); + } + + if ($agent->getIsTrusted() < $file->getIsSecret()) { + return $this->error($response, PActions::GET_FILE, 'You have no access to get this file!'); + } + $filename = $file->getFilename(); + $extension = explode('.', $filename)[sizeof(explode('.', $filename)) - 1]; + + $agent = $this->updateAgent($agent, PActions::GET_FILE); + + return $this->success($response, PActions::GET_FILE, [ + PResponseGetFile::FILENAME => $filename, + PResponseGetFile::EXTENSION => $extension, + PResponseGetFile::URL => 'getFile.php?file=' . $file->getId() . '&token=' . $agent->getToken(), + PResponseGetFile::FILESIZE => (int)$file->getSize(), + ]); + } +} diff --git a/src/inc/agentapi/model/GetFileStatusAction.php b/src/inc/agentapi/model/GetFileStatusAction.php new file mode 100644 index 000000000..3c0448942 --- /dev/null +++ b/src/inc/agentapi/model/GetFileStatusAction.php @@ -0,0 +1,44 @@ +getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $deleteRequests = Factory::getFileDeleteFactory()->filter([]); + $files = []; + foreach ($deleteRequests as $deleteRequest) { + $files[] = $deleteRequest->getFilename(); + } + + $this->updateAgent($agent, PActions::GET_FILE_STATUS); + + return $this->success($response, PActions::GET_FILE_STATUS, [ + PResponseGetFileStatus::FILENAMES => $files, + ]); + } +} diff --git a/src/inc/agentapi/model/GetFoundAction.php b/src/inc/agentapi/model/GetFoundAction.php new file mode 100644 index 000000000..7fcc9e63e --- /dev/null +++ b/src/inc/agentapi/model/GetFoundAction.php @@ -0,0 +1,95 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryGetFound::HASHLIST_ID])) { + return $this->error($response, PActions::GET_FOUND, 'Invalid found query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $hashlist = Factory::getHashlistFactory()->get($body[PQueryGetFound::HASHLIST_ID]); + if ($hashlist === null) { + return $this->error($response, PActions::GET_FOUND, 'Invalid hashlist!'); + } + + DServerLog::log(DServerLog::DEBUG, 'Requesting founds for hashlist...', [$agent, $hashlist]); + + $qF = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); + if ($assignment === null) { + return $this->error($response, PActions::GET_FOUND, 'Agent is not assigned to a task!'); + } + + $task = Factory::getTaskFactory()->get($assignment->getTaskId()); + if ($task === null) { + DServerLog::log(DServerLog::WARNING, 'Assignment contained invalid task!', [$agent, $assignment]); + return $this->error($response, PActions::GET_FOUND, 'Assignment contains invalid task!'); + } + + $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); + if ($taskWrapper === null) { + DServerLog::log(DServerLog::FATAL, 'Inconsistency between taskWrapper and tasks!', [$agent, $task]); + return $this->error($response, PActions::GET_FOUND, 'Inconsistent taskWrapper for task!'); + } + + if ($taskWrapper->getHashlistId() != $hashlist->getId()) { + DServerLog::log(DServerLog::WARNING, 'Agent requested hashlist not used for task!', [$agent, $taskWrapper, $task, $hashlist]); + return $this->error($response, PActions::GET_FOUND, 'This hashlist is not used for the assigned task!'); + } + elseif ($agent->getIsTrusted() < $hashlist->getIsSecret()) { + return $this->error($response, PActions::GET_FOUND, 'You have not access to this hashlist!'); + } + + $hashlists = Util::checkSuperHashlist($hashlist); + foreach ($hashlists as $subHashlist) { + if ($subHashlist->getIsSecret() > $agent->getIsTrusted()) { + return $this->error($response, PActions::GET_FOUND, 'Agent would require to download secret hashlist with insufficient level!'); + } + } + + $agent = $this->updateAgent($agent, PActions::GET_FOUND); + + if (sizeof($hashlists) == 0) { + return $this->error($response, PActions::GET_FOUND, 'No hashlists selected/available!'); + } + return $this->success($response, PActions::GET_FOUND, [ + PResponseGetFound::URL => 'getFound.php?hashlists=' . implode(',', Util::arrayOfIds($hashlists)) . '&token=' . $agent->getToken(), + ]); + } +} diff --git a/src/inc/agentapi/model/GetHashlistAction.php b/src/inc/agentapi/model/GetHashlistAction.php new file mode 100644 index 000000000..dbf041c03 --- /dev/null +++ b/src/inc/agentapi/model/GetHashlistAction.php @@ -0,0 +1,95 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryGetHashlist::HASHLIST_ID])) { + return $this->error($response, PActions::GET_HASHLIST, 'Invalid hashlist query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $hashlist = Factory::getHashlistFactory()->get($body[PQueryGetHashlist::HASHLIST_ID]); + if ($hashlist === null) { + return $this->error($response, PActions::GET_HASHLIST, 'Invalid hashlist!'); + } + + DServerLog::log(DServerLog::DEBUG, 'Requesting a hashlist...', [$agent, $hashlist]); + + $qF = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); + if ($assignment === null) { + return $this->error($response, PActions::GET_HASHLIST, 'Agent is not assigned to a task!'); + } + + $task = Factory::getTaskFactory()->get($assignment->getTaskId()); + if ($task === null) { + DServerLog::log(DServerLog::WARNING, 'Assignment contained invalid task!', [$agent, $assignment]); + return $this->error($response, PActions::GET_HASHLIST, 'Assignment contains invalid task!'); + } + + $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); + if ($taskWrapper === null) { + DServerLog::log(DServerLog::FATAL, 'Inconsistency between taskWrapper and tasks!', [$agent, $task]); + return $this->error($response, PActions::GET_HASHLIST, 'Inconsistent taskWrapper for task!'); + } + + if ($taskWrapper->getHashlistId() != $hashlist->getId()) { + DServerLog::log(DServerLog::WARNING, 'Agent requested hashlist not used for task!', [$agent, $taskWrapper, $task, $hashlist]); + return $this->error($response, PActions::GET_HASHLIST, 'This hashlist is not used for the assigned task!'); + } + elseif ($agent->getIsTrusted() < $hashlist->getIsSecret()) { + return $this->error($response, PActions::GET_HASHLIST, 'You have not access to this hashlist!'); + } + + $hashlists = Util::checkSuperHashlist($hashlist); + foreach ($hashlists as $subHashlist) { + if ($subHashlist->getIsSecret() > $agent->getIsTrusted()) { + return $this->error($response, PActions::GET_HASHLIST, 'Agent would require to download secret hashlist with insufficient level!'); + } + } + + $agent = $this->updateAgent($agent, PActions::GET_HASHLIST); + + if (sizeof($hashlists) == 0) { + return $this->error($response, PActions::GET_HASHLIST, 'No hashlists selected/available!'); + } + return $this->success($response, PActions::GET_HASHLIST, [ + PResponseGetHashlist::URL => 'getHashlist.php?hashlists=' . implode(',', Util::arrayOfIds($hashlists)) . '&token=' . $agent->getToken(), + ]); + } +} diff --git a/src/inc/agentapi/model/GetHealthCheckAction.php b/src/inc/agentapi/model/GetHealthCheckAction.php new file mode 100644 index 000000000..99166601b --- /dev/null +++ b/src/inc/agentapi/model/GetHealthCheckAction.php @@ -0,0 +1,67 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN])) { + return $this->error($response, PActions::GET_HEALTH_CHECK, 'Invalid get health check query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $agent = $this->updateAgent($agent, PActions::GET_HEALTH_CHECK); + + $healthCheckAgent = HealthUtils::checkNeeded($agent); + if ($healthCheckAgent === false) { + return $this->error($response, PActions::GET_HEALTH_CHECK, 'No health check available for this agent!'); + } + $healthCheck = Factory::getHealthCheckFactory()->get($healthCheckAgent->getHealthCheckId()); + + DServerLog::log(DServerLog::INFO, 'Received health check task', [$agent, $healthCheck]); + + $hashes = file_get_contents('/tmp/health-check-' . $healthCheck->getId() . '.txt'); + $hashes = explode("\n", $hashes); + + return $this->success($response, PActions::GET_HEALTH_CHECK, [ + PResponseGetHealthCheck::ATTACK => ' --hash-type=' . $healthCheck->getHashtypeId() . ' ' . $healthCheck->getAttackCmd() . ' ' . $agent->getCmdPars(), + PResponseGetHealthCheck::CRACKER_BINARY_ID => (int)$healthCheck->getCrackerBinaryId(), + PResponseGetHealthCheck::HASHES => $hashes, + PResponseGetHealthCheck::CHECK_ID => (int)$healthCheck->getId(), + PResponseGetHealthCheck::HASHLIST_ALIAS => SConfig::getInstance()->getVal(DConfig::HASHLIST_ALIAS), + ]); + } +} diff --git a/src/inc/agentapi/model/GetTaskAction.php b/src/inc/agentapi/model/GetTaskAction.php new file mode 100644 index 000000000..e0ec522a7 --- /dev/null +++ b/src/inc/agentapi/model/GetTaskAction.php @@ -0,0 +1,208 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN])) { + return $this->error($response, PActions::GET_TASK, 'Invalid task query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + $agent = $this->updateAgent($agent, PActions::GET_TASK); + + DServerLog::log(DServerLog::TRACE, 'Requesting a task...', [$agent]); + + if (HealthUtils::checkNeeded($agent)) { + DServerLog::log(DServerLog::INFO, 'Notified about pending health check', [$agent]); + return $this->success($response, PActions::GET_TASK, [ + PResponseGetTask::TASK_ID => PValuesTask::HEALTH_CHECK, + ]); + } + + if ($agent->getIsActive() == 0) { + DServerLog::log(DServerLog::TRACE, 'Agent is inactive and cannot get a task', [$agent]); + return $this->success($response, PActions::GET_TASK, [ + PResponseGetTask::TASK_ID => PValues::NONE, + PResponseGetTask::REASON => 'Agent is inactive!', + ]); + } + + DServerLog::log(DServerLog::TRACE, 'Searching for assignment and best task', [$agent]); + $qF = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); + $task = TaskUtils::getBestTask($agent); + DServerLog::log(DServerLog::TRACE, 'Search results', [$agent, $assignment, $task]); + + if ($task === null) { + if ($assignment === null) { + return $this->noTask($response); + } + $currentTask = Factory::getTaskFactory()->get($assignment->getTaskId()); + $currentTask = TaskUtils::checkTask($currentTask); + if ($currentTask === null) { + DServerLog::log(DServerLog::TRACE, 'No best task available and current assigned task is fullfilled', [$agent]); + Factory::getAssignmentFactory()->delete($assignment); + return $this->noTask($response); + } + if (TaskUtils::isSaturatedByOtherAgents($currentTask, $agent)) { + Factory::getAssignmentFactory()->delete($assignment); + return $this->noTask($response); + } + DServerLog::log(DServerLog::TRACE, 'Current task is running, continue with this', [$agent, $currentTask]); + return $this->sendTask($response, $agent, $currentTask, $assignment); + } + + if ($assignment !== null) { + $currentTask = Factory::getTaskFactory()->get($assignment->getTaskId()); + if ($currentTask === null) { + DServerLog::log(DServerLog::TRACE, 'Current task does not exist anymore, send new one', [$agent, $task]); + return $this->sendTask($response, $agent, $task, $assignment); + } + $currentTask = TaskUtils::checkTask($currentTask); + if ($currentTask === null) { + DServerLog::log(DServerLog::TRACE, 'Current task is done or permissions changed, send new one', [$agent, $task]); + return $this->sendTask($response, $agent, $task, $assignment); + } + if (TaskUtils::isSaturatedByOtherAgents($currentTask, $agent)) { + return $this->sendTask($response, $agent, $task, $assignment); + } + DServerLog::log(DServerLog::TRACE, 'Current task is fine, send the more important one', [$agent, $currentTask, $task]); + return $this->sendTask($response, $agent, TaskUtils::getImportantTask($task, $currentTask), $assignment); + } + + DServerLog::log(DServerLog::TRACE, 'Task available, but nothing assigned, send new one', [$agent, $task]); + return $this->sendTask($response, $agent, $task, $assignment); + } + + private function noTask(Response $response): ResponseInterface { + return $this->success($response, PActions::GET_TASK, [ + PResponseGetTask::TASK_ID => PValues::NONE, + PResponseGetTask::REASON => 'No suitable task available!', + ]); + } + + /** + * @throws Exception + */ + private function sendTask(Response $response, Agent $agent, Task $task, ?Assignment $assignment): ResponseInterface { + if ($assignment === null) { + $assignment = new Assignment(null, $task->getId(), $agent->getId(), '0'); + $assignment = Factory::getAssignmentFactory()->save($assignment); + DServerLog::log(DServerLog::TRACE, 'No assignment present, created', [$agent, $assignment]); + } + else { + if ($assignment->getTaskId() != $task->getId()) { + $qF = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); + DServerLog::log(DServerLog::TRACE, 'Current task does not match assignment, delete it', [$agent, $assignment]); + $assignment = new Assignment(null, $task->getId(), $agent->getId(), '0'); + $assignment = Factory::getAssignmentFactory()->save($assignment); + DServerLog::log(DServerLog::TRACE, 'Created new assignment', [$agent, $assignment]); + } + } + + $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); + if ($taskWrapper === null) { + DServerLog::log(DServerLog::FATAL, 'Inconsistency between taskWrapper and task', [$agent, $task]); + return $this->error($response, PActions::GET_TASK, 'Inconsistent TaskWrapper information!'); + } + $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); + if ($hashlist === null) { + DServerLog::log(DServerLog::TRACE, 'Inconsistency between taskWrapper and hashlist', [$agent, $taskWrapper]); + return $this->error($response, PActions::GET_TASK, 'Inconsistent TaskWrapper-Hashlist information'); + } + + $taskFiles = []; + $qF = new QueryFilter(FileTask::TASK_ID, $task->getId(), '=', Factory::getFileTaskFactory()); + $jF = new JoinFilter(Factory::getFileTaskFactory(), File::FILE_ID, FileTask::FILE_ID); + $joined = Factory::getFileFactory()->filter([Factory::FILTER => $qF, Factory::JOIN => $jF]); + /** @var File[] $files */ + $files = $joined[Factory::getFileFactory()->getModelName()]; + foreach ($files as $file) { + $taskFiles[] = $file->getFilename(); + } + + $hashtype = Factory::getHashTypeFactory()->get($hashlist->getHashTypeId()); + + DServerLog::log(DServerLog::TRACE, 'Sending task to agent', [$agent, $task, $taskFiles]); + + $brain = $hashlist->getBrainId() && !$task->getForcePipe() && !$task->getUsePreprocessor(); + + $fields = [ + PResponseGetTask::TASK_ID => (int)$task->getId(), + PResponseGetTask::ATTACK_COMMAND => $task->getAttackCmd(), + PResponseGetTask::CMD_PARAMETERS => ' --hash-type=' . $hashlist->getHashTypeId() . ' ' . $agent->getCmdPars(), + PResponseGetTask::HASHLIST_ID => (int)$taskWrapper->getHashlistId(), + PResponseGetTask::BENCHMARK => (int)SConfig::getInstance()->getVal(DConfig::BENCHMARK_TIME), + PResponseGetTask::STATUS_TIMER => (int)$task->getStatusTimer(), + PResponseGetTask::FILES => $taskFiles, + PResponseGetTask::CRACKER_ID => $task->getCrackerBinaryId(), + PResponseGetTask::BENCHTYPE => ($task->getUseNewBench() == 1) ? 'speed' : 'run', + PResponseGetTask::HASHLIST_ALIAS => SConfig::getInstance()->getVal(DConfig::HASHLIST_ALIAS), + PResponseGetTask::KEYSPACE => $task->getKeyspace(), + PResponseGetTask::USE_PREPROCESSOR => (bool)$task->getUsePreprocessor(), + PResponseGetTask::PREPROCESSOR => $task->getUsePreprocessor(), + PResponseGetTask::PREPROCESSOR_COMMAND => $task->getPreprocessorCommand(), + PResponseGetTask::ENFORCE_PIPE => (bool)$task->getForcePipe(), + PResponseGetTask::SLOW_HASH => (bool)$hashtype->getIsSlowHash(), + PResponseGetTask::USE_BRAIN => $brain, + ]; + + if ($brain) { + $fields[PResponseGetTask::BRAIN_HOST] = SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_HOST); + $fields[PResponseGetTask::BRAIN_PORT] = intval(SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_PORT)); + $fields[PResponseGetTask::BRAIN_PASS] = SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_PASS); + $fields[PResponseGetTask::BRAIN_FEATURES] = (int)$hashlist->getBrainFeatures(); + } + + return $this->success($response, PActions::GET_TASK, $fields); + } +} diff --git a/src/inc/agentapi/model/LoginAction.php b/src/inc/agentapi/model/LoginAction.php new file mode 100644 index 000000000..afb672d0c --- /dev/null +++ b/src/inc/agentapi/model/LoginAction.php @@ -0,0 +1,64 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryLogin::CLIENT_SIGNATURE])) { + return $this->error($response, PActions::LOGIN, 'Invalid login query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $agent = Factory::getAgentFactory()->set( + $agent, + Agent::CLIENT_SIGNATURE, + htmlentities($body[PQueryLogin::CLIENT_SIGNATURE], ENT_QUOTES, 'UTF-8'), + ); + $agent = $this->updateAgent($agent, PActions::LOGIN); + + DServerLog::log(DServerLog::DEBUG, 'Agent logged in', [$agent]); + + return $this->success($response, PActions::LOGIN, [ + PResponseLogin::MULTICAST => (bool)SConfig::getInstance()->getVal(DConfig::MULTICAST_ENABLE), + PResponseLogin::TIMEOUT => (int)SConfig::getInstance()->getVal(DConfig::AGENT_TIMEOUT), + PResponseLogin::VERSION => StartupConfig::getInstance()->getVersion() . ' (' . Util::getGitCommit() . ')', + ]); + } +} diff --git a/src/inc/agentapi/model/RegisterAgentAction.php b/src/inc/agentapi/model/RegisterAgentAction.php new file mode 100644 index 000000000..4e0204dee --- /dev/null +++ b/src/inc/agentapi/model/RegisterAgentAction.php @@ -0,0 +1,98 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQueryRegister::VOUCHER]) || !isset($body[PQueryRegister::AGENT_NAME])) { + return $this->error($response, PActions::REGISTER, 'Invalid registering query!'); + } + + $qF = new QueryFilter(RegVoucher::VOUCHER, $body[PQueryRegister::VOUCHER], '='); + $voucher = Factory::getRegVoucherFactory()->filter([Factory::FILTER => $qF], true); + if ($voucher === null) { + return $this->error($response, PActions::REGISTER, 'Provided voucher does not exist.'); + } + + $name = htmlentities($body[PQueryRegister::AGENT_NAME], ENT_QUOTES, 'UTF-8'); + + $cpuOnly = 0; + if (isset($body[PQueryRegister::CPU_ONLY]) && $body[PQueryRegister::CPU_ONLY]) { + $cpuOnly = 1; + } + + $token = Util::randomString(10); + $agent = new Agent(null, $name, '', -1, '', '', 0, 1, 0, $token, PActions::REGISTER, time(), Util::getIP(), null, $cpuOnly, ''); + + if (SConfig::getInstance()->getVal(DConfig::VOUCHER_DELETION) == 0) { + Factory::getRegVoucherFactory()->delete($voucher); + } + + try { + $agent = Factory::getAgentFactory()->save($agent); + } catch (Exception $e) { + DServerLog::log(DServerLog::ERROR, 'Saving of agent failed!', [$agent]); + return $this->error($response, PActions::REGISTER, 'Could not register you to server: Saving failed!'); + } + + if ($agent === null) { + DServerLog::log(DServerLog::ERROR, 'Saving of agent failed!', [$agent]); + return $this->error($response, PActions::REGISTER, 'Could not register you to server: Saving failed!'); + } + + $payload = new DataSet([DPayloadKeys::AGENT => $agent]); + NotificationHandler::checkNotifications(DNotificationType::NEW_AGENT, $payload); + DServerLog::log(DServerLog::INFO, 'Registered new agent', [$agent]); + + $accessGroup = AccessUtils::getOrCreateDefaultAccessGroup(); + $accessGroupAgent = new AccessGroupAgent(null, $accessGroup->getId(), $agent->getId()); + Factory::getAccessGroupAgentFactory()->save($accessGroupAgent); + DServerLog::log(DServerLog::INFO, 'Assigned agent to access group', [$agent, $accessGroup]); + + return $this->success($response, PActions::REGISTER, [ + PResponseRegister::TOKEN => $token, + ]); + } +} diff --git a/src/inc/agentapi/model/SendBenchmarkAction.php b/src/inc/agentapi/model/SendBenchmarkAction.php new file mode 100644 index 000000000..d23629850 --- /dev/null +++ b/src/inc/agentapi/model/SendBenchmarkAction.php @@ -0,0 +1,100 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQuerySendBenchmark::TASK_ID]) || !isset($body[PQuerySendBenchmark::TYPE]) || !isset($body[PQuerySendBenchmark::RESULT])) { + return $this->error($response, PActions::SEND_BENCHMARK, 'Invalid benchmark query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + $agent = $this->updateAgent($agent, PActions::SEND_BENCHMARK); + + $task = Factory::getTaskFactory()->get($body[PQuerySendBenchmark::TASK_ID]); + if ($task === null) { + return $this->error($response, PActions::SEND_BENCHMARK, 'Invalid task ID!'); + } + + $qF1 = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($assignment === null) { + return $this->error($response, PActions::SEND_BENCHMARK, 'You are not assigned to this task!'); + } + + $type = $body[PQuerySendBenchmark::TYPE]; + $benchmark = $body[PQuerySendBenchmark::RESULT]; + + DServerLog::log(DServerLog::TRACE, 'Agent sending benchmark', [$agent, $task, $type, $benchmark]); + + switch ($type) { + case PValuesBenchmarkType::SPEED_TEST: + $split = explode(':', (string)$benchmark); + if (sizeof($split) != 2 || !is_numeric($split[0]) || !is_numeric($split[1]) || $split[0] <= 0 || $split[1] <= 0) { + Factory::getAgentFactory()->set($agent, Agent::IS_ACTIVE, 0); + DServerLog::log(DServerLog::ERROR, 'Invalid speed test benchmark result!', [$agent, $benchmark]); + return $this->error($response, PActions::SEND_BENCHMARK, 'Invalid benchmark result!'); + } + break; + case PValuesBenchmarkType::RUN_TIME: + if (!is_numeric($benchmark) || $benchmark <= 0) { + Factory::getAgentFactory()->set($agent, Agent::IS_ACTIVE, 0); + DServerLog::log(DServerLog::ERROR, 'Invalid benchmark results for runtime benchmark', [$agent, $task, $benchmark]); + return $this->error($response, PActions::SEND_BENCHMARK, 'Invalid benchmark result!'); + } + $benchmark = $benchmark / SConfig::getInstance()->getVal(DConfig::BENCHMARK_TIME) * 100; + DServerLog::log(DServerLog::TRACE, 'Saving normalized runtime benchmark', [$agent, $task, $benchmark]); + break; + default: + Factory::getAgentFactory()->set($agent, Agent::IS_ACTIVE, 0); + return $this->error($response, PActions::SEND_BENCHMARK, 'Invalid benchmark type!'); + } + + $assignment->setBenchmark((string)$benchmark); + Factory::getAssignmentFactory()->update($assignment); + DServerLog::log(DServerLog::DEBUG, 'Saved agent benchmark', [$agent, $task, $assignment]); + + return $this->success($response, PActions::SEND_BENCHMARK, [ + PResponseSendBenchmark::BENCHMARK => PValues::OK, + ]); + } +} diff --git a/src/inc/agentapi/model/SendHealthCheckAction.php b/src/inc/agentapi/model/SendHealthCheckAction.php new file mode 100644 index 000000000..796b7c524 --- /dev/null +++ b/src/inc/agentapi/model/SendHealthCheckAction.php @@ -0,0 +1,92 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQuerySendHealthCheck::CHECK_ID]) || !isset($body[PQuerySendHealthCheck::NUM_CRACKED]) || !isset($body[PQuerySendHealthCheck::START]) || !isset($body[PQuerySendHealthCheck::END]) || !isset($body[PQuerySendHealthCheck::NUM_GPUS]) || !isset($body[PQuerySendHealthCheck::ERRORS])) { + return $this->error($response, PActions::SEND_HEALTH_CHECK, 'Invalid send health check query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $agent = $this->updateAgent($agent, PActions::SEND_HEALTH_CHECK); + + $healthCheck = Factory::getHealthCheckFactory()->get($body[PQuerySendHealthCheck::CHECK_ID]); + if ($healthCheck === null) { + return $this->error($response, PActions::SEND_HEALTH_CHECK, 'Invalid health check id!'); + } + $qF1 = new QueryFilter(HealthCheckAgent::HEALTH_CHECK_ID, $healthCheck->getId(), '='); + $qF2 = new QueryFilter(HealthCheckAgent::AGENT_ID, $agent->getId(), '='); + $healthCheckAgent = Factory::getHealthCheckAgentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($healthCheckAgent === null) { + return $this->error($response, PActions::SEND_HEALTH_CHECK, 'Invalid health check agent id!'); + } + + $numCracked = intval($body[PQuerySendHealthCheck::NUM_CRACKED]); + $numGpus = intval($body[PQuerySendHealthCheck::NUM_GPUS]); + $errors = $body[PQuerySendHealthCheck::ERRORS]; + $start = intval($body[PQuerySendHealthCheck::START]); + $end = intval($body[PQuerySendHealthCheck::END]); + + if (!is_array($errors)) { + $errors = [$errors]; + } + + $status = DHealthCheckAgentStatus::COMPLETED; + if (sizeof($errors) > 0 && $agent->getIgnoreErrors() == DAgentIgnoreErrors::NO) { + $status = DHealthCheckAgentStatus::FAILED; + } + elseif ($numCracked != $healthCheck->getExpectedCracks()) { + $status = DHealthCheckAgentStatus::FAILED; + } + + $healthCheckAgent->setCracked($numCracked); + $healthCheckAgent->setNumGpus($numGpus); + $healthCheckAgent->setErrors(json_encode($errors)); + $healthCheckAgent->setStart($start); + $healthCheckAgent->setEnd($end); + $healthCheckAgent->setStatus($status); + Factory::getHealthCheckAgentFactory()->update($healthCheckAgent); + + DServerLog::log(DServerLog::DEBUG, 'Agent sent health check results', [$agent, $healthCheck, $healthCheckAgent]); + + HealthUtils::checkCompletion($healthCheck); + return $this->success($response, PActions::SEND_HEALTH_CHECK); + } +} diff --git a/src/inc/agentapi/model/SendKeyspaceAction.php b/src/inc/agentapi/model/SendKeyspaceAction.php new file mode 100644 index 000000000..9e86e1382 --- /dev/null +++ b/src/inc/agentapi/model/SendKeyspaceAction.php @@ -0,0 +1,97 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQuerySendKeyspace::KEYSPACE]) || !isset($body[PQuerySendKeyspace::TASK_ID])) { + return $this->error($response, PActions::SEND_KEYSPACE, 'Invalid keyspace query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + $agent = $this->updateAgent($agent, PActions::SEND_KEYSPACE); + + $keyspace = intval($body[PQuerySendKeyspace::KEYSPACE]); + + $task = Factory::getTaskFactory()->get($body[PQuerySendKeyspace::TASK_ID]); + if ($task === null) { + return $this->error($response, PActions::SEND_KEYSPACE, 'Invalid task ID!'); + } + + DServerLog::log(DServerLog::TRACE, 'Agent sending keyspace...', [$agent, $task]); + + $qF1 = new QueryFilter(Assignment::AGENT_ID, $agent->getId(), '='); + $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); + if ($assignment === null) { + DServerLog::log(DServerLog::TRACE, 'Agent not assigned to task to send keyspace', [$agent]); + return $this->error($response, PActions::SEND_KEYSPACE, 'You are not assigned to this task!'); + } + + if ($task->getKeyspace() == 0) { + if ($task->getUsePreprocessor() && $keyspace == -1) { + DServerLog::log(DServerLog::TRACE, 'Keyspace is too large to save, we set it to a specific number', [$agent]); + $keyspace = DPrince::PRINCE_KEYSPACE; + } + else if ($keyspace < 0) { + DServerLog::log(DServerLog::WARNING, 'Keyspace is negative, most likely due to 32bit server', [$agent, $keyspace]); + return $this->error($response, PActions::SEND_KEYSPACE, "Server parsed a negative keyspace, it's very likely that the number was too big to be handled by the server system!"); + } + + $task = Factory::getTaskFactory()->set($task, Task::KEYSPACE, $keyspace); + DServerLog::log(DServerLog::TRACE, 'Keyspace saved', [$agent, $task]); + } + + if ($task->getSkipKeyspace() > $task->getKeyspace() && $task->getKeyspace() != DPrince::PRINCE_KEYSPACE) { + DServerLog::log(DServerLog::ERROR, 'Task skip value is too high, putting task inactive!', [$agent, $task]); + $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); + $qF = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); + Util::createLogEntry(DLogEntryIssuer::API, $agent->getToken(), DLogEntry::ERROR, 'Task with ID ' . $task->getId() . ' has set a skip value which is too high for its keyspace!'); + } + + return $this->success($response, PActions::SEND_KEYSPACE, [ + PResponseSendKeyspace::KEYSPACE => PValues::OK, + ]); + } +} diff --git a/src/inc/agentapi/model/SendProgressAction.php b/src/inc/agentapi/model/SendProgressAction.php new file mode 100644 index 000000000..7fbd330c0 --- /dev/null +++ b/src/inc/agentapi/model/SendProgressAction.php @@ -0,0 +1,601 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQuerySendProgress::CHUNK_ID]) || !isset($body[PQuerySendProgress::KEYSPACE_PROGRESS]) || !isset($body[PQuerySendProgress::RELATIVE_PROGRESS]) || !isset($body[PQuerySendProgress::SPEED]) || !isset($body[PQuerySendProgress::HASHCAT_STATE]) || !isset($body[PQuerySendProgress::CRACKED_HASHES])) { + return $this->error($response, PActions::SEND_PROGRESS, 'Invalid progress query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + $agent = $this->updateAgent($agent, PActions::SEND_PROGRESS); + + $keyspaceProgress = $body[PQuerySendProgress::KEYSPACE_PROGRESS]; + $relativeProgress = intval($body[PQuerySendProgress::RELATIVE_PROGRESS]); + $speed = intval($body[PQuerySendProgress::SPEED]); + $state = intval($body[PQuerySendProgress::HASHCAT_STATE]); + + DServerLog::log(DServerLog::TRACE, 'Agent sending progress', [$agent]); + + $chunk = Factory::getChunkFactory()->get(intval($body[PQuerySendProgress::CHUNK_ID])); + if ($chunk === null) { + return $this->error($response, PActions::SEND_PROGRESS, 'Invalid chunk id ' . intval($body[PQuerySendProgress::CHUNK_ID])); + } + if ($chunk->getAgentId() != $agent->getId()) { + return $this->error($response, PActions::SEND_PROGRESS, 'You are not assigned to this chunk'); + } + if ($agent->getIsActive() == 0) { + Factory::getChunkFactory()->set($chunk, Chunk::SPEED, 0); + return $this->error($response, PActions::SEND_PROGRESS, 'Agent is marked inactive!'); + } + + DServerLog::log(DServerLog::TRACE, 'Agent is assigned to this chunk and active', [$agent, $chunk]); + + $task = Factory::getTaskFactory()->get($chunk->getTaskId()); + if ($task === null) { + DServerLog::log(DServerLog::ERROR, 'Inconsistency between chunk and task!', [$agent, $chunk]); + return $this->error($response, PActions::SEND_PROGRESS, 'No task exists for the given chunk'); + } + if ($task->getIsArchived() == 1) { + return $this->error($response, PActions::SEND_PROGRESS, 'Task is archived, no work to do'); + } + $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); + if ($taskWrapper === null) { + DServerLog::log(DServerLog::ERROR, 'Inconsistency between task and taskWrapper!', [$agent, $task]); + return $this->error($response, PActions::SEND_PROGRESS, 'Inconsistency error on taskWrapper'); + } + + DServerLog::log(DServerLog::TRACE, 'Agent working on valid task', [$agent, $task]); + + $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); + if ($hashlist === null) { + DServerLog::log(DServerLog::ERROR, 'Task is not having a valid hashlist!', [$agent, $task]); + return $this->error($response, PActions::SEND_PROGRESS, 'The given task does not have a corresponding hashlist!'); + } + $totalHashlist = $hashlist; + $isSuperhashlist = false; + if ($hashlist->getFormat() == DHashlistFormat::SUPERHASHLIST) { + $isSuperhashlist = true; + } + $hashlists = Util::checkSuperHashlist($hashlist); + foreach ($hashlists as $hl) { + if ($hl->getIsSecret() > $agent->getIsTrusted()) { + DServerLog::log(DServerLog::TRACE, 'For some reason agent was working on a hashlist he is not allowed to (probabily permission change)', [$agent, $task, $hl]); + return $this->error($response, PActions::SEND_PROGRESS, 'Unknown Error. The API does not trust you with more information'); + } + } + + DServerLog::log(DServerLog::TRACE, 'Agent working on correct hashlist(s)', [$agent, $totalHashlist]); + + $dataTime = time(); + $this->saveAgentStats($body, $agent, $dataTime); + + $skip = $chunk->getSkip(); + $length = $chunk->getLength(); + $taskID = $task->getId(); + + if ($relativeProgress > 10000) { + $relativeProgress = 10000; + } + if ($keyspaceProgress > $length + $skip) { + $keyspaceProgress = $length + $skip; + } + + $this->saveDebugOutput($body, $chunk); + + $aborting = false; + if ($chunk->getState() == DHashcatStatus::ABORTED) { + DServerLog::log(DServerLog::TRACE, 'Chunk was aborted, we need to stop afterwards', [$agent]); + $aborting = true; + } + $chunk = Factory::getChunkFactory()->mset($chunk, [ + Chunk::PROGRESS => $relativeProgress, + Chunk::CHECKPOINT => $keyspaceProgress, + Chunk::SOLVE_TIME => time(), + Chunk::STATE => $state, + ]); + DServerLog::log(DServerLog::TRACE, 'Progress updated chunk', [$agent, $chunk]); + + $format = $hashlists[0]->getFormat(); + + $skipped = 0; + $cracked = []; + foreach ($hashlists as $hl) { + $cracked[$hl->getId()] = 0; + } + + $crackedHashes = $body[PQuerySendProgress::CRACKED_HASHES]; + Factory::getAgentFactory()->getDB()->beginTransaction(); + + $plainUpdates = []; + $crackPosUpdates = []; + $crackHashes = []; + $timeUpdates = []; + $zaps = []; + + $isNewWPA = false; + foreach ($hashlists as $hl) { + if ($hl->getHashTypeId() == 22000) { + $isNewWPA = true; + break; + } + } + + for ($i = 0; $i < sizeof($crackedHashes); $i++) { + $crackedHash = $crackedHashes[$i]; + if (!is_array($crackedHash) && $crackedHash == '') { + continue; + } + else if (!is_array($crackedHash)) { + $splitLine = explode(SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR), $crackedHash); + $splitLine[] = ''; + $splitLine[] = -1; + } + else { + $splitLine = $crackedHash; + } + + switch ($format) { + case DHashlistFormat::PLAIN: + $result = $this->processPlainCrack($splitLine, $hashlists, $chunk, $agent, $totalHashlist, $isNewWPA, $cracked); + if ($result === 'skip') { + $skipped++; + } + else { + $plainUpdates = array_merge($plainUpdates, $result['plainUpdates']); + $crackPosUpdates = array_merge($crackPosUpdates, $result['crackPosUpdates']); + $timeUpdates = array_merge($timeUpdates, $result['timeUpdates']); + $crackHashes = array_merge($crackHashes, $result['crackHashes']); + $zaps = array_merge($zaps, $result['zaps']); + } + if (sizeof($plainUpdates) >= 1000) { + $this->flushPlainBatch($plainUpdates, $crackPosUpdates, $timeUpdates, $crackHashes, $zaps, $chunk); + $zaps = []; + $plainUpdates = []; + $crackHashes = []; + } + break; + + case DHashlistFormat::WPA: + $skipped += $this->processWPACrack($splitLine, $chunk, $totalHashlist, $cracked); + break; + + case DHashlistFormat::BINARY: + $skipped += $this->processBinaryCrack($splitLine, $chunk, $totalHashlist, $cracked); + break; + } + } + + if ($format == DHashlistFormat::PLAIN && sizeof($plainUpdates) > 0) { + $this->flushPlainBatch($plainUpdates, $crackPosUpdates, $timeUpdates, $crackHashes, $zaps, $chunk); + } + + Factory::getAgentFactory()->getDB()->commit(); + + Factory::getAgentFactory()->getDB()->beginTransaction(); + $sumCracked = 0; + foreach ($cracked as $listId => $cracks) { + $list = Factory::getHashlistFactory()->get($listId); + if ($cracks > 0) { + Factory::getHashlistFactory()->inc($list, Hashlist::CRACKED, $cracks); + } + if (!$isSuperhashlist) { + $superHashlists = Util::getParentSuperHashlists($list); + foreach ($superHashlists as $superHashlist) { + if ($cracks > 0) { + Factory::getHashlistFactory()->inc($superHashlist, Hashlist::CRACKED, $cracks); + } + } + } + $sumCracked += $cracks; + } + if ($sumCracked > 0) { + Factory::getChunkFactory()->inc($chunk, Chunk::CRACKED, $sumCracked); + } + if ($isSuperhashlist && $sumCracked > 0) { + $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); + Factory::getHashlistFactory()->inc($hashlist, Hashlist::CRACKED, $sumCracked); + } + Factory::getAgentFactory()->getDB()->commit(); + + DServerLog::log(DServerLog::TRACE, 'Updated with received cracks', [$agent, $chunk]); + + if ($chunk->getState() == DHashcatStatus::STATUS_ABORTED_RUNTIME) { + $chunk = Factory::getChunkFactory()->set($chunk, Chunk::STATE, DHashcatStatus::ABORTED); + DServerLog::log(DServerLog::TRACE, 'Chunk was manually interrupted', [$agent]); + return $this->error($response, PActions::SEND_PROGRESS, 'Chunk was manually interrupted.'); + } + + $taskdone = false; + if ($relativeProgress == 10000 && $task->getKeyspaceProgress() == $task->getKeyspace()) { + $incompleteFilter = new QueryFilter(Chunk::PROGRESS, 10000, '<'); + $taskFilter = new QueryFilter(Chunk::TASK_ID, $taskID, '='); + $count = Factory::getChunkFactory()->countFilter([Factory::FILTER => [$incompleteFilter, $taskFilter]]); + $incompleteFilter = new QueryFilter(Chunk::PROGRESS, null, '='); + $count += Factory::getChunkFactory()->countFilter([Factory::FILTER => [$incompleteFilter, $taskFilter]]); + if ($count == 0) { + $taskdone = true; + DServerLog::log(DServerLog::INFO, 'Chunk is the last one and is completed and keyspace is reached', [$agent, $task, $chunk]); + } + } + + if ($taskdone) { + $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); + if ($taskWrapper->getTaskType() == DTaskTypes::SUPERTASK) { + if (Util::checkTaskWrapperCompleted($taskWrapper)) { + $taskWrapper = Factory::getTaskWrapperFactory()->set($taskWrapper, TaskWrapper::PRIORITY, 0); + } + } + else { + $taskWrapper = Factory::getTaskWrapperFactory()->set($taskWrapper, TaskWrapper::PRIORITY, 0); + } + DServerLog::log(DServerLog::TRACE, 'As task is done, finished it and updated taskWrapper', [$agent, $task, $taskWrapper]); + $payload = new DataSet([DPayloadKeys::TASK => $task]); + NotificationHandler::checkNotifications(DNotificationType::TASK_COMPLETE, $payload); + } + + $toZap = []; + + if ($sumCracked > 0) { + $payload = new DataSet([DPayloadKeys::NUM_CRACKED => $sumCracked, DPayloadKeys::AGENT => $agent, DPayloadKeys::TASK => $task, DPayloadKeys::HASHLIST => $totalHashlist]); + NotificationHandler::checkNotifications(DNotificationType::HASHLIST_CRACKED_HASH, $payload); + Factory::getTaskWrapperFactory()->inc($taskWrapper, TaskWrapper::CRACKED, $sumCracked); + } + + if ($aborting) { + $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::SPEED => 0, Chunk::STATE => DHashcatStatus::ABORTED]); + DServerLog::log(DServerLog::TRACE, 'From earlier setting, chunk needed to be aborted.', [$agent, $chunk]); + return $this->error($response, PActions::SEND_PROGRESS, 'Chunk was aborted!'); + } + + switch ($state) { + case DHashcatStatus::EXHAUSTED: + $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::SPEED => 0, Chunk::PROGRESS => 10000, Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength()]); + DServerLog::log(DServerLog::TRACE, 'Chunk is exhausted (cracker status)', [$agent, $chunk]); + break; + + case DHashcatStatus::CRACKED: + $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength(), Chunk::PROGRESS => 10000, Chunk::SPEED => 0]); + DServerLog::log(DServerLog::TRACE, 'Last hash was cracked (cracker status)', [$agent, $chunk]); + TaskUtils::depriorizeAllTasks($hashlists); + TaskUtils::unassignAllAgents($hashlists); + DServerLog::log(DServerLog::TRACE, 'Depriorized all tasks of the hashlist and unassigned all agents', [$agent, $totalHashlist]); + $payload = new DataSet([DPayloadKeys::HASHLIST => $totalHashlist]); + NotificationHandler::checkNotifications(DNotificationType::HASHLIST_ALL_CRACKED, $payload); + break; + + case DHashcatStatus::ABORTED: + case DHashcatStatus::QUIT: + $chunk = Factory::getChunkFactory()->set($chunk, Chunk::SPEED, 0); + return $this->error($response, PActions::SEND_PROGRESS, 'Chunk was aborted!'); + + case DHashcatStatus::RUNNING: + default: + $qF1 = new ComparisonFilter(Hashlist::CRACKED, Hashlist::HASH_COUNT, '<'); + $qF2 = new ContainFilter(Hashlist::HASHLIST_ID, Util::arrayOfIds($hashlists)); + $count = Factory::getHashlistFactory()->countFilter([Factory::FILTER => [$qF1, $qF2]]); + if ($count == 0) { + $payload = new DataSet([DPayloadKeys::HASHLIST => $totalHashlist]); + NotificationHandler::checkNotifications(DNotificationType::HASHLIST_ALL_CRACKED, $payload); + DServerLog::log(DServerLog::TRACE, 'Agent still is running, but all hashes got cracked (all agents together), stop it', [$agent]); + $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength(), Chunk::PROGRESS => 10000, Chunk::SPEED => 0]); + TaskUtils::depriorizeAllTasks($hashlists); + $qF = new QueryFilter(Assignment::TASK_ID, $task->getId(), '='); + Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); + $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); + DServerLog::log(DServerLog::TRACE, 'Depriorized all tasks and updated', [$agent, $task, $chunk, $totalHashlist]); + return $this->success($response, PActions::SEND_PROGRESS, [ + PResponseSendProgress::NUM_CRACKED => $sumCracked, + PResponseSendProgress::NUM_SKIPPED => $skipped, + PResponseSendProgress::AGENT_COMMAND => 'stop', + ]); + } + $chunk = Factory::getChunkFactory()->set($chunk, Chunk::SPEED, $speed); + if ($speed > 0) { + $s = new Speed(null, $agent->getId(), $task->getId(), $speed, time()); + Factory::getSpeedFactory()->save($s); + } + $qF = new QueryFilter(AgentZap::AGENT_ID, $agent->getId(), '='); + $agentZap = Factory::getAgentZapFactory()->filter([Factory::FILTER => $qF], true); + if ($agentZap === null) { + $agentZap = new AgentZap(null, $agent->getId(), null); + Factory::getAgentZapFactory()->save($agentZap); + } + $qF1 = new ContainFilter(Zap::HASHLIST_ID, Util::arrayOfIds($hashlists)); + $qF2 = new QueryFilter(Zap::ZAP_ID, ($agentZap->getLastZapId() === null) ? 0 : $agentZap->getLastZapId(), '>'); + $qF3 = new QueryFilterWithNull(Zap::AGENT_ID, $agent->getId(), '<>', true); + $zaps = Factory::getZapFactory()->filter([Factory::FILTER => [$qF1, $qF2, $qF3]]); + foreach ($zaps as $zap) { + if ($zap->getId() > $agentZap->getId()) { + $agentZap->setLastZapId($zap->getId()); + } + $toZap[] = $zap->getHash(); + } + Factory::getAgentFactory()->set($agent, Agent::LAST_TIME, time()); + if ($agentZap->getLastZapId() > 0) { + Factory::getAgentZapFactory()->update($agentZap); + } + DServerLog::log(DServerLog::TRACE, 'Checked zaps and sending new ones to agent', [$agent, $zaps]); + break; + } + + Util::cleaning(); + return $this->success($response, PActions::SEND_PROGRESS, [ + PResponseSendProgress::NUM_CRACKED => $sumCracked, + PResponseSendProgress::NUM_SKIPPED => $skipped, + PResponseSendProgress::HASH_ZAPS => $toZap, + ]); + } + + /** + * @throws Exception + */ + private function saveAgentStats(array $body, Agent $agent, int $dataTime): void { + $stats = [ + [PQuerySendProgress::GPU_TEMP, DAgentStatsType::GPU_TEMP, '<='], + [PQuerySendProgress::GPU_UTIL, DAgentStatsType::GPU_UTIL, '<'], + [PQuerySendProgress::CPU_UTIL, DAgentStatsType::CPU_UTIL, '<'], + ]; + foreach ($stats as [$field, $type, $op]) { + if (!isset($body[$field])) { + continue; + } + $values = $body[$field]; + for ($i = 0; $i < sizeof($values); $i++) { + if (!is_numeric($values[$i]) || ($op == '<=' && $values[$i] <= 0) || ($op == '<' && $values[$i] < 0)) { + unset($values[$i]); + } + } + if (sizeof($values) > 0) { + $data = implode(',', $values); + $agentStat = new AgentStat(null, $agent->getId(), $type, $dataTime, $data); + Factory::getAgentStatFactory()->save($agentStat); + } + } + } + + /** + * @throws Exception + */ + private function saveDebugOutput(array $body, Chunk $chunk): void { + if (!isset($body[PQuerySendProgress::DEBUG_OUTPUT])) { + return; + } + $lines = $body[PQuerySendProgress::DEBUG_OUTPUT]; + $taskDebugOutputs = []; + foreach ($lines as $line) { + $taskDebugOutputs[] = new TaskDebugOutput(null, $chunk->getTaskId(), $line); + } + if (sizeof($taskDebugOutputs) > 0) { + Factory::getTaskDebugOutputFactory()->massSave($taskDebugOutputs); + } + } + + /** + * @return array|string Returns 'skip' if the hash was skipped, + * or an array with plainUpdates, crackPosUpdates, timeUpdates, crackHashes, zaps. + * @throws Exception + */ + private function processPlainCrack(array $splitLine, array $hashlists, Chunk $chunk, Agent $agent, Hashlist $totalHashlist, bool $isNewWPA, array &$cracked): array|string { + if ($isNewWPA) { + $split = explode(':', $splitLine[0]); + if (sizeof($split) == 4) { + $split[3] = Util::strToHex($split[3]); + $identifier = 'WPA*%*' . implode('*', $split) . '%'; + } + else { + return 'skip'; + } + $qF1 = new LikeFilterInsensitive(Hash::HASH, $identifier); + } + else { + $qF1 = new QueryFilter(Hash::HASH, $splitLine[0], '='); + } + + $qF2 = new ContainFilter(Hash::HASHLIST_ID, Util::arrayOfIds($hashlists)); + $qF3 = new QueryFilter(Hash::IS_CRACKED, 0, '='); + $hashes = Factory::getHashFactory()->filter([Factory::FILTER => [$qF1, $qF2, $qF3]]); + if (sizeof($hashes) == 0) { + $check = Factory::getHashFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); + if (sizeof($check) == 0) { + $logMessage = 'Hash has been cracked but skipped! This happened while cracking hashlist with ID: ' + . $totalHashlist->getId() . ' during chunk with ID: ' . $chunk->getId() . ' This happens when the agent returns + a cracked hash that does not exist in the database. This can happen when hashcat malforms the hash.'; + Util::createLogEntry(DLogEntryIssuer::API, $agent->getToken(), DLogEntry::FATAL, $logMessage); + DServerLog::log(DServerLog::FATAL, $logMessage); + } + return 'skip'; + } + + if (sizeof($splitLine) == 5) { + $plain = $splitLine[2]; + $crackPos = intval($splitLine[4]); + } + else { + $plain = $splitLine[1]; + $crackPos = intval($splitLine[3]); + } + + $plainUpdates = []; + $crackPosUpdates = []; + $timeUpdates = []; + $crackHashes = []; + $zaps = []; + foreach ($hashes as $hash) { + $cracked[$hash->getHashlistId()]++; + $plainUpdates[] = new MassUpdateSet($hash->getId(), $plain); + $crackPosUpdates[] = new MassUpdateSet($hash->getId(), $crackPos); + $timeUpdates[] = new MassUpdateSet($hash->getId(), time()); + $crackHashes[] = $hash->getId(); + $zaps[] = new Zap(null, $hash->getHash(), time(), $agent->getId(), $totalHashlist->getId()); + } + + return [ + 'plainUpdates' => $plainUpdates, + 'crackPosUpdates' => $crackPosUpdates, + 'timeUpdates' => $timeUpdates, + 'crackHashes' => $crackHashes, + 'zaps' => $zaps, + ]; + } + + /** + * @throws Exception + */ + private function flushPlainBatch(array $plainUpdates, array $crackPosUpdates, array $timeUpdates, array $crackHashes, array $zaps, Chunk $chunk): void { + $uS1 = new UpdateSet(Hash::CHUNK_ID, $chunk->getId()); + $uS2 = new UpdateSet(Hash::IS_CRACKED, 1); + $qF = new ContainFilter(Hash::HASH_ID, $crackHashes); + Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::PLAINTEXT, $plainUpdates); + Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::CRACK_POS, $crackPosUpdates); + Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::TIME_CRACKED, $timeUpdates); + Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS1, Factory::FILTER => $qF]); + Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS2, Factory::FILTER => $qF]); + Factory::getZapFactory()->massSave($zaps); + Factory::getAgentFactory()->getDB()->commit(); + Factory::getAgentFactory()->getDB()->beginTransaction(); + } + + /** + * @throws Exception + */ + private function processWPACrack(array $splitLine, Chunk $chunk, Hashlist $totalHashlist, array &$cracked): int { + $split = explode(':', $splitLine[0]); + if (sizeof($split) == 4) { + $mac_ap = $split[1]; + $mac_cli = $split[2]; + $essid = $split[3]; + } + else if (sizeof($split) == 3) { + $mac_ap = $split[0]; + $mac_cli = $split[1]; + $essid = $split[2]; + } + else { + $mac_ap = $split[0]; + $mac_cli = $split[1]; + $essid = ''; + } + if (str_starts_with($essid, '$HEX[') && str_starts_with($essid, ']') && strlen($essid) % 2 == 0) { + $essid = substr($essid, 5, strlen($essid) - 6); + } + else if (sizeof($split) < 4) { + $essid = Util::strToHex($essid); + } + $identification = $mac_ap . SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . $mac_cli; + if (sizeof($split) > 2) { + $identification .= SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . $essid; + } + $plain = $splitLine[1]; + $crackPos = intval($splitLine[3]); + $qF1 = new QueryFilter(HashBinary::ESSID, $identification, '='); + $qF2 = new QueryFilter(HashBinary::IS_CRACKED, 0, '='); + $hashes = Factory::getHashBinaryFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); + $skipped = 0; + if (sizeof($hashes) == 0) { + $skipped = 1; + } + foreach ($hashes as $hash) { + $cracked[$hash->getHashlistId()]++; + $hash->setIsCracked(1); + $hash->setChunkId($chunk->getId()); + $hash->setPlaintext($plain); + $hash->setCrackPos($crackPos); + $hash->setTimeCracked(time()); + Factory::getHashBinaryFactory()->update($hash); + } + return $skipped; + } + + /** + * @throws Exception + */ + private function processBinaryCrack(array $splitLine, Chunk $chunk, Hashlist $totalHashlist, array &$cracked): int { + $plain = $splitLine[1]; + $crackPos = intval($splitLine[3]); + $qF1 = new QueryFilter(HashBinary::HASHLIST_ID, $totalHashlist->getId(), '='); + $qF2 = new QueryFilter(HashBinary::IS_CRACKED, 0, '='); + $hashes = Factory::getHashBinaryFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); + $skipped = 0; + if (sizeof($hashes) == 0) { + $skipped = 1; + } + foreach ($hashes as $hash) { + $cracked[$hash->getHashlistId()]++; + $hash->setIsCracked(1); + $hash->setChunkId($chunk->getId()); + $hash->setPlaintext($plain); + $hash->setCrackPos($crackPos); + $hash->setTimeCracked(time()); + Factory::getHashBinaryFactory()->update($hash); + } + return $skipped; + } +} diff --git a/src/inc/agentapi/model/TestConnectionAction.php b/src/inc/agentapi/model/TestConnectionAction.php new file mode 100644 index 000000000..5873af5ec --- /dev/null +++ b/src/inc/agentapi/model/TestConnectionAction.php @@ -0,0 +1,31 @@ +withHeader('Content-Type', 'application/json'); + $response->getBody()->write(json_encode($envelope)); + return $response; + } +} diff --git a/src/inc/agentapi/model/UpdateInformationAction.php b/src/inc/agentapi/model/UpdateInformationAction.php new file mode 100644 index 000000000..4d996403c --- /dev/null +++ b/src/inc/agentapi/model/UpdateInformationAction.php @@ -0,0 +1,68 @@ +getParsedBody(); + if (!is_array($body)) { + $body = []; + } + + if (!isset($body[PQuery::TOKEN]) || !isset($body[PQueryUpdateInformation::DEVICES]) || !isset($body[PQueryUpdateInformation::UID]) || !isset($body[PQueryUpdateInformation::OPERATING_SYSTEM])) { + return $this->error($response, PActions::UPDATE_CLIENT_INFORMATION, 'Invalid update query!'); + } + + /** @var Agent $agent */ + $agent = $request->getAttribute(AgentAction::AGENT_ATTRIBUTE); + + $devices = $body[PQueryUpdateInformation::DEVICES]; + $uid = htmlentities($body[PQueryUpdateInformation::UID], ENT_QUOTES, 'UTF-8'); + $os = intval($body[PQueryUpdateInformation::OPERATING_SYSTEM]); + + $cpuOnly = 1; + foreach ($devices as $device) { + $device = strtolower((string)$device); + if (str_contains($device, 'amd') || str_contains($device, 'ati ') || str_contains($device, 'radeon') || str_contains($device, 'nvidia') || str_contains($device, 'gtx') || str_contains($device, 'ti') || str_contains($device, 'microsoft')) { + $cpuOnly = 0; + } + } + + if (strlen($agent->getUid()) == 0 && $agent->getCpuOnly() == 0) { + $agent = Factory::getAgentFactory()->set($agent, Agent::CPU_ONLY, $cpuOnly); + } + $agent = Factory::getAgentFactory()->mset($agent, [ + Agent::DEVICES => htmlentities(implode("\n", $devices), ENT_QUOTES, 'UTF-8'), + Agent::UID => $uid, + Agent::OS => $os, + ]); + + $agent = $this->updateAgent($agent, PActions::UPDATE_CLIENT_INFORMATION); + DServerLog::log(DServerLog::DEBUG, 'Agent sent updated client information', [$agent]); + + return $this->success($response, PActions::UPDATE_CLIENT_INFORMATION); + } +} diff --git a/src/inc/agentapi/schema/OpenApiSchema.php b/src/inc/agentapi/schema/OpenApiSchema.php new file mode 100644 index 000000000..5e019659b --- /dev/null +++ b/src/inc/agentapi/schema/OpenApiSchema.php @@ -0,0 +1,280 @@ + + */ + public static function generate(): array { + $schemas = SchemaRegistry::getSchema(); + $actions = ActionRegistry::getActions(); + + $components = []; + $requestRefs = []; + $responseRefs = []; + + foreach ($actions as $action) { + $schema = $schemas[$action] ?? null; + if ($schema === null) { + continue; + } + + $reqName = self::schemaName($action, 'Request'); + $respName = self::schemaName($action, 'Response'); + + $components[$reqName] = self::buildRequestSchema($action, $schema['request'] ?? []); + $components[$respName] = self::buildResponseSchema($schema['response'] ?? [], $schema['response_variants'] ?? []); + + $requestRefs[] = ['$ref' => "#/components/schemas/$reqName"]; + $responseRefs[] = ['$ref' => "#/components/schemas/$respName"]; + } + + $components['ErrorEnvelope'] = self::buildErrorEnvelopeSchema(); + $components['InvEnvelope'] = self::buildInvEnvelopeSchema(); + + $responseRefs[] = ['$ref' => '#/components/schemas/ErrorEnvelope']; + $responseRefs[] = ['$ref' => '#/components/schemas/InvEnvelope']; + + $version = StartupConfig::getInstance()->getVersion(); + + return [ + 'openapi' => '3.1.0', + 'info' => [ + 'title' => 'Hashtopolis Agent API', + 'version' => $version, + 'description' => 'Agent communication protocol. All requests are POST /api/server.php with a JSON body containing an "action" field that determines the request and response shape.', + ], + 'paths' => [ + '/api/server.php' => [ + 'post' => [ + 'summary' => 'Agent API endpoint', + 'description' => 'Single-endpoint API. The "action" field in the request body selects one of 19 actions. Each action has its own request fields and response fields. Unknown or missing actions return the INV error envelope.', + 'operationId' => 'agentApi', + 'requestBody' => [ + 'required' => true, + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'oneOf' => $requestRefs, + ], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => 'JSON response with at least "action" and "response" fields.', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'oneOf' => $responseRefs, + ], + ], + ], + ], + ], + ], + ], + ], + 'components' => [ + 'schemas' => $components, + ], + ]; + } + + /** + * Generate the OpenAPI document as a JSON string. + */ + public static function generateJson(): string { + return json_encode(self::generate(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * Build the per-action request schema. + * + * @param string $action + * @param array> $fields + * @return array + */ + private static function buildRequestSchema(string $action, array $fields): array { + $properties = [ + PQuery::ACTION => [ + 'type' => 'string', + 'enum' => [$action], + 'description' => 'The action to perform.', + ], + ]; + $required = [PQuery::ACTION]; + + foreach ($fields as $name => $def) { + $properties[$name] = self::buildPropertySchema($def); + if ($def['required'] ?? false) { + $required[] = $name; + } + } + + return [ + 'type' => 'object', + 'properties' => $properties, + 'required' => $required, + ]; + } + + /** + * Build the per-action success response schema (extends the base + * success envelope with action-specific fields). + * + * @param array> $fields + * @param list> $variants + * @return array + */ + private static function buildResponseSchema(array $fields, array $variants = []): array { + $properties = [ + PResponse::ACTION => ['type' => 'string', 'description' => 'The action that was performed.'], + PResponse::RESPONSE => ['type' => 'string', 'enum' => [PValues::SUCCESS], 'description' => 'Always "SUCCESS" for success responses.'], + ]; + $required = [PResponse::ACTION, PResponse::RESPONSE]; + + foreach ($fields as $name => $def) { + $properties[$name] = self::buildPropertySchema($def); + } + + foreach ($variants as $variant) { + foreach ($variant['response'] ?? [] as $name => $def) { + $properties[$name] = self::buildPropertySchema($def); + } + } + + return [ + 'type' => 'object', + 'properties' => $properties, + 'required' => $required, + ]; + } + + /** + * Build the standard error envelope schema. + * + * @return array + */ + private static function buildErrorEnvelopeSchema(): array { + return [ + 'type' => 'object', + 'title' => 'Error response', + 'description' => 'Returned when an action-specific error occurs (e.g. invalid token, invalid task, etc.).', + 'properties' => [ + PResponse::ACTION => ['type' => 'string', 'description' => 'The action that was requested.'], + PResponse::RESPONSE => ['type' => 'string', 'enum' => [PValues::ERROR], 'description' => 'Always "ERROR".'], + PResponseErrorMessage::MESSAGE => ['type' => 'string', 'description' => 'Human-readable error message.'], + ], + 'required' => [PResponse::ACTION, PResponse::RESPONSE, PResponseErrorMessage::MESSAGE], + ]; + } + + /** + * Build the INV error envelope schema (unknown / missing action). + * + * @return array + */ + private static function buildInvEnvelopeSchema(): array { + return [ + 'type' => 'object', + 'title' => 'Invalid query response', + 'description' => 'Returned when the action is unknown or missing.', + 'properties' => [ + PResponse::ACTION => ['type' => 'string', 'enum' => ['INV'], 'description' => 'Always "INV".'], + PResponse::RESPONSE => ['type' => 'string', 'enum' => [PValues::ERROR], 'description' => 'Always "ERROR".'], + PResponseErrorMessage::MESSAGE => ['type' => 'string', 'description' => 'Always "Invalid query!".'], + ], + 'required' => [PResponse::ACTION, PResponse::RESPONSE, PResponseErrorMessage::MESSAGE], + ]; + } + + /** + * Convert a SchemaRegistry field definition to an OpenAPI property schema. + * + * @param array $def + * @return array + */ + private static function buildPropertySchema(array $def): array { + $type = $def['type'] ?? 'string'; + $schema = []; + + if (str_starts_with($type, 'array<')) { + $itemType = substr($type, 6, -1); + $schema['type'] = 'array'; + $schema['items'] = self::primitiveSchema($itemType); + } + else { + $schema = self::primitiveSchema($type); + } + + if (isset($def['description'])) { + $schema['description'] = $def['description']; + } + if (isset($def['enum'])) { + $schema['enum'] = $def['enum']; + } + + return $schema; + } + + /** + * Map a primitive type string to an OpenAPI schema fragment. + * + * @return array + */ + private static function primitiveSchema(string $type): array { + return match ($type) { + 'integer' => ['type' => 'integer'], + 'boolean' => ['type' => 'boolean'], + 'number' => ['type' => 'number'], + 'array' => ['type' => 'array', 'items' => ['type' => 'string']], + default => ['type' => 'string'], + }; + } + + /** + * Generate a PascalCase schema component name from an action string. + * + * @param string $action e.g. "sendKeyspace" + * @param string $suffix "Request" or "Response" + * @return string e.g. "SendKeyspaceRequest" + */ + private static function schemaName(string $action, string $suffix): string { + $parts = explode(' ', str_replace('-', ' ', preg_replace('/([A-Z])/', ' $1', $action) ?? $action)); + $name = ''; + foreach ($parts as $part) { + $name .= ucfirst($part); + } + return $name . $suffix; + } +} diff --git a/src/inc/agentapi/schema/SchemaRegistry.php b/src/inc/agentapi/schema/SchemaRegistry.php new file mode 100644 index 000000000..0054efaa1 --- /dev/null +++ b/src/inc/agentapi/schema/SchemaRegistry.php @@ -0,0 +1,328 @@ +> */ + public static function getSchema(): array { + return [ + PActions::TEST_CONNECTION => [ + 'summary' => 'Test the connection between agent and server (no auth required)', + 'request' => [], + 'response' => [], + ], + + PActions::REGISTER => [ + 'summary' => 'Register a new agent using a voucher (no auth required)', + 'request' => [ + PQueryRegister::VOUCHER => ['type' => 'string', 'required' => true, 'description' => 'Registration voucher'], + PQueryRegister::AGENT_NAME => ['type' => 'string', 'required' => true, 'description' => 'Agent name'], + PQueryRegister::CPU_ONLY => ['type' => 'boolean', 'required' => false, 'description' => 'Whether the agent is CPU-only (no GPU)'], + ], + 'response' => [ + PResponseRegister::TOKEN => ['type' => 'string', 'description' => 'Authentication token for subsequent requests'], + ], + ], + + PActions::LOGIN => [ + 'summary' => 'Validate the token and return server configuration', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryLogin::CLIENT_SIGNATURE => ['type' => 'string', 'required' => true, 'description' => 'Client signature / version string'], + ], + 'response' => [ + PResponseLogin::MULTICAST => ['type' => 'boolean', 'description' => 'Whether multicast is enabled'], + PResponseLogin::TIMEOUT => ['type' => 'integer', 'description' => 'Agent timeout in seconds'], + PResponseLogin::VERSION => ['type' => 'string', 'description' => 'Server version with git commit'], + ], + ], + + PActions::UPDATE_CLIENT_INFORMATION => [ + 'summary' => 'Update agent hardware information (UID, OS, devices)', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryUpdateInformation::UID => ['type' => 'string', 'required' => true, 'description' => 'Unique hardware identifier'], + PQueryUpdateInformation::OPERATING_SYSTEM => ['type' => 'integer', 'required' => true, 'description' => 'Operating system (0=Linux, 1=Windows, 2=Mac)'], + PQueryUpdateInformation::DEVICES => ['type' => 'array', 'required' => true, 'description' => 'List of GPU/device names'], + ], + 'response' => [], + ], + + PActions::CHECK_CLIENT_VERSION => [ + 'summary' => 'Check if a newer client binary is available', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryCheckClientVersion::VERSION => ['type' => 'string', 'required' => true, 'description' => 'Current client version'], + PQueryCheckClientVersion::TYPE => ['type' => 'string', 'required' => true, 'description' => 'Binary type to check'], + ], + 'response' => [ + PResponseClientUpdate::VERSION => ['type' => 'string', 'description' => 'Update status', 'enum' => [PValuesUpdateVersion::UP_TO_DATE, PValuesUpdateVersion::NEW_VERSION]], + PResponseClientUpdate::URL => ['type' => 'string', 'description' => 'Download URL (only when version="NEW")'], + ], + ], + + PActions::DOWNLOAD_BINARY => [ + 'summary' => 'Download a binary (7zr, uftpd, cracker, or preprocessor)', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryDownloadBinary::BINARY_TYPE => ['type' => 'string', 'required' => true, 'description' => 'Binary type', 'enum' => [PValuesDownloadBinaryType::EXTRACTOR, PValuesDownloadBinaryType::UFTPD, PValuesDownloadBinaryType::CRACKER, PValuesDownloadBinaryType::PRINCE, PValuesDownloadBinaryType::PREPROCESSOR]], + PQueryDownloadBinary::BINARY_VERSION_ID => ['type' => 'integer', 'required' => false, 'description' => 'Cracker binary version ID (for type="cracker")'], + PQueryDownloadBinary::PREPROCESSOR_ID => ['type' => 'integer', 'required' => false, 'description' => 'Preprocessor ID (for type="prince" or "preprocessor")'], + ], + 'response' => [ + PResponseBinaryDownload::EXECUTABLE => ['type' => 'string', 'description' => 'Executable file path/URL'], + PResponseBinaryDownload::URL => ['type' => 'string', 'description' => 'Download URL (cracker/preprocessor types)'], + PResponseBinaryDownload::NAME => ['type' => 'string', 'description' => 'Binary name (cracker/preprocessor types)'], + PResponseBinaryDownload::KEYSPACE_CMD => ['type' => 'string', 'description' => 'Keyspace command (preprocessor types)'], + PResponseBinaryDownload::SKIP_CMD => ['type' => 'string', 'description' => 'Skip command (preprocessor types)'], + PResponseBinaryDownload::LIMIT_CMD => ['type' => 'string', 'description' => 'Limit command (preprocessor types)'], + ], + ], + + PActions::CLIENT_ERROR => [ + 'summary' => 'Report a hashcat error from the agent', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryClientError::TASK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Task ID the error occurred on'], + PQueryClientError::MESSAGE => ['type' => 'string', 'required' => true, 'description' => 'Error message from hashcat'], + PQueryClientError::CHUNK_ID => ['type' => 'integer', 'required' => false, 'description' => 'Chunk ID the error occurred on'], + ], + 'response' => [], + ], + + PActions::GET_FILE => [ + 'summary' => 'Get the download URL and metadata for a task file', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryGetFile::TASK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Task ID'], + PQueryGetFile::FILENAME => ['type' => 'string', 'required' => true, 'description' => 'Filename to retrieve'], + ], + 'response' => [ + PResponseGetFile::FILENAME => ['type' => 'string', 'description' => 'File name'], + PResponseGetFile::EXTENSION => ['type' => 'string', 'description' => 'File extension'], + PResponseGetFile::URL => ['type' => 'string', 'description' => 'Download URL'], + PResponseGetFile::FILESIZE => ['type' => 'integer', 'description' => 'File size in bytes'], + ], + ], + + PActions::GET_HASHLIST => [ + 'summary' => 'Get the download URL for the assigned task\'s hashlist', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryGetHashlist::HASHLIST_ID => ['type' => 'integer', 'required' => true, 'description' => 'Hashlist ID'], + ], + 'response' => [ + PResponseGetHashlist::URL => ['type' => 'string', 'description' => 'Hashlist download URL'], + ], + ], + + PActions::GET_TASK => [ + 'summary' => 'Get the task the agent should work on', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + ], + 'response' => [ + PResponseGetTask::TASK_ID => ['type' => 'integer', 'description' => 'Task ID, or -1 for health check, or null if no task'], + PResponseGetTask::REASON => ['type' => 'string', 'description' => 'Reason when no task is available (only when taskId is null)'], + PResponseGetTask::ATTACK_COMMAND => ['type' => 'string', 'description' => 'Hashcat attack command'], + PResponseGetTask::CMD_PARAMETERS => ['type' => 'string', 'description' => 'Additional command-line parameters'], + PResponseGetTask::HASHLIST_ID => ['type' => 'integer', 'description' => 'Hashlist ID'], + PResponseGetTask::BENCHMARK => ['type' => 'integer', 'description' => 'Benchmark time in seconds'], + PResponseGetTask::STATUS_TIMER => ['type' => 'integer', 'description' => 'Status report interval in seconds'], + PResponseGetTask::FILES => ['type' => 'array', 'description' => 'List of task file names'], + PResponseGetTask::CRACKER_ID => ['type' => 'integer', 'description' => 'Cracker binary ID'], + PResponseGetTask::BENCHTYPE => ['type' => 'string', 'description' => 'Benchmark type', 'enum' => [PValuesBenchmarkType::SPEED_TEST, PValuesBenchmarkType::RUN_TIME]], + PResponseGetTask::HASHLIST_ALIAS => ['type' => 'string', 'description' => 'Hashlist alias placeholder'], + PResponseGetTask::KEYSPACE => ['type' => 'integer', 'description' => 'Task keyspace'], + PResponseGetTask::USE_PREPROCESSOR => ['type' => 'boolean', 'description' => 'Whether a preprocessor is used'], + PResponseGetTask::PREPROCESSOR => ['type' => 'integer', 'description' => 'Preprocessor ID'], + PResponseGetTask::PREPROCESSOR_COMMAND => ['type' => 'string', 'description' => 'Preprocessor command'], + PResponseGetTask::ENFORCE_PIPE => ['type' => 'boolean', 'description' => 'Whether piping is enforced'], + PResponseGetTask::SLOW_HASH => ['type' => 'boolean', 'description' => 'Whether the hash type is slow'], + PResponseGetTask::USE_BRAIN => ['type' => 'boolean', 'description' => 'Whether hashcat brain is used'], + ], + 'response_variants' => [ + [ + 'condition' => 'When useBrain is true', + 'response' => [ + PResponseGetTask::BRAIN_HOST => ['type' => 'string', 'description' => 'Hashcat brain host'], + PResponseGetTask::BRAIN_PORT => ['type' => 'integer', 'description' => 'Hashcat brain port'], + PResponseGetTask::BRAIN_PASS => ['type' => 'string', 'description' => 'Hashcat brain password'], + PResponseGetTask::BRAIN_FEATURES => ['type' => 'integer', 'description' => 'Hashcat brain features bitmask'], + ], + ], + ], + ], + + PActions::GET_CHUNK => [ + 'summary' => 'Get a chunk to work on for an assigned task', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryGetChunk::TASK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Task ID'], + ], + 'response' => [ + PResponseGetChunk::CHUNK_STATUS => ['type' => 'string', 'description' => 'Chunk status', 'enum' => [PValuesChunkType::OK, PValuesChunkType::KEYSPACE_REQUIRED, PValuesChunkType::BENCHMARK_REQUIRED, PValuesChunkType::FULLY_DISPATCHED, PValuesChunkType::CRACKER_UPDATE, PValuesChunkType::HEALTH_CHECK]], + PResponseGetChunk::CHUNK_ID => ['type' => 'integer', 'description' => 'Chunk ID (only when status="OK")'], + PResponseGetChunk::KEYSPACE_SKIP => ['type' => 'integer', 'description' => 'Keyspace skip offset (only when status="OK")'], + PResponseGetChunk::KEYSPACE_LENGTH => ['type' => 'integer', 'description' => 'Keyspace length (only when status="OK")'], + ], + ], + + PActions::SEND_KEYSPACE => [ + 'summary' => 'Report the measured keyspace for a task', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQuerySendKeyspace::TASK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Task ID'], + PQuerySendKeyspace::KEYSPACE => ['type' => 'integer', 'required' => true, 'description' => 'Measured keyspace size'], + ], + 'response' => [ + PResponseSendKeyspace::KEYSPACE => ['type' => 'string', 'description' => 'Confirmation', 'enum' => ['OK']], + ], + ], + + PActions::SEND_BENCHMARK => [ + 'summary' => 'Report benchmark results for a task', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQuerySendBenchmark::TASK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Task ID'], + PQuerySendBenchmark::TYPE => ['type' => 'string', 'required' => true, 'description' => 'Benchmark type', 'enum' => [PValuesBenchmarkType::SPEED_TEST, PValuesBenchmarkType::RUN_TIME]], + PQuerySendBenchmark::RESULT => ['type' => 'string', 'required' => true, 'description' => 'Benchmark result (int:float for speed, int for run)'], + ], + 'response' => [ + PResponseSendBenchmark::BENCHMARK => ['type' => 'string', 'description' => 'Confirmation', 'enum' => ['OK']], + ], + ], + + PActions::SEND_PROGRESS => [ + 'summary' => 'Report chunk progress, cracked hashes, and receive zaps', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQuerySendProgress::CHUNK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Chunk ID'], + PQuerySendProgress::KEYSPACE_PROGRESS => ['type' => 'integer', 'required' => true, 'description' => 'Keyspace progress (current position)'], + PQuerySendProgress::RELATIVE_PROGRESS => ['type' => 'integer', 'required' => true, 'description' => 'Relative progress (0-10000)'], + PQuerySendProgress::SPEED => ['type' => 'integer', 'required' => true, 'description' => 'Cracking speed (H/s)'], + PQuerySendProgress::HASHCAT_STATE => ['type' => 'integer', 'required' => true, 'description' => 'Hashcat state (0=INIT, 2=RUNNING, 4=EXHAUSTED, 5=CRACKED, 6=ABORTED, 7=QUIT, 10=STATUS_ABORTED_RUNTIME)'], + PQuerySendProgress::CRACKED_HASHES => ['type' => 'array', 'required' => true, 'description' => 'Cracked hashes: [[hash, plain, hex_plain, crack_pos], ...]'], + PQuerySendProgress::DEBUG_OUTPUT => ['type' => 'array', 'required' => false, 'description' => 'Debug output lines'], + PQuerySendProgress::GPU_TEMP => ['type' => 'array', 'required' => false, 'description' => 'GPU temperatures'], + PQuerySendProgress::GPU_UTIL => ['type' => 'array', 'required' => false, 'description' => 'GPU utilization percentages'], + PQuerySendProgress::CPU_UTIL => ['type' => 'array', 'required' => false, 'description' => 'CPU utilization percentages'], + ], + 'response' => [ + PResponseSendProgress::NUM_CRACKED => ['type' => 'integer', 'description' => 'Number of hashes cracked in this update'], + PResponseSendProgress::NUM_SKIPPED => ['type' => 'integer', 'description' => 'Number of cracks skipped (not found in DB)'], + PResponseSendProgress::HASH_ZAPS => ['type' => 'array', 'description' => 'Hashes cracked by other agents (for local removal)'], + PResponseSendProgress::AGENT_COMMAND => ['type' => 'string', 'description' => 'Agent command (only present when agent should stop)', 'enum' => ['stop']], + ], + ], + + PActions::GET_FILE_STATUS => [ + 'summary' => 'Get the list of files that should be deleted on the agent', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + ], + 'response' => [ + PResponseGetFileStatus::FILENAMES => ['type' => 'array', 'description' => 'List of filenames to delete'], + ], + ], + + PActions::GET_HEALTH_CHECK => [ + 'summary' => 'Get a pending health check for the agent', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + ], + 'response' => [ + PResponseGetHealthCheck::ATTACK => ['type' => 'string', 'description' => 'Attack command for the health check'], + PResponseGetHealthCheck::CRACKER_BINARY_ID => ['type' => 'integer', 'description' => 'Cracker binary ID to use'], + PResponseGetHealthCheck::HASHES => ['type' => 'array', 'description' => 'Hashes for the health check'], + PResponseGetHealthCheck::CHECK_ID => ['type' => 'integer', 'description' => 'Health check ID'], + PResponseGetHealthCheck::HASHLIST_ALIAS => ['type' => 'string', 'description' => 'Hashlist alias placeholder'], + ], + ], + + PActions::SEND_HEALTH_CHECK => [ + 'summary' => 'Report health check results', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQuerySendHealthCheck::CHECK_ID => ['type' => 'integer', 'required' => true, 'description' => 'Health check ID'], + PQuerySendHealthCheck::NUM_CRACKED => ['type' => 'integer', 'required' => true, 'description' => 'Number of hashes cracked'], + PQuerySendHealthCheck::NUM_GPUS => ['type' => 'integer', 'required' => true, 'description' => 'Number of GPUs used'], + PQuerySendHealthCheck::ERRORS => ['type' => 'array', 'required' => true, 'description' => 'Error messages (empty if none)'], + PQuerySendHealthCheck::START => ['type' => 'integer', 'required' => true, 'description' => 'Start timestamp'], + PQuerySendHealthCheck::END => ['type' => 'integer', 'required' => true, 'description' => 'End timestamp'], + ], + 'response' => [], + ], + + PActions::GET_FOUND => [ + 'summary' => 'Get the download URL for already-cracked hashes', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + PQueryGetFound::HASHLIST_ID => ['type' => 'integer', 'required' => true, 'description' => 'Hashlist ID'], + ], + 'response' => [ + PResponseGetFound::URL => ['type' => 'string', 'description' => 'Found hashes download URL'], + ], + ], + + PActions::DEREGISTER => [ + 'summary' => 'De-register the agent from the server (gated by allowDeregister config)', + 'request' => [ + PQuery::TOKEN => ['type' => 'string', 'required' => true, 'description' => 'Agent authentication token'], + ], + 'response' => [], + ], + ]; + } +} diff --git a/src/inc/api/APIBasic.php b/src/inc/api/APIBasic.php deleted file mode 100644 index e5837e38d..000000000 --- a/src/inc/api/APIBasic.php +++ /dev/null @@ -1,61 +0,0 @@ -agent = Factory::getAgentFactory()->mset($this->agent, [Agent::LAST_IP => Util::getIP(), Agent::LAST_ACT => $action, Agent::LAST_TIME => time()]); - } - - public function sendErrorResponse($action, $msg): void { - $ANS = array(); - $ANS[PResponse::ACTION] = $action; - $ANS[PResponse::RESPONSE] = PValues::ERROR; - $ANS[PResponseErrorMessage::MESSAGE] = $msg; - header("Content-Type: application/json"); - echo json_encode($ANS); - die(); - } - - /** - * @throws Exception - */ - public function checkToken($action, $QUERY): void { - $qF = new QueryFilter(Agent::TOKEN, $QUERY[PQuery::TOKEN], "="); - $agent = Factory::getAgentFactory()->filter([Factory::FILTER => array($qF)], true); - if ($agent == null) { - DServerLog::log(DServerLog::WARNING, "Agent from " . Util::getIP() . " sent invalid token!"); - $this->sendErrorResponse($action, "Invalid token!"); - } - $this->agent = $agent; - } -} diff --git a/src/inc/api/APICheckClientVersion.php b/src/inc/api/APICheckClientVersion.php deleted file mode 100644 index 31a9d6edd..000000000 --- a/src/inc/api/APICheckClientVersion.php +++ /dev/null @@ -1,62 +0,0 @@ -sendErrorResponse(PActions::CHECK_CLIENT_VERSION, 'Invalid version check query!'); - } - $this->checkToken(PActions::CHECK_CLIENT_VERSION, $QUERY); - - $version = $QUERY[PQueryCheckClientVersion::VERSION]; - $type = $QUERY[PQueryCheckClientVersion::TYPE]; - - $qF = new QueryFilter(AgentBinary::BINARY_TYPE, $type, "="); - $result = Factory::getAgentBinaryFactory()->filter([Factory::FILTER => $qF], true); - if ($result == null) { - DServerLog::log(DServerLog::WARNING, "Agent " . $this->agent->getId() . " sent unknown client type: " . $type); - $this->sendErrorResponse(PActions::CHECK_CLIENT_VERSION, "Type not found!"); - } - - $this->updateAgent(PActions::CHECK_CLIENT_VERSION); - if (Comparator::greaterThan($result->getVersion(), $version)) { - DServerLog::log(DServerLog::DEBUG, "Agent " . $this->agent->getId() . " got notified about client update"); - $this->sendResponse(array( - PResponse::ACTION => PActions::CHECK_CLIENT_VERSION, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseClientUpdate::VERSION => PValuesUpdateVersion::NEW_VERSION, - PResponseClientUpdate::URL => Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . "/agents.php?download=" . $result->getId() - ) - ); - } - else { - $this->sendResponse(array( - PResponse::ACTION => PActions::CHECK_CLIENT_VERSION, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseClientUpdate::VERSION => PValuesUpdateVersion::UP_TO_DATE - ) - ); - } - } -} diff --git a/src/inc/api/APIClientError.php b/src/inc/api/APIClientError.php deleted file mode 100644 index 4cf08c7d5..000000000 --- a/src/inc/api/APIClientError.php +++ /dev/null @@ -1,91 +0,0 @@ -sendErrorResponse(PActions::CLIENT_ERROR, "Invalid error query!"); - } - $this->checkToken(PActions::CLIENT_ERROR, $QUERY); - - // load task wrapper - $task = Factory::getTaskFactory()->get($QUERY[PQueryClientError::TASK_ID]); - if ($task == null) { - DServerLog::log(DServerLog::WARNING, "Agent " . $this->agent->getId() . " tried to send error for invalid task!"); - $this->sendErrorResponse(PActions::CLIENT_ERROR, "Invalid task!"); - } - - //check assignment - $qF1 = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($assignment == null) { - DServerLog::log(DServerLog::WARNING, "Agent " . $this->agent->getId() . " tried to send error for task he is not assigned to!"); - $this->sendErrorResponse(PActions::CLIENT_ERROR, "Agent is not assigned to this task!"); - } - - DServerLog::log(DServerLog::INFO, "Agent " . $this->agent->getId() . " sent error: " . $QUERY[PQueryClientError::MESSAGE]); - $whitelist = explode(",", SConfig::getInstance()->getVal(DConfig::HC_ERROR_IGNORE)); - foreach ($whitelist as $w) { - $w = trim($w); - if (str_contains($QUERY[PQueryClientError::MESSAGE], $w)) { - // error can be ignored, we just acknowledge that we received it - $this->sendResponse(array( - PResponse::ACTION => PActions::CLIENT_ERROR, - PResponse::RESPONSE => PValues::SUCCESS - ) - ); - } - } - - if ($this->agent->getIgnoreErrors() <= DAgentIgnoreErrors::IGNORE_SAVE) { - //save error message - $chunkId = null; - if (isset($QUERY[PQueryClientError::CHUNK_ID])) { - $chunkId = intval($QUERY[PQueryClientError::CHUNK_ID]); - } - $error = new AgentError(null, $this->agent->getId(), $task->getId(), $chunkId, time(), $QUERY[PQueryClientError::MESSAGE]); - Factory::getAgentErrorFactory()->save($error); - - $payload = new DataSet(array(DPayloadKeys::AGENT => $this->agent, DPayloadKeys::AGENT_ERROR => $QUERY[PQueryClientError::MESSAGE])); - NotificationHandler::checkNotifications(DNotificationType::AGENT_ERROR, $payload); - NotificationHandler::checkNotifications(DNotificationType::OWN_AGENT_ERROR, $payload); - } - - if ($this->agent->getIgnoreErrors() == DAgentIgnoreErrors::NO) { - //deactivate agent - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::IS_ACTIVE, 0); - } - - $this->updateAgent(PActions::CLIENT_ERROR); - $this->sendResponse(array( - PResponse::ACTION => PActions::CLIENT_ERROR, - PResponse::RESPONSE => PValues::SUCCESS - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIDeRegisterAgent.php b/src/inc/api/APIDeRegisterAgent.php deleted file mode 100644 index 10c1a3e70..000000000 --- a/src/inc/api/APIDeRegisterAgent.php +++ /dev/null @@ -1,41 +0,0 @@ -sendErrorResponse(PActions::DEREGISTER, "Invalid de-registering query!"); - } - $this->checkToken(PActions::DEREGISTER, $QUERY); - - if (!SConfig::getInstance()->getVal(DConfig::ALLOW_DEREGISTER)) { - $this->sendErrorResponse(PActions::DEREGISTER, "De-registration is not allowed on this server!"); - } - try { - AgentUtils::delete($this->agent->getId(), null); - } - catch (HTException $e) { - $this->sendErrorResponse(PActions::DEREGISTER, "Error occured during de-registration: " . $e->getMessage()); - } - $this->sendResponse(array( - PResponse::ACTION => PActions::DEREGISTER, - PResponse::RESPONSE => PValues::SUCCESS - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIDownloadBinary.php b/src/inc/api/APIDownloadBinary.php deleted file mode 100644 index d9d250427..000000000 --- a/src/inc/api/APIDownloadBinary.php +++ /dev/null @@ -1,94 +0,0 @@ -sendErrorResponse(PActions::DOWNLOAD_BINARY, "Invalid download query!"); - } - $this->checkToken(PActions::DOWNLOAD_BINARY, $QUERY); - $this->updateAgent(PActions::DOWNLOAD_BINARY); - - // provide agent with requested download - switch ($QUERY[PQueryDownloadBinary::BINARY_TYPE]) { - case PValuesDownloadBinaryType::EXTRACTOR: - // downloading 7zip - DServerLog::log(DServerLog::TRACE, "Agent " . $this->agent->getId() . " downloaded 7zr binary"); - $filename = "7zr" . Util::getFileExtension($this->agent->getOs()); - $path = Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . "/static/" . $filename; - $this->sendResponse(array( - PResponse::ACTION => PActions::DOWNLOAD_BINARY, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseBinaryDownload::EXECUTABLE => $path - ) - ); - case PValuesDownloadBinaryType::UFTPD: - DServerLog::log(DServerLog::TRACE, "Agent " . $this->agent->getId() . " downloaded uftpd binary"); - $filename = "uftpd" . Util::getFileExtension($this->agent->getOs()); - $path = Util::buildServerUrl() . SConfig::getInstance()->getVal(DConfig::BASE_URL) . "/static/" . $filename; - $this->sendResponse(array( - PResponse::ACTION => PActions::DOWNLOAD_BINARY, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseBinaryDownload::EXECUTABLE => $path - ) - ); - case PValuesDownloadBinaryType::CRACKER: - $crackerBinary = Factory::getCrackerBinaryFactory()->get($QUERY[PQueryDownloadBinary::BINARY_VERSION_ID]); - if ($crackerBinary == null) { - $this->sendErrorResponse(PActions::DOWNLOAD_BINARY, "Invalid cracker binary type id!"); - } - $crackerBinaryType = Factory::getCrackerBinaryTypeFactory()->get($crackerBinary->getCrackerBinaryTypeId()); - DServerLog::log(DServerLog::TRACE, "Agent " . $this->agent->getId() . " downloaded cracker binary " . $crackerBinary->getId()); - - $ext = Util::getFileExtension($this->agent->getOs()); - $this->sendResponse(array( - PResponse::ACTION => PActions::DOWNLOAD_BINARY, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseBinaryDownload::URL => $crackerBinary->getDownloadUrl(), - PResponseBinaryDownload::NAME => $crackerBinaryType->getTypeName(), - PResponseBinaryDownload::EXECUTABLE => $crackerBinary->getBinaryName() . $ext - ) - ); - case PValuesDownloadBinaryType::PRINCE: - case PValuesDownloadBinaryType::PREPROCESSOR: - $preprocessor = Factory::getPreprocessorFactory()->get($QUERY[PQueryDownloadBinary::PREPROCESSOR_ID]); - if ($preprocessor == null) { - $this->sendErrorResponse(PActions::DOWNLOAD_BINARY, "Invalid preprocessor id!"); - } - DServerLog::log(DServerLog::TRACE, "Agent " . $this->agent->getId() . " downloaded preprocessor " . $preprocessor->getId()); - - $ext = Util::getFileExtension($this->agent->getOs()); - $this->sendResponse(array( - PResponse::ACTION => PActions::DOWNLOAD_BINARY, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseBinaryDownload::URL => $preprocessor->getUrl(), - PResponseBinaryDownload::NAME => $preprocessor->getName(), - PResponseBinaryDownload::EXECUTABLE => $preprocessor->getBinaryName() . $ext, - PResponseBinaryDownload::KEYSPACE_CMD => $preprocessor->getKeyspaceCommand(), - PResponseBinaryDownload::SKIP_CMD => $preprocessor->getSkipCommand(), - PResponseBinaryDownload::LIMIT_CMD => $preprocessor->getLimitCommand() - ) - ); - default: - DServerLog::log(DServerLog::WARNING, "Agent " . $this->agent->getId() . " requested invalid binary download: " . $QUERY[PQueryDownloadBinary::BINARY_TYPE]); - $this->sendErrorResponse(PActions::DOWNLOAD_BINARY, "Unknown download type!"); - } - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetChunk.php b/src/inc/api/APIGetChunk.php deleted file mode 100644 index 234190565..000000000 --- a/src/inc/api/APIGetChunk.php +++ /dev/null @@ -1,210 +0,0 @@ -sendErrorResponse(PActions::GET_CHUNK, "Invalid chunk query!"); - } - $this->checkToken(PActions::GET_CHUNK, $QUERY); - $this->updateAgent(PActions::GET_CHUNK); - - DServerLog::log(DServerLog::DEBUG, "Requesting a chunk...", [$this->agent]); - - if (HealthUtils::checkNeeded($this->agent)) { - DServerLog::log(DServerLog::DEBUG, "Notifying agent about health check", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::HEALTH_CHECK - ) - ); - } - - $task = Factory::getTaskFactory()->get($QUERY[PQueryGetChunk::TASK_ID]); - if ($task == null) { - DServerLog::log(DServerLog::WARNING, "Requested chunk on invalid task!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Invalid task ID!"); - } - - $qF1 = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($assignment == null) { - DServerLog::log(DServerLog::WARNING, "Requested chunk on task it is not assigned to!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "You are not assigned to this task!"); - } - else if ($task->getKeyspace() == 0) { - DServerLog::log(DServerLog::TRACE, "Need to measure keyspace!", [$this->agent, $task]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::KEYSPACE_REQUIRED - ) - ); - } - else if ($assignment->getBenchmark() == 0 && $task->getIsSmall() == 0 && $task->getStaticChunks() == DTaskStaticChunking::NORMAL) { // benchmark only required on non-small tasks and on non-special chunk tasks - DServerLog::log(DServerLog::TRACE, "Need to run a benchmark!", [$this->agent, $task]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::BENCHMARK_REQUIRED - ) - ); - } - else if ($this->agent->getIsActive() == 0) { - DServerLog::log(DServerLog::TRACE, "Agent is inactive!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Agent is inactive!"); - } - - $LOCKFILE = Lock::CHUNKING . $task->getId(); - - LockUtils::get($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Retrieved lock for chunking!", [$this->agent]); - $task = Factory::getTaskFactory()->get($task->getId()); - Factory::getAgentFactory()->getDB()->beginTransaction(); - DServerLog::log(DServerLog::DEBUG, "Checking task...", [$this->agent, $task]); - $task = TaskUtils::checkTask($task, $this->agent); - if ($task == null) { // agent needs a new task - DServerLog::log(DServerLog::DEBUG, "Task is fully dispatched", [$this->agent]); - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::FULLY_DISPATCHED - ) - ); - } - - DServerLog::log(DServerLog::TRACE, "Search for best task...", [$this->agent]); - $bestTask = TaskUtils::getBestTask($this->agent); - if ($bestTask == null) { - DServerLog::log(DServerLog::TRACE, "No best task available! (Probably because permissions changed)", [$this->agent]); - // this is a special case where this task is either not allowed anymore, or it has priority 0 so it doesn't get auto assigned - if (!AccessUtils::agentCanAccessTask($this->agent, $task)) { - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::INFO, "Not allowed to work on requested task", [$this->agent, $task]); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Not allowed to work on this task!"); - } - if (TaskUtils::isSaturatedByOtherAgents($task, $this->agent)) { - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Task already saturated by other agents, no other task available!"); - } - } - - if (TaskUtils::isSaturatedByOtherAgents($task, $this->agent)) { - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Task already saturated by other agents, other tasks available!"); - } - else { - DServerLog::log(DServerLog::TRACE, "Determine important task", [$this->agent, $task, $bestTask]); - $bestTask = TaskUtils::getImportantTask($bestTask, $task); - - if ($bestTask->getId() != $task->getId()) { - Factory::getAgentFactory()->getDB()->commit(); - DServerLog::log(DServerLog::INFO, "Task with higher priority available!", [$this->agent]); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendErrorResponse(PActions::GET_CHUNK, "Task with higher priority available!"); - } - } - - // find a chunk to assign - DServerLog::log(DServerLog::DEBUG, "Searching existing chunk...", [$this->agent, $task]); - $qF1 = new QueryFilter(Chunk::PROGRESS, 10000, "<"); - $qF2 = new QueryFilter(Chunk::TASK_ID, $task->getId(), "="); - $oF = new OrderFilter(Chunk::SKIP, "ASC"); - $chunks = Factory::getChunkFactory()->filter([Factory::FILTER => [$qF1, $qF2], Factory::ORDER => $oF]); - $qF1 = new QueryFilter(Chunk::PROGRESS, null, "="); - /** @var Chunk[] $chunks */ - $chunks = array_merge($chunks, Factory::getChunkFactory()->filter([Factory::FILTER => [$qF1, $qF2], Factory::ORDER => $oF])); - foreach ($chunks as $chunk) { - if ($chunk->getAgentId() == $this->agent->getId()) { - DServerLog::log(DServerLog::DEBUG, "Found chunk of same agent which is not done yet.", [$this->agent, $task, $chunk]); - $this->sendChunk(ChunkUtils::handleExistingChunk($chunk, $task, $assignment)); - } - $timeoutTime = time() - SConfig::getInstance()->getVal(DConfig::CHUNK_TIMEOUT); - if ($chunk->getState() == DHashcatStatus::ABORTED || $chunk->getState() == DHashcatStatus::STATUS_ABORTED_RUNTIME || max($chunk->getDispatchTime(), $chunk->getSolveTime()) < $timeoutTime) { - DServerLog::log(DServerLog::DEBUG, "Found existing chunk which is not done yet", [$this->agent, $task, $chunk]); - $this->sendChunk(ChunkUtils::handleExistingChunk($chunk, $task, $assignment)); - } - } - DServerLog::log(DServerLog::DEBUG, "Create new chunk for agent", [$this->agent, $task]); - $chunk = ChunkUtils::createNewChunk($task, $assignment); - if ($chunk == null) { - DServerLog::log(DServerLog::DEBUG, "Could not create a chunk, task is fully dispatched", [$this->agent, $task]); - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release($LOCKFILE); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::FULLY_DISPATCHED - ) - ); - } - DServerLog::log(DServerLog::DEBUG, "Sending new chunk to agent", [$this->agent, $task, $chunk]); - $this->sendChunk($chunk); - } - - /** - * @param ?Chunk $chunk - * @throws Exception - */ - protected function sendChunk(?Chunk $chunk): void { - if ($chunk == null) { - return; // this can be safely done before the commit/release, because the only sendChunk which comes really at the end check for null before, so a lock which is not released cannot happen - } - Factory::getAgentFactory()->getDB()->commit(); - LockUtils::release(Lock::CHUNKING . $chunk->getTaskId()); - DServerLog::log(DServerLog::TRACE, "Released lock for chunking!", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_CHUNK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetChunk::CHUNK_STATUS => PValuesChunkType::OK, - PResponseGetChunk::CHUNK_ID => (int)($chunk->getId()), - PResponseGetChunk::KEYSPACE_SKIP => (int)($chunk->getSkip()), - PResponseGetChunk::KEYSPACE_LENGTH => (int)($chunk->getLength()) - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetFile.php b/src/inc/api/APIGetFile.php deleted file mode 100644 index a47854cf2..000000000 --- a/src/inc/api/APIGetFile.php +++ /dev/null @@ -1,77 +0,0 @@ -sendErrorResponse(PActions::GET_FILE, "Invalid file query!"); - } - $this->checkToken(PActions::GET_FILE, $QUERY); - - DServerLog::log(DServerLog::DEBUG, "Requesting file " . $QUERY[PQueryGetFile::FILENAME], [$this->agent]); - - // let agent download adjacent files - $task = Factory::getTaskFactory()->get($QUERY[PQueryGetFile::TASK_ID]); - if ($task == null) { - $this->sendErrorResponse(PActions::GET_FILE, "Invalid task!"); - } - - $qF1 = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - $qF2 = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($assignment == null) { - $this->sendErrorResponse(PActions::GET_FILE, "Client is not assigned to this task!"); - } - - $file = $QUERY[PQueryGetFile::FILENAME]; - $qF = new QueryFilter(File::FILENAME, $file, "="); - $file = Factory::getFileFactory()->filter([Factory::FILTER => $qF], true); - if ($file == null) { - $this->sendErrorResponse(PActions::GET_FILE, "Invalid file!"); - } - - $qF1 = new QueryFilter(FileTask::TASK_ID, $task->getId(), "="); - $qF2 = new QueryFilter(FileTask::FILE_ID, $file->getId(), "="); - $taskFile = Factory::getFileTaskFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($taskFile == null) { - DServerLog::log(DServerLog::WARNING, "Agent requested file not used in the task!", [$this->agent, $file, $task]); - $this->sendErrorResponse(PActions::GET_FILE, "This file is not used for the specified task!"); - } - - if ($this->agent->getIsTrusted() < $file->getIsSecret()) { - $this->sendErrorResponse(PActions::GET_FILE, "You have no access to get this file!"); - } - $filename = $file->getFilename(); - $extension = explode(".", $filename)[sizeof(explode(".", $filename)) - 1]; - - $this->updateAgent(PActions::GET_FILE); - - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_FILE, - PResponseGetFile::FILENAME => $filename, - PResponseGetFile::EXTENSION => $extension, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetFile::URL => "getFile.php?file=" . $file->getId() . "&token=" . $this->agent->getToken(), - PResponseGetFile::FILESIZE => (int)$file->getSize() - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetFileStatus.php b/src/inc/api/APIGetFileStatus.php deleted file mode 100644 index a8ea2921a..000000000 --- a/src/inc/api/APIGetFileStatus.php +++ /dev/null @@ -1,32 +0,0 @@ -checkToken(PActions::GET_FILE_STATUS, $QUERY); - - $deleteRequests = Factory::getFileDeleteFactory()->filter([]); - $files = []; - foreach ($deleteRequests as $deleteRequest) { - $files[] = $deleteRequest->getFilename(); - } - - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_FILE_STATUS, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetFileStatus::FILENAMES => $files - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetFound.php b/src/inc/api/APIGetFound.php deleted file mode 100644 index 385921505..000000000 --- a/src/inc/api/APIGetFound.php +++ /dev/null @@ -1,80 +0,0 @@ -sendErrorResponse(PActions::GET_FOUND, "Invalid found query!"); - } - $this->checkToken(PActions::GET_FOUND, $QUERY); - - $hashlist = Factory::getHashlistFactory()->get($QUERY[PQueryGetFound::HASHLIST_ID]); - if ($hashlist == null) { - $this->sendErrorResponse(PActions::GET_FOUND, "Invalid hashlist!"); - } - - DServerLog::log(DServerLog::DEBUG, "Requesting founds for hashlist...", [$this->agent, $hashlist]); - - $qF = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); - if ($assignment == null) { - $this->sendErrorResponse(PActions::GET_FOUND, "Agent is not assigned to a task!"); - } - - $task = Factory::getTaskFactory()->get($assignment->getTaskId()); - if ($task == null) { - DServerLog::log(DServerLog::WARNING, "Assignment contained invalid task!", [$this->agent, $assignment]); - $this->sendErrorResponse(PActions::GET_FOUND, "Assignment contains invalid task!"); - } - - $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); - if ($taskWrapper == null) { - DServerLog::log(DServerLog::FATAL, "Inconsistency between taskWrapper and tasks!", [$this->agent, $task]); - $this->sendErrorResponse(PActions::GET_FOUND, "Inconsistent taskWrapper for task!"); - } - - if ($taskWrapper->getHashlistId() != $hashlist->getId()) { - DServerLog::log(DServerLog::WARNING, "Agent requested hashlist not used for task!", [$this->agent, $taskWrapper, $task, $hashlist]); - $this->sendErrorResponse(PActions::GET_FOUND, "This hashlist is not used for the assigned task!"); - } - else if ($this->agent->getIsTrusted() < $hashlist->getIsSecret()) { - $this->sendErrorResponse(PActions::GET_FOUND, "You have not access to this hashlist!"); - } - - $hashlists = Util::checkSuperHashlist($hashlist); - foreach ($hashlists as $hashlist) { - if ($hashlist->getIsSecret() > $this->agent->getIsTrusted()) { - $this->sendErrorResponse(PActions::GET_FOUND, "Agent would require to download secret hashlist with insufficient level!"); - } - } - - $this->updateAgent(PActions::GET_FOUND); - - if (sizeof($hashlists) == 0) { - $this->sendErrorResponse(PActions::GET_FOUND, "No hashlists selected/available!"); - } - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_FOUND, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetFound::URL => "getFound.php?hashlists=" . implode(",", Util::arrayOfIds($hashlists)) . "&token=" . $this->agent->getToken() - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetHashlist.php b/src/inc/api/APIGetHashlist.php deleted file mode 100644 index 9f01c0f3b..000000000 --- a/src/inc/api/APIGetHashlist.php +++ /dev/null @@ -1,80 +0,0 @@ -sendErrorResponse(PActions::GET_HASHLIST, "Invalid hashlist query!"); - } - $this->checkToken(PActions::GET_HASHLIST, $QUERY); - - $hashlist = Factory::getHashlistFactory()->get($QUERY[PQueryGetHashlist::HASHLIST_ID]); - if ($hashlist == null) { - $this->sendErrorResponse(PActions::GET_HASHLIST, "Invalid hashlist!"); - } - - DServerLog::log(DServerLog::DEBUG, "Requesting a hashlist...", [$this->agent, $hashlist]); - - $qF = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); - if ($assignment == null) { - $this->sendErrorResponse(PActions::GET_HASHLIST, "Agent is not assigned to a task!"); - } - - $task = Factory::getTaskFactory()->get($assignment->getTaskId()); - if ($task == null) { - DServerLog::log(DServerLog::WARNING, "Assignment contained invalid task!", [$this->agent, $assignment]); - $this->sendErrorResponse(PActions::GET_HASHLIST, "Assignment contains invalid task!"); - } - - $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); - if ($taskWrapper == null) { - DServerLog::log(DServerLog::FATAL, "Inconsistency between taskWrapper and tasks!", [$this->agent, $task]); - $this->sendErrorResponse(PActions::GET_HASHLIST, "Inconsistent taskWrapper for task!"); - } - - if ($taskWrapper->getHashlistId() != $hashlist->getId()) { - DServerLog::log(DServerLog::WARNING, "Agent requested hashlist not used for task!", [$this->agent, $taskWrapper, $task, $hashlist]); - $this->sendErrorResponse(PActions::GET_HASHLIST, "This hashlist is not used for the assigned task!"); - } - else if ($this->agent->getIsTrusted() < $hashlist->getIsSecret()) { - $this->sendErrorResponse(PActions::GET_HASHLIST, "You have not access to this hashlist!"); - } - - $hashlists = Util::checkSuperHashlist($hashlist); - foreach ($hashlists as $hashlist) { - if ($hashlist->getIsSecret() > $this->agent->getIsTrusted()) { - $this->sendErrorResponse(PActions::GET_HASHLIST, "Agent would require to download secret hashlist with insufficient level!"); - } - } - - $this->updateAgent(PActions::GET_HASHLIST); - - if (sizeof($hashlists) == 0) { - $this->sendErrorResponse(PActions::GET_HASHLIST, "No hashlists selected/available!"); - } - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_HASHLIST, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetHashlist::URL => "getHashlist.php?hashlists=" . implode(",", Util::arrayOfIds($hashlists)) . "&token=" . $this->agent->getToken() - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetHealthCheck.php b/src/inc/api/APIGetHealthCheck.php deleted file mode 100644 index 9fc63ffda..000000000 --- a/src/inc/api/APIGetHealthCheck.php +++ /dev/null @@ -1,51 +0,0 @@ -sendErrorResponse(PActions::GET_HEALTH_CHECK, "Invalid get health check query!"); - } - $this->checkToken(PActions::GET_HEALTH_CHECK, $QUERY); - $this->updateAgent(PActions::GET_HEALTH_CHECK); - - $healthCheckAgent = HealthUtils::checkNeeded($this->agent); - if ($healthCheckAgent == null) { - // for whatever reason there is no check available anymore - $this->sendErrorResponse(PActions::GET_HEALTH_CHECK, "No health check available for this agent!"); - } - $healthCheck = Factory::getHealthCheckFactory()->get($healthCheckAgent->getHealthCheckId()); - - DServerLog::log(DServerLog::INFO, "Received health check task", [$this->agent, $healthCheck]); - - $hashes = file_get_contents("/tmp/health-check-" . $healthCheck->getId() . ".txt"); - $hashes = explode("\n", $hashes); - - $this->sendResponse([ - PResponse::ACTION => PActions::GET_HEALTH_CHECK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetHealthCheck::ATTACK => " --hash-type=" . $healthCheck->getHashtypeId() . " " . $healthCheck->getAttackCmd() . " " . $this->agent->getCmdPars(), - PResponseGetHealthCheck::CRACKER_BINARY_ID => (int)$healthCheck->getCrackerBinaryId(), - PResponseGetHealthCheck::HASHES => $hashes, - PResponseGetHealthCheck::CHECK_ID => (int)$healthCheck->getId(), - PResponseGetHealthCheck::HASHLIST_ALIAS => SConfig::getInstance()->getVal(DConfig::HASHLIST_ALIAS) - ] - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIGetTask.php b/src/inc/api/APIGetTask.php deleted file mode 100644 index bf321cb0b..000000000 --- a/src/inc/api/APIGetTask.php +++ /dev/null @@ -1,210 +0,0 @@ -sendErrorResponse(PActions::GET_TASK, "Invalid task query!"); - } - $this->checkToken(PActions::GET_TASK, $QUERY); - $this->updateAgent(PActions::GET_TASK); - - DServerLog::log(DServerLog::TRACE, "Requesting a task...", [$this->agent]); - - if (HealthUtils::checkNeeded($this->agent)) { - DServerLog::log(DServerLog::INFO, "Notified about pending health check", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_TASK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetTask::TASK_ID => PValuesTask::HEALTH_CHECK - ) - ); - } - - if ($this->agent->getIsActive() == 0) { - DServerLog::log(DServerLog::TRACE, "Agent is inactive and cannot get a task", [$this->agent]); - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_TASK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetTask::TASK_ID => PValues::NONE, - PResponseGetTask::REASON => "Agent is inactive!" - ) - ); - } - - DServerLog::log(DServerLog::TRACE, "Searching for assignment and best task", [$this->agent]); - $qF = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF], true); - $task = TaskUtils::getBestTask($this->agent); - DServerLog::log(DServerLog::TRACE, "Search results", [$this->agent, $assignment, $task]); - if ($task == null) { - if ($assignment == null) { - // there is no best task available and nothing is assigned currently -> no task to assign - $this->noTask(); - } - else { - // check if the current assignment is fulfilled - $currentTask = Factory::getTaskFactory()->get($assignment->getTaskId()); - $currentTask = TaskUtils::checkTask($currentTask); - if ($currentTask == null) { - // we checked the task and it is completed - DServerLog::log(DServerLog::TRACE, "No best task available and current assigned task is fullfilled", [$this->agent]); - Factory::getAssignmentFactory()->delete($assignment); - $this->noTask(); - } - if (TaskUtils::isSaturatedByOtherAgents($currentTask, $this->agent)) { - Factory::getAssignmentFactory()->delete($assignment); - $this->noTask(); - } - // assignment is still good -> send this task - DServerLog::log(DServerLog::TRACE, "Current task is running, continue with this", [$this->agent, $currentTask]); - $this->sendTask($currentTask, $assignment); - } - } - else { - if ($assignment != null) { - // check if this assignment has not a high enough priority to be kept - $currentTask = Factory::getTaskFactory()->get($assignment->getTaskId()); - if ($currentTask == null) { - // current task is not available anymore, just send the new one - DServerLog::log(DServerLog::TRACE, "Current task does not exist anymore, send new one", [$this->agent, $task]); - $this->sendTask($task, $assignment); - } - // check if this task is fulfilled, or we still have the permission on it - $currentTask = TaskUtils::checkTask($currentTask); - if ($currentTask == null) { - // it got filtered out, just send new task - DServerLog::log(DServerLog::TRACE, "Current task is done or permissions changed, send new one", [$this->agent, $task]); - $this->sendTask($task, $assignment); - } - else { - if (TaskUtils::isSaturatedByOtherAgents($currentTask, $this->agent)) { - $this->sendTask($task, $assignment); - } - DServerLog::log(DServerLog::TRACE, "Current task is fine, send the more important one", [$this->agent, $currentTask, $task]); - $this->sendTask(TaskUtils::getImportantTask($task, $currentTask), $assignment); - } - } - else { - DServerLog::log(DServerLog::TRACE, "Task available, but nothing assigned, send new one", [$this->agent, $task]); - $this->sendTask($task, $assignment); - } - } - } - - private function noTask(): void { - $this->sendResponse(array( - PResponse::ACTION => PActions::GET_TASK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetTask::TASK_ID => PValues::NONE, - PResponseGetTask::REASON => "No suitable task available!" - ) - ); - } - - /** - * @param $task Task - * @param $assignment ?Assignment - * @throws Exception - */ - private function sendTask(Task $task, ?Assignment $assignment): void { - // check if the assignment is up-to-date and correct if needed - if ($assignment == null) { - $assignment = new Assignment(null, $task->getId(), $this->agent->getId(), 0); - $assignment = Factory::getAssignmentFactory()->save($assignment); - DServerLog::log(DServerLog::TRACE, "No assignment present, created", [$this->agent, $assignment]); - } - else { - if ($assignment->getTaskId() != $task->getId()) { - $qF = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); - DServerLog::log(DServerLog::TRACE, "Current task does not match assignment, delete it", [$this->agent, $assignment]); - $assignment = new Assignment(null, $task->getId(), $this->agent->getId(), 0); - $assignment = Factory::getAssignmentFactory()->save($assignment); - DServerLog::log(DServerLog::TRACE, "Created new assignment", [$this->agent, $assignment]); - } - } - - $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); - if ($taskWrapper == null) { - DServerLog::log(DServerLog::FATAL, "Inconsistency between taskWrapper and task", [$this->agent, $task]); - $this->sendErrorResponse(PActions::GET_TASK, "Inconsistent TaskWrapper information!"); - } - $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); - if ($hashlist == null) { - DServerLog::log(DServerLog::TRACE, "Inconsistency between taskWrapper and hashlist", [$this->agent, $taskWrapper]); - $this->sendErrorResponse(PActions::GET_TASK, "Inconsistent TaskWrapper-Hashlist information"); - } - - $taskFiles = array(); - $qF = new QueryFilter(FileTask::TASK_ID, $task->getId(), "=", Factory::getFileTaskFactory()); - $jF = new JoinFilter(Factory::getFileTaskFactory(), File::FILE_ID, FileTask::FILE_ID); - $joined = Factory::getFileFactory()->filter([Factory::FILTER => $qF, Factory::JOIN => $jF]); - /** @var File[] $files */ - $files = $joined[Factory::getFileFactory()->getModelName()]; - foreach ($files as $file) { - $taskFiles[] = $file->getFilename(); - } - - $hashtype = Factory::getHashTypeFactory()->get($hashlist->getHashTypeId()); - - DServerLog::log(DServerLog::TRACE, "Sending task to agent", [$this->agent, $task, $taskFiles]); - - $brain = $hashlist->getBrainId() && !$task->getForcePipe() && !$task->getUsePreprocessor(); - - $response = array( - PResponse::ACTION => PActions::GET_TASK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseGetTask::TASK_ID => (int)$task->getId(), - PResponseGetTask::ATTACK_COMMAND => $task->getAttackCmd(), - PResponseGetTask::CMD_PARAMETERS => " --hash-type=" . $hashlist->getHashTypeId() . " " . $this->agent->getCmdPars(), - PResponseGetTask::HASHLIST_ID => (int)$taskWrapper->getHashlistId(), - PResponseGetTask::BENCHMARK => (int)SConfig::getInstance()->getVal(DConfig::BENCHMARK_TIME), - PResponseGetTask::STATUS_TIMER => (int)$task->getStatusTimer(), - PResponseGetTask::FILES => $taskFiles, - PResponseGetTask::CRACKER_ID => $task->getCrackerBinaryId(), - PResponseGetTask::BENCHTYPE => ($task->getUseNewBench() == 1) ? "speed" : "run", - PResponseGetTask::HASHLIST_ALIAS => SConfig::getInstance()->getVal(DConfig::HASHLIST_ALIAS), - PResponseGetTask::KEYSPACE => $task->getKeyspace(), - PResponseGetTask::USE_PREPROCESSOR => (bool)$task->getUsePreprocessor(), - PResponseGetTask::PREPROCESSOR => $task->getUsePreprocessor(), - PResponseGetTask::PREPROCESSOR_COMMAND => $task->getPreprocessorCommand(), - PResponseGetTask::ENFORCE_PIPE => (bool)$task->getForcePipe(), - PResponseGetTask::SLOW_HASH => (bool)$hashtype->getIsSlowHash(), - PResponseGetTask::USE_BRAIN => $brain, - ); - - if ($brain) { - $response[PResponseGetTask::BRAIN_HOST] = SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_HOST); - $response[PResponseGetTask::BRAIN_PORT] = intval(SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_PORT)); - $response[PResponseGetTask::BRAIN_PASS] = SConfig::getInstance()->getVal(DConfig::HASHCAT_BRAIN_PASS); - $response[PResponseGetTask::BRAIN_FEATURES] = (int)$hashlist->getBrainFeatures(); - } - - $this->sendResponse($response); - } -} diff --git a/src/inc/api/APILogin.php b/src/inc/api/APILogin.php deleted file mode 100644 index 131882764..000000000 --- a/src/inc/api/APILogin.php +++ /dev/null @@ -1,42 +0,0 @@ -sendErrorResponse(PActions::LOGIN, "Invalid login query!"); - } - $this->checkToken(PActions::LOGIN, $QUERY); - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::CLIENT_SIGNATURE, htmlentities($QUERY[PQueryLogin::CLIENT_SIGNATURE], ENT_QUOTES, "UTF-8")); - $this->updateAgent(PActions::LOGIN); - - DServerLog::log(DServerLog::DEBUG, "Agent logged in", [$this->agent]); - - $this->sendResponse(array( - PResponse::ACTION => PActions::LOGIN, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseLogin::MULTICAST => (bool)SConfig::getInstance()->getVal(DConfig::MULTICAST_ENABLE), - PResponseLogin::TIMEOUT => (int)SConfig::getInstance()->getVal(DConfig::AGENT_TIMEOUT), - PResponseLogin::VERSION => StartupConfig::getInstance()->getVersion() . " (" . Util::getGitCommit() . ")" - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIRegisterAgent.php b/src/inc/api/APIRegisterAgent.php deleted file mode 100644 index 46b698e22..000000000 --- a/src/inc/api/APIRegisterAgent.php +++ /dev/null @@ -1,80 +0,0 @@ -sendErrorResponse(PActions::REGISTER, "Invalid registering query!"); - } - - $qF = new QueryFilter(RegVoucher::VOUCHER, $QUERY[PQueryRegister::VOUCHER], "="); - $voucher = Factory::getRegVoucherFactory()->filter([Factory::FILTER => $qF], true); - if ($voucher == null) { - $this->sendErrorResponse(PActions::REGISTER, "Provided voucher does not exist."); - } - - $name = htmlentities($QUERY[PQueryRegister::AGENT_NAME], ENT_QUOTES, "UTF-8"); - - $cpuOnly = 0; - if (isset($QUERY[PQueryRegister::CPU_ONLY]) && $QUERY[PQueryRegister::CPU_ONLY]) { - $cpuOnly = 1; - } - - //create access token & save agent details - $token = Util::randomString(10); - $agent = new Agent(null, $name, "", -1, "", "", 0, 1, 0, $token, PActions::REGISTER, time(), Util::getIP(), null, $cpuOnly, ""); - - if (SConfig::getInstance()->getVal(DConfig::VOUCHER_DELETION) == 0) { - Factory::getRegVoucherFactory()->delete($voucher); - } - $agent = Factory::getAgentFactory()->save($agent); - if ($agent != null) { - $payload = new DataSet(array(DPayloadKeys::AGENT => $agent)); - NotificationHandler::checkNotifications(DNotificationType::NEW_AGENT, $payload); - DServerLog::log(DServerLog::INFO, "Registered new agent", [$agent]); - - // assign agent to default group - $accessGroup = AccessUtils::getOrCreateDefaultAccessGroup(); - $accessGroupAgent = new AccessGroupAgent(null, $accessGroup->getId(), $agent->getId()); - Factory::getAccessGroupAgentFactory()->save($accessGroupAgent); - DServerLog::log(DServerLog::INFO, "Assigned agent to access group", [$agent, $accessGroup]); - - $this->sendResponse(array( - PResponse::ACTION => PActions::REGISTER, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseRegister::TOKEN => $token - ) - ); - } - else { - DServerLog::log(DServerLog::ERROR, "Saving of agent failed!", [$agent]); - $this->sendErrorResponse(PActions::REGISTER, "Could not register you to server: Saving failed!"); - } - } -} diff --git a/src/inc/api/APISendBenchmark.php b/src/inc/api/APISendBenchmark.php deleted file mode 100644 index 4ee4c3eff..000000000 --- a/src/inc/api/APISendBenchmark.php +++ /dev/null @@ -1,82 +0,0 @@ -sendErrorResponse(PActions::SEND_BENCHMARK, "Invalid benchmark query!"); - } - $this->checkToken(PActions::SEND_BENCHMARK, $QUERY); - $this->updateAgent(PActions::SEND_BENCHMARK); - - $task = Factory::getTaskFactory()->get($QUERY[PQuerySendBenchmark::TASK_ID]); - if ($task == null) { - $this->sendErrorResponse(PActions::SEND_BENCHMARK, "Invalid task ID!"); - } - - $qF1 = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($assignment == null) { - $this->sendErrorResponse(PActions::SEND_BENCHMARK, "You are not assigned to this task!"); - } - - $type = $QUERY[PQuerySendBenchmark::TYPE]; - $benchmark = $QUERY[PQuerySendBenchmark::RESULT]; - - DServerLog::log(DServerLog::TRACE, "Agent sending benchmark", [$this->agent, $task, $type, $benchmark]); - - switch ($type) { - case PValuesBenchmarkType::SPEED_TEST: - $split = explode(":", $benchmark); - if (sizeof($split) != 2 || !is_numeric($split[0]) || !is_numeric($split[1]) || $split[0] <= 0 || $split[1] <= 0) { - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::IS_ACTIVE, 0); - DServerLog::log(DServerLog::ERROR, "Invalid speed test benchmark result!", [$this->agent, $benchmark]); - $this->sendErrorResponse(PActions::SEND_BENCHMARK, "Invalid benchmark result!"); - } - break; - case PValuesBenchmarkType::RUN_TIME: - if (!is_numeric($benchmark) || $benchmark <= 0) { - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::IS_ACTIVE, 0); - DServerLog::log(DServerLog::ERROR, "Invalid benchmark results for runtime benchmark", [$this->agent, $task, $benchmark]); - $this->sendErrorResponse(PActions::SEND_BENCHMARK, "Invalid benchmark result!"); - } - // normalize time of the benchmark to 100 seconds - $benchmark = $benchmark / SConfig::getInstance()->getVal(DConfig::BENCHMARK_TIME) * 100; - DServerLog::log(DServerLog::TRACE, "Saving normalized runtime benchmark", [$this->agent, $task, $benchmark]); - break; - default: - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::IS_ACTIVE, 0); - $this->sendErrorResponse(PActions::SEND_BENCHMARK, "Invalid benchmark type!"); - } - - $assignment->setBenchmark($benchmark); - Factory::getAssignmentFactory()->update($assignment); - DServerLog::log(DServerLog::DEBUG, "Saved agent benchmark", [$this->agent, $task, $assignment]); - $this->sendResponse(array( - PResponse::ACTION => PActions::SEND_BENCHMARK, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseSendBenchmark::BENCHMARK => PValues::OK - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APISendHealthCheck.php b/src/inc/api/APISendHealthCheck.php deleted file mode 100644 index 4f3d26f15..000000000 --- a/src/inc/api/APISendHealthCheck.php +++ /dev/null @@ -1,77 +0,0 @@ -sendErrorResponse(PActions::SEND_HEALTH_CHECK, "Invalid send health check query!"); - } - $this->checkToken(PActions::SEND_HEALTH_CHECK, $QUERY); - $this->updateAgent(PActions::SEND_HEALTH_CHECK); - - $healthCheck = Factory::getHealthCheckFactory()->get($QUERY[PQuerySendHealthCheck::CHECK_ID]); - if ($healthCheck == null) { - // for whatever reason there is no check available anymore - $this->sendErrorResponse(PActions::SEND_HEALTH_CHECK, "Invalid health check id!"); - } - $qF1 = new QueryFilter(HealthCheckAgent::HEALTH_CHECK_ID, $healthCheck->getId(), "="); - $qF2 = new QueryFilter(HealthCheckAgent::AGENT_ID, $this->agent->getId(), "="); - $healthCheckAgent = Factory::getHealthCheckAgentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($healthCheckAgent == null) { - // for whatever reason there is no check available anymore - $this->sendErrorResponse(PActions::SEND_HEALTH_CHECK, "Invalid health check agent id!"); - } - - $numCracked = intval($QUERY[PQuerySendHealthCheck::NUM_CRACKED]); - $numGpus = intval($QUERY[PQuerySendHealthCheck::NUM_GPUS]); - $errors = $QUERY[PQuerySendHealthCheck::ERRORS]; - $start = intval($QUERY[PQuerySendHealthCheck::START]); - $end = intval($QUERY[PQuerySendHealthCheck::END]); - - if (!is_array($errors)) { - $errors = [$errors]; - } - - $status = DHealthCheckAgentStatus::COMPLETED; - if (sizeof($errors) > 0 && $this->agent->getIgnoreErrors() == DAgentIgnoreErrors::NO) { - $status = DHealthCheckAgentStatus::FAILED; - } - else if ($numCracked != $healthCheck->getExpectedCracks()) { - $status = DHealthCheckAgentStatus::FAILED; - } - - $healthCheckAgent->setCracked($numCracked); - $healthCheckAgent->setNumGpus($numGpus); - $healthCheckAgent->setErrors(json_encode($errors)); - $healthCheckAgent->setStart($start); - $healthCheckAgent->setEnd($end); - $healthCheckAgent->setStatus($status); - Factory::getHealthCheckAgentFactory()->update($healthCheckAgent); - - DServerLog::log(DServerLog::DEBUG, "Agent sent health check results", [$this->agent, $healthCheck, $healthCheckAgent]); - - HealthUtils::checkCompletion($healthCheck); - $this->sendResponse([ - PResponse::ACTION => PActions::SEND_HEALTH_CHECK, - PResponse::RESPONSE => PValues::SUCCESS - ] - ); - } -} diff --git a/src/inc/api/APISendKeyspace.php b/src/inc/api/APISendKeyspace.php deleted file mode 100644 index 4a1d0f068..000000000 --- a/src/inc/api/APISendKeyspace.php +++ /dev/null @@ -1,82 +0,0 @@ -sendErrorResponse(PActions::SEND_KEYSPACE, "Invalid keyspace query!"); - } - $this->checkToken(PActions::SEND_KEYSPACE, $QUERY); - $this->updateAgent(PActions::SEND_KEYSPACE); - - $keyspace = intval($QUERY[PQuerySendKeyspace::KEYSPACE]); - - $task = Factory::getTaskFactory()->get($QUERY[PQuerySendKeyspace::TASK_ID]); - if ($task == null) { - $this->sendErrorResponse(PActions::SEND_KEYSPACE, "Invalid task ID!"); - } - - DServerLog::log(DServerLog::TRACE, "Agent sending keyspace...", [$this->agent, $task]); - - $qF1 = new QueryFilter(Assignment::AGENT_ID, $this->agent->getId(), "="); - $qF2 = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]], true); - if ($assignment == null) { - DServerLog::log(DServerLog::TRACE, "Agent not assigned to task to send keyspace", [$this->agent]); - $this->sendErrorResponse(PActions::SEND_KEYSPACE, "You are not assigned to this task!"); - } - - if ($task->getKeyspace() == 0) { - // keyspace is still required - if ($task->getUsePreprocessor() && $keyspace == -1) { - // this is the case when the keyspace gets too large, but we still accept it - DServerLog::log(DServerLog::TRACE, "Keyspace is too large to save, we set it to a specific number", [$this->agent]); - $keyspace = DPrince::PRINCE_KEYSPACE; - } - else if ($keyspace < 0) { - DServerLog::log(DServerLog::WARNING, "Keyspace is negative, most likely due to 32bit server", [$this->agent, $keyspace]); - $this->sendErrorResponse(PActions::SEND_KEYSPACE, "Server parsed a negative keyspace, it's very likely that the number was too big to be handled by the server system!"); - } - - $task = Factory::getTaskFactory()->set($task, Task::KEYSPACE, $keyspace); - DServerLog::log(DServerLog::TRACE, "Keyspace saved", [$this->agent, $task]); - } - - // test if the task may have a skip value which is too high for this keyspace - if ($task->getSkipKeyspace() > $task->getKeyspace() && $task->getKeyspace() != DPrince::PRINCE_KEYSPACE) { - // skip is too high - DServerLog::log(DServerLog::ERROR, "Task skip value is too high, putting task inactive!", [$this->agent, $task]); - $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); - $qF = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); - Util::createLogEntry(DLogEntryIssuer::API, $this->agent->getToken(), DLogEntry::ERROR, "Task with ID " . $task->getId() . " has set a skip value which is too high for its keyspace!"); - } - - $this->sendResponse(array( - PResponse::ACTION => PActions::SEND_KEYSPACE, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseSendKeyspace::KEYSPACE => PValues::OK - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APISendProgress.php b/src/inc/api/APISendProgress.php deleted file mode 100644 index 0af0c5a15..000000000 --- a/src/inc/api/APISendProgress.php +++ /dev/null @@ -1,590 +0,0 @@ -sendErrorResponse(PActions::SEND_PROGRESS, "Invalid progress query!"); - } - $this->checkToken(PActions::SEND_PROGRESS, $QUERY); - $this->updateAgent(PActions::SEND_PROGRESS); - - // upload cracked hashes to server - $keyspaceProgress = $QUERY[PQuerySendProgress::KEYSPACE_PROGRESS]; - $relativeProgress = intval($QUERY[PQuerySendProgress::RELATIVE_PROGRESS]);//Normalized between 1-10k - $speed = intval($QUERY[PQuerySendProgress::SPEED]); - $state = intval($QUERY[PQuerySendProgress::HASHCAT_STATE]); - - DServerLog::log(DServerLog::TRACE, "Agent sending progress", [$this->agent]); - - $chunk = Factory::getChunkFactory()->get(intval($QUERY[PQuerySendProgress::CHUNK_ID])); - if ($chunk == null) { - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Invalid chunk id " . intval($QUERY[PQuerySendProgress::CHUNK_ID])); - } - else if ($chunk->getAgentId() != $this->agent->getId()) { - $this->sendErrorResponse(PActions::SEND_PROGRESS, "You are not assigned to this chunk"); - } - else if ($this->agent->getIsActive() == 0) { - $chunk = Factory::getChunkFactory()->set($chunk, Chunk::SPEED, 0); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Agent is marked inactive!"); - } - - DServerLog::log(DServerLog::TRACE, "Agent is assigned to this chunk and active", [$this->agent, $chunk]); - - $task = Factory::getTaskFactory()->get($chunk->getTaskId()); - if ($task == null) { - DServerLog::log(DServerLog::ERROR, "Inconsistency between chunk and task!", [$this->agent, $chunk]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "No task exists for the given chunk"); - } - else if ($task->getIsArchived() == 1) { - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Task is archived, no work to do"); - } - $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); - if ($taskWrapper == null) { - DServerLog::log(DServerLog::ERROR, "Inconsistency between task and taskWrapper!", [$this->agent, $task]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Inconsistency error on taskWrapper"); - } - - DServerLog::log(DServerLog::TRACE, "Agent working on valid task", [$this->agent, $task]); - - $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); - if ($hashlist == null) { - DServerLog::log(DServerLog::ERROR, "Task is not having a valid hashlist!", [$this->agent, $task]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "The given task does not have a corresponding hashlist!"); - } - $totalHashlist = $hashlist; - $isSuperhashlist = false; - if ($hashlist->getFormat() == DHashlistFormat::SUPERHASHLIST) { - $isSuperhashlist = true; - } - $hashlists = Util::checkSuperHashlist($hashlist); - foreach ($hashlists as $hl) { - if ($hl->getIsSecret() > $this->agent->getIsTrusted()) { - DServerLog::log(DServerLog::TRACE, "For some reason agent was working on a hashlist he is not allowed to (probabily permission change)", [$this->agent, $task, $hl]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Unknown Error. The API does not trust you with more information"); - } - } - - DServerLog::log(DServerLog::TRACE, "Agent working on correct hashlist(s)", [$this->agent, $totalHashlist]); - - $dataTime = time(); - if (isset($QUERY[PQuerySendProgress::GPU_TEMP])) { - for ($i = 0; $i < sizeof($QUERY[PQuerySendProgress::GPU_TEMP]); $i++) { - if (!is_numeric($QUERY[PQuerySendProgress::GPU_TEMP][$i]) || $QUERY[PQuerySendProgress::GPU_TEMP][$i] <= 0) { - unset($QUERY[PQuerySendProgress::GPU_TEMP][$i]); - } - } - if (sizeof($QUERY[PQuerySendProgress::GPU_TEMP]) > 0) { - $data = implode(",", $QUERY[PQuerySendProgress::GPU_TEMP]); - $agentStat = new AgentStat(null, $this->agent->getId(), DAgentStatsType::GPU_TEMP, $dataTime, $data); - Factory::getAgentStatFactory()->save($agentStat); - } - } - if (isset($QUERY[PQuerySendProgress::GPU_UTIL])) { - for ($i = 0; $i < sizeof($QUERY[PQuerySendProgress::GPU_UTIL]); $i++) { - if (!is_numeric($QUERY[PQuerySendProgress::GPU_UTIL][$i]) || $QUERY[PQuerySendProgress::GPU_UTIL][$i] < 0) { - unset($QUERY[PQuerySendProgress::GPU_UTIL][$i]); - } - } - if (sizeof($QUERY[PQuerySendProgress::GPU_UTIL]) > 0) { - $data = implode(",", $QUERY[PQuerySendProgress::GPU_UTIL]); - $agentStat = new AgentStat(null, $this->agent->getId(), DAgentStatsType::GPU_UTIL, $dataTime, $data); - Factory::getAgentStatFactory()->save($agentStat); - } - } - if (isset($QUERY[PQuerySendProgress::CPU_UTIL])) { - for ($i = 0; $i < sizeof($QUERY[PQuerySendProgress::CPU_UTIL]); $i++) { - if (!is_numeric($QUERY[PQuerySendProgress::CPU_UTIL][$i]) || $QUERY[PQuerySendProgress::CPU_UTIL][$i] < 0) { - unset($QUERY[PQuerySendProgress::CPU_UTIL][$i]); - } - } - if (sizeof($QUERY[PQuerySendProgress::CPU_UTIL]) > 0) { - $data = implode(",", $QUERY[PQuerySendProgress::CPU_UTIL]); - $agentStat = new AgentStat(null, $this->agent->getId(), DAgentStatsType::CPU_UTIL, $dataTime, $data); - Factory::getAgentStatFactory()->save($agentStat); - } - } - - // agent is assigned to this chunk (not necessarily task!) - // it can be already assigned to other task, but is still computing this chunk until it realizes it - $skip = $chunk->getSkip(); - $length = $chunk->getLength(); - $taskID = $task->getId(); - - //if by accident the number of the combinationProgress overshoots the limit - if ($relativeProgress > 10000) { - $relativeProgress = 10000; - } - if ($keyspaceProgress > $length + $skip) { - $keyspaceProgress = $length + $skip; - } - - /* - * Save Debug output if provided - */ - if (isset($QUERY[PQuerySendProgress::DEBUG_OUTPUT])) { - $lines = $QUERY[PQuerySendProgress::DEBUG_OUTPUT]; - $taskDebugOutputs = []; - foreach ($lines as $line) { - $taskDebugOutputs[] = new TaskDebugOutput(null, $chunk->getTaskId(), $line); - } - if (sizeof($taskDebugOutputs) > 0) { - Factory::getTaskDebugOutputFactory()->massSave($taskDebugOutputs); - } - } - - /* - * Save chunk updates - */ - $aborting = false; - if ($chunk->getState() == DHashcatStatus::ABORTED) { - DServerLog::log(DServerLog::TRACE, "Chunk was aborted, we need to stop afterwards", [$this->agent]); - $aborting = true; - } - $chunk = Factory::getChunkFactory()->mset($chunk, [ - Chunk::PROGRESS => $relativeProgress, - Chunk::CHECKPOINT => $keyspaceProgress, - Chunk::SOLVE_TIME => time(), - Chunk::STATE => $state - ] - ); - DServerLog::log(DServerLog::TRACE, "Progress updated chunk", [$this->agent, $chunk]); - - $format = $hashlists[0]->getFormat(); - - // reset values - $skipped = 0; - $cracked = array(); - foreach ($hashlists as $hl) { - $cracked[$hl->getId()] = 0; - } - - // process solved hashes, should there be any - $crackedHashes = $QUERY[PQuerySendProgress::CRACKED_HASHES]; - Factory::getAgentFactory()->getDB()->beginTransaction(); - - $plainUpdates = []; - $crackPosUpdates = []; - $crackHashes = []; - $timeUpdates = []; - $zaps = []; - - $isNewWPA = false; - foreach ($hashlists as $hl) { - if ($hl->getHashTypeId() == 22000) { - $isNewWPA = true; - break; - } - } - - for ($i = 0; $i < sizeof($crackedHashes); $i++) { - // hash[:salt]:plain:hex_plain:crack_pos - $crackedHash = $crackedHashes[$i]; - if (!is_array($crackedHash) && $crackedHash == "") { - continue; - } - else if (!is_array($crackedHash)) { - // this is here for compatibility with older client versions - $splitLine = explode(SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR), $crackedHash); - $splitLine[] = ''; // for hex plain - $splitLine[] = -1; // for crack pos - } - else { - $splitLine = $crackedHash; - } - switch ($format) { - case DHashlistFormat::PLAIN: - if ($isNewWPA) { // special 22000 handle, hashcat prints it completely different than the input looks like - $split = explode(":", $splitLine[0]); - if (sizeof($split) == 4) { // this format was sent up to (and including) release 5.1.0 for -m 2500 - $split[3] = Util::strToHex($split[3]); - $identifier = "WPA*%*" . implode("*", $split) . "%"; - } - else{ - $skipped++; - break; - } - $qF1 = new LikeFilterInsensitive(Hash::HASH, $identifier); - } - else { // we use the exact match for all other hashes to avoid performance loss - $qF1 = new QueryFilter(Hash::HASH, $splitLine[0], "="); - } - - $qF2 = new ContainFilter(Hash::HASHLIST_ID, Util::arrayOfIds($hashlists)); - $qF3 = new QueryFilter(Hash::IS_CRACKED, 0, "="); - $hashes = Factory::getHashFactory()->filter([Factory::FILTER => [$qF1, $qF2, $qF3]]); - if (sizeof($hashes) == 0) { - // check without IS_CRACKED=0 to check if the hash was already cracked, or if it really cannot be found (which then triggers the log entry) - $check = Factory::getHashFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); - if (sizeof($check) == 0) { - //This can happen if agent rebuild the hash incorrectly - //Log the skipped hash so that admin can spot this false negative - $logMessage = "Hash has been cracked but skipped! This happened while cracking hashlist with ID: " - . $hashlist->getId() . " during chunk with ID: " . $chunk->getId() . " This happens when the agent returns - a cracked hash that does not exist in the database. This can happen when hashcat malforms the hash."; - Util::createLogEntry(DLogEntryIssuer::API, $this->agent->getToken(), DLogEntry::FATAL, $logMessage); - DServerLog::log(DServerLog::FATAL, $logMessage); - } - - $skipped++; - break; - } - else if (sizeof($splitLine) == 5) { - $plain = $splitLine[2]; // if hash is salted - $crackPos = intval($splitLine[4]); - } - else { - $plain = $splitLine[1]; - $crackPos = intval($splitLine[3]); - } - - foreach ($hashes as $hash) { - $cracked[$hash->getHashlistId()]++; - $plainUpdates[] = new MassUpdateSet($hash->getId(), $plain); - $crackPosUpdates[] = new MassUpdateSet($hash->getId(), $crackPos); - $timeUpdates[] = new MassUpdateSet($hash->getId(), time()); - $crackHashes[] = $hash->getId(); - $zaps[] = new Zap(null, $hash->getHash(), time(), $this->agent->getId(), $totalHashlist->getId()); - } - - if (sizeof($plainUpdates) >= 1000) { - $uS1 = new UpdateSet(Hash::CHUNK_ID, $chunk->getId()); - $uS2 = new UpdateSet(Hash::IS_CRACKED, 1); - $qF = new ContainFilter(Hash::HASH_ID, $crackHashes); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::PLAINTEXT, $plainUpdates); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::CRACK_POS, $crackPosUpdates); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::TIME_CRACKED, $timeUpdates); - Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS1, Factory::FILTER => $qF]); - Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS2, Factory::FILTER => $qF]); - Factory::getZapFactory()->massSave($zaps); - Factory::getAgentFactory()->getDB()->commit(); - Factory::getAgentFactory()->getDB()->beginTransaction(); - $zaps = array(); - $plainUpdates = array(); - $crackHashes = array(); - } - break; - case DHashlistFormat::WPA: - // save cracked wpa / pmkid password - // possible results: - // HCCAPX: [a895f7d62ccc3e892fa9e9f9146232c1:]aef50f22801c:987bdcf9f950:8381533406003807685881523 hashcat! 6861736863617421 12 - // PMKID (16800): 4604ba734d4e:89acf0e761f4:$HEX[ed487162465a774bfba60eb603a39f3a] hashcat! 6861736863617421 31 - // PMKID (16801): 4604ba734d4e:89acf0e761f4 5b13d4babb3714ccc62c9f71864bc984efd6a55f237c7a87fc2151e1ca658a9- 3562313364346261626233373134636363363263396637313836346263393834656664366135356632333763376138376663323135316531636136353861392d 14 - $split = explode(":", $splitLine[0]); - if (sizeof($split) == 4) { // this format was sent up to (and including) release 5.1.0 for -m 2500 - $mac_ap = $split[1]; - $mac_cli = $split[2]; - $essid = $split[3]; - } - else if (sizeof($split) == 3) { // this format is used in the current state of hashcat for -m 2500,16800 - $mac_ap = $split[0]; - $mac_cli = $split[1]; - $essid = $split[2]; - } - else { // this format is used for -m 16801 - $mac_ap = $split[0]; - $mac_cli = $split[1]; - $essid = ""; - } - if (Util::startsWith($essid, '$HEX[') && Util::endsWith($essid, "]") && strlen($essid) % 2 == 0) { - $essid = substr($essid, 5, strlen($essid) - 6); - } - else if (sizeof($split) < 4) { // for the new formats, if the SSID is not given in hex, we need to convert it back, as the input is always in hex - $essid = Util::strToHex($essid); - } - $identification = $mac_ap . SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . $mac_cli; - if (sizeof($split) > 2) { - $identification .= SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . $essid; - } - $plain = $splitLine[1]; - $crackPos = intval($splitLine[3]); - $qF1 = new QueryFilter(HashBinary::ESSID, $identification, "="); - $qF2 = new QueryFilter(HashBinary::IS_CRACKED, 0, "="); - $hashes = Factory::getHashBinaryFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); - if (sizeof($hashes) == 0) { - $skipped++; - } - foreach ($hashes as $hash) { - $cracked[$hash->getHashlistId()]++; - $hash->setIsCracked(1); - $hash->setChunkId($chunk->getId()); - $hash->setPlaintext($plain); - $hash->setCrackPos($crackPos); - $hash->setTimeCracked(time()); - Factory::getHashBinaryFactory()->update($hash); - } - break; - case DHashlistFormat::BINARY: - // save binary password - // result sent: ..\hashcat_luks_testfiles\luks_tests\hashcat_ripemd160_aes_cbc-essiv_128.luks:hashcat:68617368636174:12 - $plain = $splitLine[1]; - $crackPos = intval($splitLine[3]); - $qF1 = new QueryFilter(HashBinary::HASHLIST_ID, $totalHashlist->getId(), "="); - $qF2 = new QueryFilter(HashBinary::IS_CRACKED, 0, "="); - $hashes = Factory::getHashBinaryFactory()->filter([Factory::FILTER => [$qF1, $qF2]]); - if (sizeof($hashes) == 0) { - $skipped++; - } - foreach ($hashes as $hash) { - $cracked[$hash->getHashlistId()]++; - $hash->setIsCracked(1); - $hash->setChunkId($chunk->getId()); - $hash->setPlaintext($plain); - $hash->setCrackPos($crackPos); - $hash->setTimeCracked(time()); - Factory::getHashBinaryFactory()->update($hash); - } - break; - } - } - if ($format == DHashlistFormat::PLAIN && sizeof($plainUpdates) > 0) { - $uS1 = new UpdateSet(Hash::CHUNK_ID, $chunk->getId()); - $uS2 = new UpdateSet(Hash::IS_CRACKED, 1); - $qF = new ContainFilter(Hash::HASH_ID, $crackHashes); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::PLAINTEXT, $plainUpdates); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::CRACK_POS, $crackPosUpdates); - Factory::getHashFactory()->massSingleUpdate(Hash::HASH_ID, Hash::TIME_CRACKED, $timeUpdates); - Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS1, Factory::FILTER => $qF]); - Factory::getHashFactory()->massUpdate([Factory::UPDATE => $uS2, Factory::FILTER => $qF]); - Factory::getZapFactory()->massSave($zaps); - } - - Factory::getAgentFactory()->getDB()->commit(); - - //insert #Cracked hashes and update in hashlist how many hashes were cracked - Factory::getAgentFactory()->getDB()->beginTransaction(); - $sumCracked = 0; - foreach ($cracked as $listId => $cracks) { - $list = Factory::getHashlistFactory()->get($listId); - if ($cracks > 0) { - Factory::getHashlistFactory()->inc($list, Hashlist::CRACKED, $cracks); - } - - if (!$isSuperhashlist) { - // check if it is part of one or more superhashlists and if yes, update the count there as well - $superHashlists = Util::getParentSuperHashlists($list); - foreach ($superHashlists as $superHashlist) { - if ($cracks > 0) { - Factory::getHashlistFactory()->inc($superHashlist, Hashlist::CRACKED, $cracks); - } - } - } - - $sumCracked += $cracks; - } - - if ($sumCracked > 0) { - Factory::getChunkFactory()->inc($chunk, Chunk::CRACKED, $sumCracked); - } - - if ($isSuperhashlist && $sumCracked > 0) { - // if it's a superhashlist, we need to update the count for the superhashlist as well - $hashlist = Factory::getHashlistFactory()->get($taskWrapper->getHashlistId()); - Factory::getHashlistFactory()->inc($hashlist, Hashlist::CRACKED, $sumCracked); - } - Factory::getAgentFactory()->getDB()->commit(); - - DServerLog::log(DServerLog::TRACE, "Updated with received cracks", [$this->agent, $chunk]); - - assert($chunk instanceof Chunk); - if ($chunk->getState() == DHashcatStatus::STATUS_ABORTED_RUNTIME) { - // the chunk was manually interrupted - $chunk = Factory::getChunkFactory()->set($chunk, Chunk::STATE, DHashcatStatus::ABORTED); - DServerLog::log(DServerLog::TRACE, "Chunk was manually interrupted", [$this->agent]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Chunk was manually interrupted."); - } - /** Check if the task is done */ - $taskdone = false; - if ($relativeProgress == 10000 && $task->getKeyspaceProgress() == $task->getKeyspace()) { - // chunk is done and the task has been fully dispatched - $incompleteFilter = new QueryFilter(Chunk::PROGRESS, 10000, "<"); - $taskFilter = new QueryFilter(Chunk::TASK_ID, $taskID, "="); - $count = Factory::getChunkFactory()->countFilter([Factory::FILTER => [$incompleteFilter, $taskFilter]]); - $incompleteFilter = new QueryFilter(Chunk::PROGRESS, null, "="); - $count += Factory::getChunkFactory()->countFilter([Factory::FILTER => [$incompleteFilter, $taskFilter]]); - if ($count == 0) { - // this was the last incomplete chunk! - $taskdone = true; - DServerLog::log(DServerLog::INFO, "Chunk is the last one and is completed and keyspace is reached", [$this->agent, $task, $chunk]); - } - } - - if ($taskdone) { - // task is fully dispatched and this last chunk is done, deprioritize it - $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); - - if ($taskWrapper->getTaskType() == DTaskTypes::SUPERTASK) { - // check if the task wrapper is a supertask and is completed - if (Util::checkTaskWrapperCompleted($taskWrapper)) { - $taskWrapper = Factory::getTaskWrapperFactory()->set($taskWrapper, TaskWrapper::PRIORITY, 0); - } - } - else { - $taskWrapper = Factory::getTaskWrapperFactory()->set($taskWrapper, TaskWrapper::PRIORITY, 0); - } - DServerLog::log(DServerLog::TRACE, "As task is done, finished it and updated taskWrapper", [$this->agent, $task, $taskWrapper]); - - $payload = new DataSet(array(DPayloadKeys::TASK => $task)); - NotificationHandler::checkNotifications(DNotificationType::TASK_COMPLETE, $payload); - } - - $toZap = array(); - - if ($sumCracked > 0) { - $payload = new DataSet(array(DPayloadKeys::NUM_CRACKED => $sumCracked, DPayloadKeys::AGENT => $this->agent, DPayloadKeys::TASK => $task, DPayloadKeys::HASHLIST => $totalHashlist)); - NotificationHandler::checkNotifications(DNotificationType::HASHLIST_CRACKED_HASH, $payload); - - Factory::getTaskWrapperFactory()->inc($taskWrapper, TaskWrapper::CRACKED, $sumCracked); - } - - if ($aborting) { - $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::SPEED => 0, Chunk::STATE => DHashcatStatus::ABORTED]); - DServerLog::log(DServerLog::TRACE, "From earlier setting, chunk needed to be aborted.", [$this->agent, $chunk]); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Chunk was aborted!"); - } - - switch ($state) { - case DHashcatStatus::EXHAUSTED: - // the chunk has finished (exhausted) - $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::SPEED => 0, Chunk::PROGRESS => 10000, Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength()]); - DServerLog::log(DServerLog::TRACE, "Chunk is exhausted (cracker status)", [$this->agent, $chunk]); - break; - case DHashcatStatus::CRACKED: - // the chunk has finished (cracked whole hashList) - // de-prioritize all tasks and un-assign all agents - $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength(), Chunk::PROGRESS => 10000, Chunk::SPEED => 0]); - DServerLog::log(DServerLog::TRACE, "Last hash was cracked (cracker status)", [$this->agent, $chunk]); - - TaskUtils::depriorizeAllTasks($hashlists); - TaskUtils::unassignAllAgents($hashlists); - DServerLog::log(DServerLog::TRACE, "Depriorized all tasks of the hashlist and unassigned all agents", [$this->agent, $totalHashlist]); - - $payload = new DataSet(array(DPayloadKeys::HASHLIST => $totalHashlist)); - NotificationHandler::checkNotifications(DNotificationType::HASHLIST_ALL_CRACKED, $payload); - break; - case DHashcatStatus::ABORTED: - case DHashcatStatus::QUIT: - // the chunk was aborted or quit - $chunk = Factory::getChunkFactory()->set($chunk, Chunk::SPEED, 0); - $this->sendErrorResponse(PActions::SEND_PROGRESS, "Chunk was aborted!"); - case DHashcatStatus::RUNNING: - default: - // the chunk isn't finished yet, we will send zaps - $qF1 = new ComparisonFilter(Hashlist::CRACKED, Hashlist::HASH_COUNT, "<"); - $qF2 = new ContainFilter(Hashlist::HASHLIST_ID, Util::arrayOfIds($hashlists)); - $count = Factory::getHashlistFactory()->countFilter([Factory::FILTER => [$qF1, $qF2]]); - if ($count == 0) { - $payload = new DataSet(array(DPayloadKeys::HASHLIST => $totalHashlist)); - NotificationHandler::checkNotifications(DNotificationType::HASHLIST_ALL_CRACKED, $payload); - DServerLog::log(DServerLog::TRACE, "Agent still is running, but all hashes got cracked (all agents together), stop it", [$this->agent]); - - $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength(), Chunk::PROGRESS => 10000, Chunk::SPEED => 0]); - TaskUtils::depriorizeAllTasks($hashlists); - - $qF = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); - Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); - - $task = Factory::getTaskFactory()->set($task, Task::PRIORITY, 0); - DServerLog::log(DServerLog::TRACE, "Depriorized all tasks and updated", [$this->agent, $task, $chunk, $totalHashlist]); - - //stop agent - $this->sendResponse(array( - PResponse::ACTION => PActions::SEND_PROGRESS, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseSendProgress::NUM_CRACKED => $sumCracked, - PResponseSendProgress::NUM_SKIPPED => $skipped, - PResponseSendProgress::AGENT_COMMAND => "stop" - ) - ); - } - $chunk = Factory::getChunkFactory()->set($chunk, Chunk::SPEED, $speed); - - // save speed in history - if ($speed > 0) { - $s = new Speed(null, $this->agent->getId(), $task->getId(), $speed, time()); - Factory::getSpeedFactory()->save($s); - } - - $qF = new QueryFilter(AgentZap::AGENT_ID, $this->agent->getId(), "="); - $agentZap = Factory::getAgentZapFactory()->filter([Factory::FILTER => $qF], true); - if ($agentZap == null) { - $agentZap = new AgentZap(null, $this->agent->getId(), null); - Factory::getAgentZapFactory()->save($agentZap); - } - - $qF1 = new ContainFilter(Zap::HASHLIST_ID, Util::arrayOfIds($hashlists)); - $qF2 = new QueryFilter(Zap::ZAP_ID, ($agentZap->getLastZapId() == null) ? 0 : $agentZap->getLastZapId(), ">"); - $qF3 = new QueryFilterWithNull(Zap::AGENT_ID, $this->agent->getId(), "<>", true); - $zaps = Factory::getZapFactory()->filter([Factory::FILTER => [$qF1, $qF2, $qF3]]); - foreach ($zaps as $zap) { - if ($zap->getId() > $agentZap->getId()) { - $agentZap->setLastZapId($zap->getId()); - } - $toZap[] = $zap->getHash(); - } - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::LAST_TIME, time()); - if ($agentZap->getLastZapId() > 0) { - Factory::getAgentZapFactory()->update($agentZap); - } - - DServerLog::log(DServerLog::TRACE, "Checked zaps and sending new ones to agent", [$this->agent, $zaps]); - break; - } - Util::cleaning(); - $this->sendResponse(array( - PResponse::ACTION => PActions::SEND_PROGRESS, - PResponse::RESPONSE => PValues::SUCCESS, - PResponseSendProgress::NUM_CRACKED => $sumCracked, - PResponseSendProgress::NUM_SKIPPED => $skipped, - PResponseSendProgress::HASH_ZAPS => $toZap - ) - ); - } -} diff --git a/src/inc/api/APITestConnection.php b/src/inc/api/APITestConnection.php deleted file mode 100644 index 94554ab8a..000000000 --- a/src/inc/api/APITestConnection.php +++ /dev/null @@ -1,18 +0,0 @@ -sendResponse(array( - PResponse::ACTION => PActions::TEST_CONNECTION, - PResponse::RESPONSE => PValues::SUCCESS - ) - ); - } -} \ No newline at end of file diff --git a/src/inc/api/APIUpdateClientInformation.php b/src/inc/api/APIUpdateClientInformation.php deleted file mode 100644 index f23112f26..000000000 --- a/src/inc/api/APIUpdateClientInformation.php +++ /dev/null @@ -1,59 +0,0 @@ -sendErrorResponse(PActions::UPDATE_CLIENT_INFORMATION, "Invalid update query!"); - } - $this->checkToken(PActions::UPDATE_CLIENT_INFORMATION, $QUERY); - - $devices = $QUERY[PQueryUpdateInformation::DEVICES]; - $uid = htmlentities($QUERY[PQueryUpdateInformation::UID], ENT_QUOTES, "UTF-8"); - $os = intval($QUERY[PQueryUpdateInformation::OPERATING_SYSTEM]); - - //determine if the client has cpu only (most likely) - $cpuOnly = 1; - foreach ($devices as $device) { - $device = strtolower($device); - if (str_contains($device, "amd") || str_contains($device, "ati ") || str_contains($device, "radeon") || str_contains($device, "nvidia") || str_contains($device, "gtx") || str_contains($device, "ti") || strpos($device, "microsoft")) { - $cpuOnly = 0; - } - } - - // save agent details - if (strlen($this->agent->getUid()) == 0 && $this->agent->getCpuOnly() == 0) { - // we only update this variable on the first time, otherwise we would overwrite manual changes - $this->agent = Factory::getAgentFactory()->set($this->agent, Agent::CPU_ONLY, $cpuOnly); - } - $this->agent = Factory::getAgentFactory()->mset($this->agent, [ - Agent::DEVICES => htmlentities(implode("\n", $devices), ENT_QUOTES, "UTF-8"), - Agent::UID => $uid, - Agent::OS => $os - ] - ); - - $this->updateAgent(PActions::UPDATE_CLIENT_INFORMATION); - DServerLog::log(DServerLog::DEBUG, "Agent sent updated client information", [$this->agent]); - - $this->sendResponse(array( - PResponse::ACTION => PActions::UPDATE_CLIENT_INFORMATION, - PResponse::RESPONSE => PValues::SUCCESS - ) - ); - } -}