Changed file system

This commit is contained in:
BarsTiger
2022-01-23 22:15:06 +02:00
parent 89bef345cb
commit 79e2d7266f
10 changed files with 12 additions and 11 deletions

33
modules/auth.py Normal file
View File

@@ -0,0 +1,33 @@
import json
def get_auth():
with open('config.cfg') as f:
config = json.load(f)
try:
if config['auth']:
return config['auth']
else:
raise Exception('No auth found')
except:
print('[!] No auth found, please login first')
print('email')
email = input('> ')
print('password')
password = input('> ')
config['auth'] = {'email': email, 'password': password}
with open('config.cfg', 'w') as f:
json.dump(config, f)
print('[OK] Auth created')
return config['auth']
def del_auth():
with open('config.cfg') as f:
config = json.load(f)
if config['auth']:
config['auth'] = None
with open('config.cfg', 'w') as f:
json.dump(config, f)
print('[OK] Auth deleted')

5
modules/console.py Normal file
View File

@@ -0,0 +1,5 @@
import os
def cls():
os.system('cls' if os.name == 'nt' else 'clear')

138
modules/manager.py Normal file
View File

@@ -0,0 +1,138 @@
import json
import threading
from rich import print
import requests
import modules.vars as horsy_vars
from tqdm import tqdm
import os
import zipfile
from modules.virustotal import get_key, scan_file, get_report
def install(package, is_gui=False):
horsypath = os.popen('echo %HORSYPATH%').read().replace('\n', '') + '/'
r = requests.get(f"{horsy_vars.protocol}{horsy_vars.server_url}/packages/json/{package}").text
try:
r = json.loads(r)
except:
print("[red]Error with unsupported message[/]")
return
try:
if r["message"] == "not found":
print("[red]Package not found[/]")
return
if r["message"] == "Internal server error":
print("[red]Internal server error[/]")
return
except:
pass
try:
print(f"[green]App {r['name']} found, information loaded[/]")
if not os.path.exists('{1}apps/{0}'.format(r['name'], horsypath)):
os.makedirs('{1}apps/{0}'.format(r['name'], horsypath))
if not is_gui:
print(f"Downloading {r['url'].split('/')[-1]}")
chunk_size = 1024
file_r = requests.get(r['url'], stream=True)
with open('{2}apps/{0}/{1}'.format(r['name'], r['url'].split('/')[-1], horsypath), "wb") as f:
pbar = tqdm(unit="B", unit_scale=True, total=int(file_r.headers['Content-Length']))
for chunk in file_r.iter_content(chunk_size=chunk_size):
if chunk:
pbar.update(len(chunk))
f.write(chunk)
pbar.close()
print(f"Starting virustotal scan")
if not get_key():
print(f"[red]Virustotal api key not found[/]")
print(f"You can add it by entering [bold]horsy --vt \[your key][/] in terminal")
else:
print(f"[green]Virustotal api key found[/]")
print(f"[italic white]If you want to disable scan, type [/][bold]horsy --vt disable[/]"
f"[italic white] in terminal[/]")
scan_file('{2}apps/{0}/{1}'.format(r['name'], r['url'].split('/')[-1], horsypath))
print(f"[green]Virustotal scan finished[/]")
analysis = get_report('{2}apps/{0}/{1}'.format(r['name'], r['url'].split('/')[-1], horsypath))
print(f"[green]You can see report by opening: [white]{analysis['link']}[/]")
print(f"{analysis['detect']['malicious']} antivirus flagged this file as malicious")
print(f"[green][OK] Done[/]")
def unzip(file, where):
with zipfile.ZipFile(file, 'r') as zip_ref:
zip_ref.extractall(where)
print(f"[green]Extracted[/]")
if r['url'].split('.')[-1] == 'zip':
print(f"Extracting {r['url'].split('/')[-1]}")
unzip('{2}apps/{0}/{1}'.format(r['name'], r['url'].split('/')[-1], horsypath),
'{1}apps/{0}'.format(r['name'], horsypath))
if r['download']:
print(f"Found dependency")
if not is_gui:
print(f"Downloading {r['download'].split('/')[-1]}")
chunk_size = 1024
file_r = requests.get(r['download'], stream=True)
with open('{2}apps/{0}/{1}'.format(r['name'], r['download'].split('/')[-1], horsypath), "wb") as f:
pbar = tqdm(unit="B", unit_scale=True, total=int(file_r.headers['Content-Length']))
for chunk in file_r.iter_content(chunk_size=chunk_size):
if chunk:
pbar.update(len(chunk))
f.write(chunk)
pbar.close()
print(f"Starting virustotal scan")
if not get_key():
print(f"[red]Virustotal api key not found[/]")
print(f"You can add it by entering [italic white]horsy --vt \[your key][/] in terminal")
else:
print(f"[green]Virustotal api key found[/]")
scan_file('{2}apps/{0}/{1}'.format(r['name'], r['download'].split('/')[-1], horsypath))
print(f"[green]Virustotal scan finished[/]")
analysis = get_report('{2}apps/{0}/{1}'.format(r['name'], r['download'].split('/')[-1], horsypath))
print(f"[green]You can see report by opening: [white]{analysis['link']}[/]")
print(f"{analysis['detect']['malicious']} antivirus flagged this file as malicious")
if analysis['detect']['malicious'] > 0:
print(f"[red]Dependency can be malicious. It may run now, if this added to installation "
f"config[/]")
input("Press enter if you want continue, or ctrl+c to exit")
if r['install']:
print(f"Found install option")
threading.Thread(target=os.system, args=('{2}apps/{0}/{1}'.format(r['name'], r['install'], horsypath),)) \
.start()
print(f"Generating launch script")
with open('{1}apps/{0}.bat'.format(r['name'], horsypath), 'w') as f:
f.write(f"@ECHO off\n")
f.write(f"{horsypath}apps/{r['name']}/{r['run']} %*\n")
print(f"[green][OK] All done![/]")
print(f"[green]You can run your app by entering [italic white]{r['name']}[/] in terminal[/]")
except:
print("[red]Unexpected error[/]")
raise
return
def uninstall(package, is_gui=False):
horsypath = os.popen('echo %HORSYPATH%').read().replace('\n', '') + '/'
if not is_gui:
if os.path.exists('{1}apps/{0}'.format(package, horsypath)):
os.system('rmdir /s /q "{1}apps/{0}"'.format(package, horsypath))
print(f"[green][OK] Files deleted[/]")
else:
print(f"[red]App {package} is not installed or doesn't have files[/]")
if os.path.isfile('{1}apps/{0}.bat'.format(package, horsypath)):
os.remove("{1}apps/{0}.bat".format(package, horsypath))
print(f"[green][OK] Launch script deleted[/]")
else:
print(f"[red]App {package} is not installed or doesn't have launch script[/]")

