pywebexec 1.3.4__py3-none-any.whl → 1.3.6__py3-none-any.whl

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.
pywebexec/pywebexec.py CHANGED
@@ -17,8 +17,9 @@ from socket import gethostname, gethostbyname_ex, gethostbyaddr, inet_aton, inet
17
17
  import ssl
18
18
  import re
19
19
  import pwd
20
- import platform
21
20
  from secrets import token_urlsafe
21
+ import pty
22
+ import select
22
23
 
23
24
  if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
24
25
  from ldap3 import Server, Connection, ALL, SIMPLE, SUBTREE, Tls
@@ -328,12 +329,7 @@ def parseargs():
328
329
  user = pwd.getpwuid(os.getuid())[0]
329
330
  update_command_status(command_id, 'running', command="term", params=[user,os.ttyname(sys.stdout.fileno())], start_time=start_time, user=user)
330
331
  output_file_path = get_output_file_path(command_id)
331
- if platform.system() == 'Darwin':
332
- script_opt = '-qF'
333
- else:
334
- script_opt = '-qf'
335
- res = os.system(f"script {script_opt} {output_file_path}")
336
-
332
+ res = script(output_file_path)
337
333
  end_time = datetime.now().isoformat()
338
334
  update_command_status(command_id, status="success", end_time=end_time, exit_code=res)
339
335
  sys.exit(res)
@@ -431,36 +427,90 @@ def read_command_status(command_id):
431
427
 
432
428
  return status_data
433
429
 
434
- # Dictionary to store the process objects
435
- processes = {}
430
+
431
+ def script(filename):
432
+ shell = os.environ.get('SHELL', 'sh')
433
+ with open(filename, 'wb') as s:
434
+ def read(fd):
435
+ data = os.read(fd, 1024)
436
+ s.write(data)
437
+ s.flush()
438
+ return data
439
+ return pty.spawn(shell, read)
440
+
436
441
 
437
442
  def run_command(command, params, command_id, user):
438
443
  start_time = datetime.now().isoformat()
439
444
  update_command_status(command_id, 'running', command=command, params=params, start_time=start_time, user=user)
440
445
  try:
441
446
  output_file_path = get_output_file_path(command_id)
442
- with open(output_file_path, 'w') as output_file:
443
- # Run the command with parameters and redirect stdout and stderr to the file
444
- process = subprocess.Popen([command] + params, stdout=output_file, stderr=output_file, bufsize=0) #text=True)
445
- update_command_status(command_id, 'running', pid=process.pid, user=user)
446
- processes[command_id] = process
447
- process.wait()
448
- processes.pop(command_id, None)
449
-
447
+ with open(output_file_path, 'wb') as output_file:
448
+ def read(fd):
449
+ data = os.read(fd, 1024)
450
+ if not data: # Check for EOF
451
+ raise OSError("EOF reached")
452
+ output_file.write(data)
453
+ output_file.flush()
454
+ return data
455
+
456
+ def spawn_pty():
457
+ pid, fd = pty.fork()
458
+ if pid == 0: # Child process
459
+ try:
460
+ os.setsid()
461
+ except:
462
+ pass
463
+ os.execvp(command, [command] + params)
464
+ else: # Parent process
465
+ update_command_status(command_id, 'running', pid=pid, user=user)
466
+ while True:
467
+ try:
468
+ read(fd)
469
+ except OSError:
470
+ break
471
+ (pid, status) = os.waitpid(pid, 0)
472
+ return status
473
+
474
+ status = spawn_pty()
450
475
  end_time = datetime.now().isoformat()
451
476
  # Update the status based on the result
452
- if process.returncode == 0:
453
- update_command_status(command_id, 'success', end_time=end_time, exit_code=process.returncode, user=user)
454
- elif process.returncode == -15:
455
- update_command_status(command_id, 'aborted', end_time=end_time, exit_code=process.returncode, user=user)
456
- else:
457
- update_command_status(command_id, 'failed', end_time=end_time, exit_code=process.returncode, user=user)
477
+ if os.WIFEXITED(status):
478
+ exit_code = os.WEXITSTATUS(status)
479
+ if exit_code == 0:
480
+ update_command_status(command_id, 'success', end_time=end_time, exit_code=exit_code, user=user)
481
+ elif exit_code == -15:
482
+ update_command_status(command_id, 'aborted', end_time=end_time, exit_code=exit_code, user=user)
483
+ else:
484
+ update_command_status(command_id, 'failed', end_time=end_time, exit_code=exit_code, user=user)
458
485
  except Exception as e:
459
486
  end_time = datetime.now().isoformat()
460
487
  update_command_status(command_id, 'failed', end_time=end_time, exit_code=1, user=user)
461
488
  with open(get_output_file_path(command_id), 'a') as output_file:
462
489
  output_file.write(str(e))
463
490
 
