@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Network\Clients\Hsgq;
|
||||
|
||||
interface HsgqCliClient
|
||||
{
|
||||
public function connect(string $username, string $password): void;
|
||||
|
||||
public function command(string $command): string;
|
||||
|
||||
public function enterEnable(string $password): void;
|
||||
|
||||
public function disconnect(): void;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Network\Clients\Hsgq;
|
||||
|
||||
use phpseclib3\Net\SSH2;
|
||||
use RuntimeException;
|
||||
|
||||
class HsgqSshClient implements HsgqCliClient
|
||||
{
|
||||
private SSH2 $ssh;
|
||||
|
||||
public function __construct(private readonly string $host, private readonly int $port, private readonly int $timeout = 10) {}
|
||||
|
||||
public function connect(string $username, string $password): void
|
||||
{
|
||||
$this->ssh = new SSH2($this->host, $this->port, $this->timeout);
|
||||
$this->ssh->setTimeout($this->timeout);
|
||||
if (! $this->ssh->login($username, $password)) {
|
||||
throw new RuntimeException('AUTHENTICATION_FAILED: login SSH HSGQ ditolak.');
|
||||
}
|
||||
$this->readPrompt();
|
||||
}
|
||||
|
||||
public function command(string $command): string
|
||||
{
|
||||
$this->ssh->write($command."\n");
|
||||
$output = $this->readPrompt();
|
||||
if (preg_match('/(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command)/i', $output)) {
|
||||
preg_match('/^(?!.*user\s+(?:add|password)).*(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command).*$/mi', $output, $detail);
|
||||
throw new RuntimeException('COMMAND_REJECTED: '.str($detail[0] ?? 'HSGQ menolak perintah CLI.')->trim()->limit(180));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function enterEnable(string $password): void
|
||||
{
|
||||
$this->ssh->write("enable\n");
|
||||
$output = $this->ssh->read('/(?:Password\s*:|#\s*$)/i', SSH2::READ_REGEX);
|
||||
if (preg_match('/Password\s*:/i', $output)) {
|
||||
$this->ssh->write($password."\n");
|
||||
$output = $this->readPrompt();
|
||||
}
|
||||
if (! str_ends_with(trim($output), '#')) {
|
||||
throw new RuntimeException('ENABLE_FAILED: gagal masuk privileged mode HSGQ.');
|
||||
}
|
||||
}
|
||||
|
||||
public function disconnect(): void
|
||||
{
|
||||
if (isset($this->ssh)) {
|
||||
$this->ssh->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private function readPrompt(): string
|
||||
{
|
||||
$output = $this->ssh->read('/(?:\([^\r\n]+\))?[#>]\s*$/', SSH2::READ_REGEX);
|
||||
if ($output === false || $output === '') {
|
||||
throw new RuntimeException('CONNECTION_TIMEOUT: prompt CLI HSGQ tidak diterima.');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Network\Clients\Hsgq;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class HsgqTelnetClient implements HsgqCliClient
|
||||
{
|
||||
/** @var resource|null */
|
||||
private $socket;
|
||||
|
||||
public function __construct(private readonly string $host, private readonly int $port = 23, private readonly int $timeout = 10) {}
|
||||
|
||||
public function connect(string $username, string $password): void
|
||||
{
|
||||
$errorNumber = 0;
|
||||
$errorMessage = '';
|
||||
$this->socket = @stream_socket_client("tcp://{$this->host}:{$this->port}", $errorNumber, $errorMessage, $this->timeout);
|
||||
if (! is_resource($this->socket)) {
|
||||
throw new RuntimeException('TELNET_UNREACHABLE: koneksi Telnet HSGQ port 23 gagal.');
|
||||
}
|
||||
|
||||
stream_set_timeout($this->socket, $this->timeout);
|
||||
$this->readUntil('/(?:login|username|user\s*name)\s*:\s*$/i');
|
||||
$this->write($username);
|
||||
$this->readUntil('/password\s*:\s*$/i');
|
||||
$this->write($password);
|
||||
$output = $this->readPrompt();
|
||||
if (! preg_match('/[>#]\s*$/', trim($output))) {
|
||||
throw new RuntimeException('AUTHENTICATION_FAILED: login Telnet HSGQ ditolak.');
|
||||
}
|
||||
}
|
||||
|
||||
public function command(string $command): string
|
||||
{
|
||||
$this->write($command);
|
||||
$output = $this->readPrompt();
|
||||
$isHelpCommand = in_array(trim($command), ['?', 'help', 'list'], true);
|
||||
if (! $isHelpCommand && preg_match('/(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command)/i', $output)) {
|
||||
preg_match('/^(?!.*user\s+(?:add|password)).*(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command).*$/mi', $output, $detail);
|
||||
throw new RuntimeException('COMMAND_REJECTED: '.str($detail[0] ?? 'HSGQ menolak perintah CLI Telnet.')->trim()->limit(180));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function enterEnable(string $password): void
|
||||
{
|
||||
$this->write('enable');
|
||||
$output = $this->readUntil('/(?:Password\s*:|#\s*$)/i');
|
||||
if (preg_match('/Password\s*:/i', $output)) {
|
||||
$this->write($password);
|
||||
$output = $this->readPrompt();
|
||||
}
|
||||
if (! str_ends_with(trim($output), '#')) {
|
||||
throw new RuntimeException('ENABLE_FAILED: gagal masuk privileged mode Telnet HSGQ.');
|
||||
}
|
||||
}
|
||||
|
||||
public function disconnect(): void
|
||||
{
|
||||
if (is_resource($this->socket)) {
|
||||
fclose($this->socket);
|
||||
}
|
||||
$this->socket = null;
|
||||
}
|
||||
|
||||
private function write(string $value): void
|
||||
{
|
||||
if (! is_resource($this->socket) || fwrite($this->socket, $value."\r\n") === false) {
|
||||
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ terputus.');
|
||||
}
|
||||
}
|
||||
|
||||
private function readPrompt(): string
|
||||
{
|
||||
return $this->readUntil('/(?:\([^\r\n]+\))?[#>]\s*$/');
|
||||
}
|
||||
|
||||
private function readUntil(string $pattern): string
|
||||
{
|
||||
if (! is_resource($this->socket)) {
|
||||
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ belum tersambung.');
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$startedAt = microtime(true);
|
||||
while (microtime(true) - $startedAt < $this->timeout) {
|
||||
$chunk = fread($this->socket, 4096);
|
||||
if ($chunk === false || ($chunk === '' && feof($this->socket))) {
|
||||
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ ditutup perangkat.');
|
||||
}
|
||||
if ($chunk !== '') {
|
||||
$output .= $this->stripNegotiation($chunk);
|
||||
if (preg_match($pattern, $output)) {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
$metadata = stream_get_meta_data($this->socket);
|
||||
if ($metadata['timed_out']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException('CONNECTION_TIMEOUT: prompt Telnet HSGQ tidak diterima.');
|
||||
}
|
||||
|
||||
private function stripNegotiation(string $data): string
|
||||
{
|
||||
$clean = '';
|
||||
$length = strlen($data);
|
||||
for ($index = 0; $index < $length; $index++) {
|
||||
if (ord($data[$index]) !== 255) {
|
||||
$clean .= $data[$index];
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($index + 2 >= $length) {
|
||||
break;
|
||||
}
|
||||
$command = ord($data[++$index]);
|
||||
$option = ord($data[++$index]);
|
||||
if (in_array($command, [251, 252], true)) {
|
||||
fwrite($this->socket, pack('CCC', 255, 254, $option));
|
||||
} elseif (in_array($command, [253, 254], true)) {
|
||||
fwrite($this->socket, pack('CCC', 255, 252, $option));
|
||||
}
|
||||
}
|
||||
|
||||
return str_replace("\0", '', $clean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Network\Clients\Hsgq;
|
||||
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use RuntimeException;
|
||||
|
||||
class HsgqWebClient
|
||||
{
|
||||
private ?string $token = null;
|
||||
|
||||
public function __construct(private readonly string $host, private readonly int $timeout = 10) {}
|
||||
|
||||
public function login(string $username, string $password): void
|
||||
{
|
||||
$response = $this->request()->post($this->url('/userlogin?form=login'), [
|
||||
'method' => 'set',
|
||||
'param' => [
|
||||
'name' => $username,
|
||||
'key' => md5($username.':'.$password),
|
||||
'value' => '',
|
||||
'captcha_v' => '',
|
||||
'captcha_f' => '',
|
||||
],
|
||||
]);
|
||||
$this->assertSuccess($response->json());
|
||||
$this->token = $response->header('x-token');
|
||||
if (! $this->token) {
|
||||
throw new RuntimeException('HSGQ_WEB_AUTH_FAILED: WebGUI tidak memberikan token sesi.');
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
public function users(): array
|
||||
{
|
||||
$response = $this->authenticated()->get($this->url('/usermgmt?form=userlist'));
|
||||
$this->assertSuccess($response->json());
|
||||
|
||||
return $response->json('data', []);
|
||||
}
|
||||
|
||||
public function addUser(string $username, string $password, int $level): void
|
||||
{
|
||||
$this->post('/usermgmt?form=userlist', [
|
||||
'method' => 'add',
|
||||
'param' => [
|
||||
'name' => $username,
|
||||
'key' => md5($username.':'.$password),
|
||||
'level' => $level,
|
||||
'reenter' => 4,
|
||||
'info' => 'Managed by RADIQ NDM',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function changePassword(string $username, string $newPassword, string $currentPassword = ''): void
|
||||
{
|
||||
$this->post('/usermgmt?form=modifyps', [
|
||||
'method' => 'set',
|
||||
'param' => [
|
||||
'name' => $username,
|
||||
'key' => $currentPassword === '' ? '' : md5($username.':'.$currentPassword),
|
||||
'key1' => md5($username.':'.$newPassword),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteUser(string $username): void
|
||||
{
|
||||
$this->post('/usermgmt?form=userlist', [
|
||||
'method' => 'delete',
|
||||
'param' => ['name' => $username],
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasUser(string $username): bool
|
||||
{
|
||||
return collect($this->users())->contains(fn (array $user) => ($user['name'] ?? null) === $username);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function boardInfo(): array
|
||||
{
|
||||
$response = $this->authenticated()->get($this->url('/board_info'));
|
||||
$this->assertSuccess($response->json());
|
||||
|
||||
return $response->json('data', []);
|
||||
}
|
||||
|
||||
private function post(string $path, array $payload): void
|
||||
{
|
||||
$response = $this->authenticated()->post($this->url($path), $payload);
|
||||
$this->assertSuccess($response->json());
|
||||
}
|
||||
|
||||
private function request(): PendingRequest
|
||||
{
|
||||
return Http::acceptJson()->asJson()->timeout($this->timeout)->connectTimeout($this->timeout);
|
||||
}
|
||||
|
||||
private function authenticated(): PendingRequest
|
||||
{
|
||||
if (! $this->token) {
|
||||
throw new RuntimeException('HSGQ_WEB_AUTH_REQUIRED: sesi WebGUI belum dibuat.');
|
||||
}
|
||||
|
||||
return $this->request()->withHeader('x-token', $this->token);
|
||||
}
|
||||
|
||||
private function assertSuccess(?array $payload): void
|
||||
{
|
||||
if (($payload['code'] ?? null) !== 1) {
|
||||
throw new RuntimeException('HSGQ_WEB_REJECTED: '.str($payload['message'] ?? 'WebGUI menolak operasi user.')->limit(160));
|
||||
}
|
||||
}
|
||||
|
||||
private function url(string $path): string
|
||||
{
|
||||
return 'http://'.$this->host.$path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user