31
modules/path.py Normal file
View File

@@ -0,0 +1,31 @@
# Module for PATH actions
import os
def add_to_path(program_path: str):
import winreg
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: # Get the current user's registry
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: # Open the environment key
existing_path_value = os.popen('echo %PATH%').read() # Get the existing path value
print(existing_path_value)
new_path_value = existing_path_value + ";" + program_path # Connect the new path to the existing path
winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, new_path_value) # Update the path value
def delete_from_path(program_path: str):
import winreg
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: # Get the current user's registry
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: # Open the environment key
existing_path_value = os.popen('echo %PATH%').read() # Get the existing path value
new_path_value = existing_path_value.replace(program_path + ";", "") # Remove the program path from path
winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, new_path_value) # Update the path value
def add_var(horsy_path: str):
import winreg
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root: # Get the current user's registry
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key: # Open the environment key
winreg.SetValueEx(key, "HORSYPATH", 0, winreg.REG_EXPAND_SZ, horsy_path) # Update the path value

19
modules/tui.py Normal file
View File

@@ -0,0 +1,19 @@
def menu(options: list) -> int:
for i in range(len(options)):
print(str(i) + ' - ' + options[i])
user_input = None
while user_input is None:
try:
user_input = int(input('\n> '))
if user_input < 0 or user_input >= len(options):
user_input = None
print('Choose number between 0 and ' + str(len(options) - 1))
except ValueError:
print('Choose number option')
return user_input
def get(description: str) -> str:
print(description)
return input('> ')

142
modules/uploader.py Normal file
View File