491
+ @app.route('/stop_command/<command_id>', methods=['POST'])
492
+ def stop_command(command_id):
493
+ status = read_command_status(command_id)
494
+ if not status or 'pid' not in status:
495
+ return jsonify({'error': 'Invalid command_id or command not running'}), 400
496
+
497
+ pid = status['pid']
498
+ end_time = datetime.now().isoformat()
499
+ try:
500
+ os.killpg(os.getpgid(pid), 15) # Send SIGTERM to the process group
501
+ #os.waitpid(pid, 0) # Wait for the process to terminate
502
+ #update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
503
+ return jsonify({'message': 'Command aborted'})
504
+ except Exception as e:
505
+ status_data = read_command_status(command_id) or {}
506
+ status_data['status'] = 'failed'
507
+ status_data['end_time'] = end_time
508
+ status_data['exit_code'] = 1
509
+ with open(get_status_file_path(command_id), 'w') as f:
510
+ json.dump(status_data, f)
511
+ with open(get_output_file_path(command_id), 'a') as output_file:
512
+ output_file.write(str(e))
513
+ return jsonify({'error': 'Failed to terminate command'}), 500
464
514
 
465
515
  parseargs()
466
516
 
@@ -563,29 +613,6 @@ def run_command_endpoint():
563
613
 
564
614
  return jsonify({'message': 'Command is running', 'command_id': command_id})
565
615
 
566
- @app.route('/stop_command/<command_id>', methods=['POST'])
567
- def stop_command(command_id):
568
- status = read_command_status(command_id)
569
- if not status or 'pid' not in status:
570
- return jsonify({'error': 'Invalid command_id or command not running'}), 400
571
-
572
- pid = status['pid']
573
- end_time = datetime.now().isoformat()
574
- try:
575
- os.kill(pid, 15) # Send SIGTERM
576
- #update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
577
- return jsonify({'message': 'Command aborted'})
578
- except Exception as e:
579
- status_data = read_command_status(command_id) or {}
580
- status_data['status'] = 'failed'
581
- status_data['end_time'] = end_time
582
- status_data['exit_code'] = 1
583
- with open(get_status_file_path(command_id), 'w') as f:
584
- json.dump(status_data, f)
585
- with open(get_output_file_path(command_id), 'a') as output_file:
586
- output_file.write(str(e))
587
- return jsonify({'error': 'Failed to terminate command'}), 500
588
-
589
616
  @app.route('/command_status/<command_id>', methods=['GET'])
590
617
  def get_command_status(command_id):
591
618
  status = read_command_status(command_id)
pywebexec/version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.3.4'
16
- __version_tuple__ = version_tuple = (1, 3, 4)
15
+ __version__ = version = '1.3.6'
16
+ __version_tuple__ = version_tuple = (1, 3, 6)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.3.4
3
+ Version: 1.3.6
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1,6 +1,6 @@
1
1
  pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
2
- pywebexec/pywebexec.py,sha256=CsixpYwvlg2FUt05aEKMntvUtdmEhRL3EaSQixIq4bY,25620
3
- pywebexec/version.py,sha256=q-rMkBhc2cbe8SdkcXn0kh8Fvtl2cIlapH0EvPC1bmc,411
2
+ pywebexec/pywebexec.py,sha256=OUpyU7X275eVA-yn8coUsfN1vKaXvfxaPczeo-rEzrg,26419
3
+ pywebexec/version.py,sha256=d5ulHSpHe8PYDRupuj0qHnCpT54pR8sMjv17y0ht5fs,411
4
4
  pywebexec/static/css/Consolas NF.ttf,sha256=DJEOzF0eqZ-kxu3Gs_VE8X0NJqiobBzmxWDGpdgGRxI,1313900
5
5
  pywebexec/static/css/style.css,sha256=iLX6k1hoWLinZWyqtbH50U-0hND2M-5_Zr1U1UC_gos,5578
6
6
  pywebexec/static/css/xterm.css,sha256=gy8_LGA7Q61DUf8ElwFQzHqHMBQnbbEmpgZcbdgeSHI,5383
@@ -20,9 +20,9 @@ pywebexec/static/js/xterm/xterm-addon-fit.js,sha256=Pprm9pZe4SadVXS5Bc8b9VnC9Ex4
20
20
  pywebexec/static/js/xterm/xterm.js,sha256=Bzka76jZwEhVt_LlS0e0qMw7ryGa1p5qfxFyeohphBo,283371
21
21
  pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  pywebexec/templates/index.html,sha256=DYtT555wSNhnFtzzHhPMWJireynCJNnAuTytpoORQeE,2321
23
- pywebexec-1.3.4.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
- pywebexec-1.3.4.dist-info/METADATA,sha256=WofvGA3I_xqTnXrhZ6ie1WXXRE_7aQ6gIT2vYwjS7BM,7397
25
- pywebexec-1.3.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
- pywebexec-1.3.4.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
- pywebexec-1.3.4.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
- pywebexec-1.3.4.dist-info/RECORD,,
23
+ pywebexec-1.3.6.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
+ pywebexec-1.3.6.dist-info/METADATA,sha256=vuIpsD_OxyKGVzMWDZO0HEAt0oPOtKt_0em-nvHc__s,7397
25
+ pywebexec-1.3.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
+ pywebexec-1.3.6.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
+ pywebexec-1.3.6.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
+ pywebexec-1.3.6.dist-info/RECORD,,