pywebexec 1.3.3__py3-none-any.whl → 1.3.5__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 = '-F'
333
- else:
334
- script_opt = '-f'
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,88 @@ 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
+ output_file.write(data)
451
+ output_file.flush()
452
+ return data
453
+
454
+ def spawn_pty():
455
+ pid, fd = pty.fork()
456
+ if pid == 0: # children
457
+ try:
458
+ os.setsid()
459
+ except:
460
+ pass
461
+ os.execvp(command, [command] + params)
462
+ else:
463
+ update_command_status(command_id, 'running', pid=pid, user=user)
464
+ while True:
465
+ try:
466
+ read(fd)
467
+ except OSError:
468
+ break
469
+ (pid, status) = os.waitpid(pid, 0)
470
+ print(status)
471
+ return status
472
+ status = spawn_pty()
450
473
  end_time = datetime.now().isoformat()
451
474
  # 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)
475
+ if os.WIFEXITED(status):
476
+ exit_code = os.WEXITSTATUS(status)
477
+ if exit_code == 0:
478
+ update_command_status(command_id, 'success', end_time=end_time, exit_code=exit_code, user=user)
479
+ elif exit_code == -15:
480
+ update_command_status(command_id, 'aborted', end_time=end_time, exit_code=exit_code, user=user)
481
+ else:
482
+ update_command_status(command_id, 'failed', end_time=end_time, exit_code=exit_code, user=user)
458
483
  except Exception as e:
459
484
  end_time = datetime.now().isoformat()
460
485
  update_command_status(command_id, 'failed', end_time=end_time, exit_code=1, user=user)
461
486
  with open(get_output_file_path(command_id), 'a') as output_file:
462
487
  output_file.write(str(e))
463
488
 
489
+ @app.route('/stop_command/<command_id>', methods=['POST'])
490
+ def stop_command(command_id):
491
+ status = read_command_status(command_id)
492
+ if not status or 'pid' not in status:
493
+ return jsonify({'error': 'Invalid command_id or command not running'}), 400
494
+
495
+ pid = status['pid']
496
+ end_time = datetime.now().isoformat()
497
+ try:
498
+ os.killpg(os.getpgid(pid), 15) # Send SIGTERM to the process group
499
+ #os.waitpid(pid, 0) # Wait for the process to terminate
500
+ #update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
501
+ return jsonify({'message': 'Command aborted'})
502
+ except Exception as e:
503
+ status_data = read_command_status(command_id) or {}
504
+ status_data['status'] = 'failed'
505
+ status_data['end_time'] = end_time
506
+ status_data['exit_code'] = 1
507
+ with open(get_status_file_path(command_id), 'w') as f:
508
+ json.dump(status_data, f)
509
+ with open(get_output_file_path(command_id), 'a') as output_file:
510
+ output_file.write(str(e))
511
+ return jsonify({'error': 'Failed to terminate command'}), 500
464
512
 
465
513
  parseargs()
466
514
 
@@ -563,29 +611,6 @@ def run_command_endpoint():
563
611
 
564
612
  return jsonify({'message': 'Command is running', 'command_id': command_id})
565
613
 
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
614
  @app.route('/command_status/<command_id>', methods=['GET'])
590
615
  def get_command_status(command_id):
591
616
  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.3'
16
- __version_tuple__ = version_tuple = (1, 3, 3)
15
+ __version__ = version = '1.3.5'
16
+ __version_tuple__ = version_tuple = (1, 3, 5)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.3.3
3
+ Version: 1.3.5
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=b-lypYr27O-t-YTUJs0pIWiDPsBE0eSQwSBqfNdkYR4,25618
3
- pywebexec/version.py,sha256=VriGPi1kVXIBM0YGAuhpE803XR-FNq1JvTW1Kz2us08,411
2
+ pywebexec/pywebexec.py,sha256=zZRaHVUoWqX6IHCesfrboCjcjKGYnOq3jZpos7h9JEI,26333
3
+ pywebexec/version.py,sha256=FcYWgM5oYgqvidiF8nHnDFf0uLGibSe_Bdaq7MY6S4U,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.3.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
- pywebexec-1.3.3.dist-info/METADATA,sha256=A69MhlrTDRfLv8tzyDJSdVFccYSaHNFsE5JHq_Qz8rM,7397
25
- pywebexec-1.3.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
- pywebexec-1.3.3.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
- pywebexec-1.3.3.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
- pywebexec-1.3.3.dist-info/RECORD,,
23
+ pywebexec-1.3.5.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
+ pywebexec-1.3.5.dist-info/METADATA,sha256=rNB2fBP4AMqwoDaR9tE2yq3EDr1mKs6zW6neFJ87aRM,7397
25
+ pywebexec-1.3.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
+ pywebexec-1.3.5.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
+ pywebexec-1.3.5.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
+ pywebexec-1.3.5.dist-info/RECORD,,