@@ -0,0 +1,142 @@
import json
import time
import requests
from rich import print
from modules.auth import get_auth, del_auth
import re
import modules.vars as horsy_vars
import os
def matches(s):
return re.match("^[a-z_-]*$", s) is not None
def urlmatch(s):
return re.match("^https?://.*.(?:zip|exe)$", s) is not None
def upload():
print('Welcome to the uploader')
print('Before starting, please make sure you have done your project and [blink]uploaded[/] it to any hosting '
'service or file sharing service')
input('[OK] Press enter to continue...')
auth = get_auth()
print('Please enter the name of your project. It should contain only lowercase letters, '
'underscores and dashes')
project_name = None
while project_name is None:
project_name = input('> ')
if not matches(project_name) or len(project_name) > 64 or len(project_name) < 3:
print('[red]Invalid project name[/red]')
project_name = None
print('Please paste there project description. It should be a short text under 256 characters')
description = None
while description is None:
description = input('> ')
if len(description) > 256:
print('[red]Description is too long[/red]')
description = None
print('Please paste there url of executable file. It should be a link to exe or zip file hosted somewhere. '
'If app needs dependencies or specific launch options (python, node, etc), you can add them later')
url = None
while url is None:
url = input('> ')
if not urlmatch(url):
print('[red]Invalid file url, also it should end on .exe or .zip[/red]')
url = None
print('Please paste there url of your project on GitHub or somewhere else. It should be a link to source code '
'of your app. It can be archive, repository, site, whatever you want, optional but highly recommended.'
'If you don\'t want to add it, just press Enter')
source_url = input('> ')
source_url = None if source_url == '' else source_url
print('If your app needs any dependencies, please paste its link here. It can be exe of installer from official '
'site. If you don\'t want to add it, just press Enter')
download = None
while download is None:
download = input('> ')
if download == '':
download = None
break
if not urlmatch(download):
print('[red]Invalid download url[/red]')
download = None
print('Please add which files should be run during installation. It should be an executable file name.'
'If you don\'t want to add it, just press Enter')
install = input('> ')
install = None if install == '' else install
print('Please specify main executable command. It can be executable file name (some-file.exe) or command, that '
'launches your script (python some-file.py, etc)')
run = None
while run is None:
run = input('> ')
if run == '':
print('[red]Please, specify runtime[/red]')
run = None
request = {
'auth': auth,
'name': project_name,
'description': description,
'url': url,
'sourceUrl': source_url,
'download': download,
'install': install,
'run': run
}
# request = {
# "auth": {"email": "meshko_a@dlit.dp.ua", "password": "VeryGoodPassword"},
# "name": "testapp",
# "description": "Very good description",
# # "url": "https://github.com/Cactus-0/cabanchik/raw/main/dist/cabanchik.exe",
# "sourceUrl": "https://github.com/Cactus-0/cabanchik",
# "download": "https://www.python.org/ftp/python/3.10.2/python-3.10.2-amd64.exe",
# "install": "python-3.10.2-amd64.exe",
# "run": "cabanchik.exe"
# }
r = None
while r is None:
try:
r = requests.post(horsy_vars.protocol + horsy_vars.server_url + '/packages/new', json=request).text
r = json.loads(r)
if r['message'] == 'Unauthorized':
print('[red]Invalid credentials[/red]')
print('Deleting auth from config')
del_auth()
request['auth'] = get_auth()
print(r)
r = None
elif r['message'] == 'Internal server error':
print('[red]Internal server error, request is broken[/red]')
break
elif r['message'] == 'Invalid body':
print('[red]Invalid request body, try again[/red]')
break
elif r['message'] == 'Success':
print('[green]Success, your project is created. You can install it by running[/] '
'[i]horsy install {0}[/]'.format(request['name']))
break
else:
print('[red]Unknown error[/red]')
print('Server response:')
print(r)
break
except:
with open(f'error_{time.time()}.txt', 'w') as f:
f.write(str(r))
print(f'[red]Something went wrong with unsupported error. You can check servers response in '
f'{os.getcwd()}/{f.name}[/red]')
break

2
modules/vars.py Normal file
View File

@@ -0,0 +1,2 @@
protocol = "http://"
server_url = 'localhost:60666'

53
modules/virustotal.py Normal file
View File

@@ -0,0 +1,53 @@
import json
import requests
import os
import hashlib
def add_to_cfg(key):
with open('config.cfg') as f:
config = json.load(f)
config['vt-key'] = key
with open('config.cfg', 'w') as f:
json.dump(config, f)
def get_key():
with open('config.cfg') as f:
config = json.load(f)
try:
return config['vt-key']
except KeyError:
return None
def scan_file(filename):
api_url = 'https://www.virustotal.com/api/v3/files'
headers = {'x-apikey': get_key()}
with open(filename, 'rb') as file:
files = {'file': (filename, file)}
if os.path.getsize(filename) < 33554432:
response = requests.post(api_url, headers=headers, files=files)
return response.json()['data']['id']
else:
api_url = 'https://www.virustotal.com/api/v3/files/upload_url'
response = requests.get(api_url, headers=headers)
response = requests.post(response.json()['data'], headers=headers, files=files)
return response.json()['data']['id']
def get_report(filename):
hash_md5 = hashlib.md5()
with open(filename, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
api_url = 'https://www.virustotal.com/api/v3/files/' + hash_md5.hexdigest()
headers = {'x-apikey': get_key()}
response = requests.get(api_url, headers=headers)
analysis = dict()
analysis['detect'] = response.json()['data']['attributes']['last_analysis_stats']
analysis['link'] = 'https://www.virustotal.com/gui/file/' + response.json()['data']['id']
return analysis