473 lines
16 KiB
Python
473 lines
16 KiB
Python
|
|
# Módulo MDM para o Cliente NoIdle
|
||
|
|
# Executa políticas e comandos remotos
|
||
|
|
|
||
|
|
import subprocess
|
||
|
|
import time
|
||
|
|
import json
|
||
|
|
import requests
|
||
|
|
import winreg
|
||
|
|
import os
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
class MDMExecutor:
|
||
|
|
def __init__(self, api_url, device_id):
|
||
|
|
self.api_url = api_url
|
||
|
|
self.device_id = device_id
|
||
|
|
self.handlers = {
|
||
|
|
'windows_update': self.execute_windows_update,
|
||
|
|
'software_install': self.execute_software_install,
|
||
|
|
'software_uninstall': self.execute_software_uninstall,
|
||
|
|
'powershell_script': self.execute_powershell_script,
|
||
|
|
'registry': self.execute_registry,
|
||
|
|
'reboot': self.execute_reboot,
|
||
|
|
'cleanup': self.execute_cleanup,
|
||
|
|
'firewall': self.execute_firewall
|
||
|
|
}
|
||
|
|
|
||
|
|
def poll_commands(self):
|
||
|
|
"""Busca comandos pendentes da API"""
|
||
|
|
try:
|
||
|
|
response = requests.post(
|
||
|
|
f"{self.api_url}/mdm/commands/poll",
|
||
|
|
json={'device_id': self.device_id},
|
||
|
|
timeout=10
|
||
|
|
)
|
||
|
|
|
||
|
|
if response.status_code == 200:
|
||
|
|
data = response.json()
|
||
|
|
if data.get('success'):
|
||
|
|
return data.get('commands', [])
|
||
|
|
return []
|
||
|
|
except Exception as e:
|
||
|
|
print(f"❌ Erro ao buscar comandos: {e}")
|
||
|
|
return []
|
||
|
|
|
||
|
|
def report_start(self, command_id):
|
||
|
|
"""Reporta início de execução"""
|
||
|
|
try:
|
||
|
|
requests.post(
|
||
|
|
f"{self.api_url}/mdm/commands/{command_id}/start",
|
||
|
|
timeout=5
|
||
|
|
)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"⚠️ Erro ao reportar início: {e}")
|
||
|
|
|
||
|
|
def report_result(self, command_id, status, result=None, error_message=None, duration=0):
|
||
|
|
"""Reporta resultado da execução"""
|
||
|
|
try:
|
||
|
|
response = requests.post(
|
||
|
|
f"{self.api_url}/mdm/commands/{command_id}/result",
|
||
|
|
json={
|
||
|
|
'status': status,
|
||
|
|
'result': result,
|
||
|
|
'error_message': error_message,
|
||
|
|
'duration_seconds': duration
|
||
|
|
},
|
||
|
|
timeout=10
|
||
|
|
)
|
||
|
|
return response.status_code == 200
|
||
|
|
except Exception as e:
|
||
|
|
print(f"❌ Erro ao reportar resultado: {e}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
def execute_command(self, command):
|
||
|
|
"""Executa um comando MDM"""
|
||
|
|
command_id = command['command_id']
|
||
|
|
command_type = command['command_type']
|
||
|
|
command_data = command['command_data']
|
||
|
|
|
||
|
|
print(f"🔧 Executando: {command.get('policy_name', command_type)}")
|
||
|
|
|
||
|
|
# Reportar início
|
||
|
|
self.report_start(command_id)
|
||
|
|
|
||
|
|
start_time = time.time()
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Executar comando baseado no tipo
|
||
|
|
if command_type in self.handlers:
|
||
|
|
result = self.handlers[command_type](command_data)
|
||
|
|
duration = int(time.time() - start_time)
|
||
|
|
|
||
|
|
# Reportar sucesso
|
||
|
|
self.report_result(command_id, 'success', result, None, duration)
|
||
|
|
print(f"✅ Comando executado com sucesso em {duration}s")
|
||
|
|
return True
|
||
|
|
else:
|
||
|
|
error = f"Tipo de comando não suportado: {command_type}"
|
||
|
|
self.report_result(command_id, 'failed', None, error, 0)
|
||
|
|
print(f"❌ {error}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
duration = int(time.time() - start_time)
|
||
|
|
error_message = str(e)
|
||
|
|
self.report_result(command_id, 'failed', None, error_message, duration)
|
||
|
|
print(f"❌ Erro ao executar comando: {e}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
def execute_windows_update(self, config):
|
||
|
|
"""Força Windows Update"""
|
||
|
|
print("🔄 Forçando Windows Update...")
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Script PowerShell para forçar update
|
||
|
|
ps_script = """
|
||
|
|
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
|
||
|
|
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
|
||
|
|
|
||
|
|
Write-Host "Procurando atualizações..."
|
||
|
|
$SearchResult = $UpdateSearcher.Search("IsInstalled=0")
|
||
|
|
|
||
|
|
if ($SearchResult.Updates.Count -eq 0) {
|
||
|
|
Write-Host "Nenhuma atualização disponível"
|
||
|
|
exit 0
|
||
|
|
}
|
||
|
|
|
||
|
|
Write-Host "Encontradas $($SearchResult.Updates.Count) atualizações"
|
||
|
|
|
||
|
|
$UpdatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl
|
||
|
|
foreach ($Update in $SearchResult.Updates) {
|
||
|
|
$UpdatesToDownload.Add($Update) | Out-Null
|
||
|
|
}
|
||
|
|
|
||
|
|
Write-Host "Baixando atualizações..."
|
||
|
|
$Downloader = $UpdateSession.CreateUpdateDownloader()
|
||
|
|
$Downloader.Updates = $UpdatesToDownload
|
||
|
|
$DownloadResult = $Downloader.Download()
|
||
|
|
|
||
|
|
Write-Host "Instalando atualizações..."
|
||
|
|
$UpdatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
|
||
|
|
foreach ($Update in $SearchResult.Updates) {
|
||
|
|
if ($Update.IsDownloaded) {
|
||
|
|
$UpdatesToInstall.Add($Update) | Out-Null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$Installer = $UpdateSession.CreateUpdateInstaller()
|
||
|
|
$Installer.Updates = $UpdatesToInstall
|
||
|
|
$InstallResult = $Installer.Install()
|
||
|
|
|
||
|
|
Write-Host "Resultado: $($InstallResult.ResultCode)"
|
||
|
|
|
||
|
|
if ($InstallResult.RebootRequired) {
|
||
|
|
Write-Host "REBOOT_REQUIRED"
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
['powershell', '-ExecutionPolicy', 'Bypass', '-Command', ps_script],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
timeout=1800 # 30 minutos
|
||
|
|
)
|
||
|
|
|
||
|
|
output = result.stdout
|
||
|
|
reboot_required = "REBOOT_REQUIRED" in output
|
||
|
|
|
||
|
|
# Se reboot for necessário e configurado, agendar
|
||
|
|
if reboot_required and config.get('reboot_if_required'):
|
||
|
|
delay = config.get('reboot_delay_minutes', 30)
|
||
|
|
self.schedule_reboot(delay)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'updates_found': 'atualizações' in output.lower(),
|
||
|
|
'reboot_required': reboot_required,
|
||
|
|
'output': output[:500] # Limitar tamanho
|
||
|
|
}
|
||
|
|
|
||
|
|
except subprocess.TimeoutExpired:
|
||
|
|
raise Exception("Timeout ao executar Windows Update")
|
||
|
|
except Exception as e:
|
||
|
|
raise Exception(f"Erro no Windows Update: {str(e)}")
|
||
|
|
|
||
|
|
def execute_software_install(self, config):
|
||
|
|
"""Instala software"""
|
||
|
|
software_name = config.get('software_name', 'Unknown')
|
||
|
|
download_url = config.get('download_url')
|
||
|
|
install_args = config.get('install_args', '/S')
|
||
|
|
|
||
|
|
print(f"📦 Instalando: {software_name}")
|
||
|
|
|
||
|
|
if not download_url:
|
||
|
|
raise Exception("URL de download não fornecida")
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Baixar instalador
|
||
|
|
import tempfile
|
||
|
|
temp_dir = tempfile.gettempdir()
|
||
|
|
filename = download_url.split('/')[-1] or 'installer.exe'
|
||
|
|
installer_path = os.path.join(temp_dir, filename)
|
||
|
|
|
||
|
|
print(f"⬇️ Baixando de {download_url}...")
|
||
|
|
response = requests.get(download_url, timeout=300)
|
||
|
|
response.raise_for_status()
|
||
|
|
|
||
|
|
with open(installer_path, 'wb') as f:
|
||
|
|
f.write(response.content)
|
||
|
|
|
||
|
|
print(f"🔧 Instalando...")
|
||
|
|
# Executar instalador
|
||
|
|
result = subprocess.run(
|
||
|
|
[installer_path] + install_args.split(),
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
timeout=600
|
||
|
|
)
|
||
|
|
|
||
|
|
# Limpar
|
||
|
|
try:
|
||
|
|
os.remove(installer_path)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
return {
|
||
|
|
'software': software_name,
|
||
|
|
'exit_code': result.returncode,
|
||
|
|
'success': result.returncode == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
raise Exception(f"Erro ao instalar {software_name}: {str(e)}")
|
||
|
|
|
||
|
|
def execute_software_uninstall(self, config):
|
||
|
|
"""Desinstala software"""
|
||
|
|
software_name = config.get('software_name')
|
||
|
|
silent = config.get('silent', True)
|
||
|
|
|
||
|
|
print(f"🗑️ Desinstalando: {software_name}")
|
||
|
|
|
||
|
|
# Usar wmic para desinstalar
|
||
|
|
ps_script = f"""
|
||
|
|
$software = Get-WmiObject -Class Win32_Product | Where-Object {{ $_.Name -like "*{software_name}*" }}
|
||
|
|
if ($software) {{
|
||
|
|
$software.Uninstall()
|
||
|
|
Write-Host "Desinstalado: $($software.Name)"
|
||
|
|
}} else {{
|
||
|
|
Write-Host "Software não encontrado"
|
||
|
|
}}
|
||
|
|
"""
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
['powershell', '-ExecutionPolicy', 'Bypass', '-Command', ps_script],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
timeout=300
|
||
|
|
)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'software': software_name,
|
||
|
|
'output': result.stdout,
|
||
|
|
'success': 'Desinstalado' in result.stdout
|
||
|
|
}
|
||
|
|
|
||
|
|
def execute_powershell_script(self, config):
|
||
|
|
"""Executa script PowerShell"""
|
||
|
|
script = config.get('script', '')
|
||
|
|
run_as_admin = config.get('run_as_admin', True)
|
||
|
|
timeout_minutes = config.get('timeout_minutes', 30)
|
||
|
|
execution_policy = config.get('execution_policy', 'Bypass')
|
||
|
|
|
||
|
|
print(f"💻 Executando script PowerShell...")
|
||
|
|
|
||
|
|
if not script:
|
||
|
|
raise Exception("Script não fornecido")
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
['powershell', '-ExecutionPolicy', execution_policy, '-Command', script],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
timeout=timeout_minutes * 60
|
||
|
|
)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'exit_code': result.returncode,
|
||
|
|
'stdout': result.stdout[:1000], # Limitar tamanho
|
||
|
|
'stderr': result.stderr[:1000],
|
||
|
|
'success': result.returncode == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
def execute_registry(self, config):
|
||
|
|
"""Modifica registro do Windows"""
|
||
|
|
hive = config.get('hive', 'HKLM')
|
||
|
|
key = config.get('key')
|
||
|
|
value_name = config.get('value_name')
|
||
|
|
value_data = config.get('value_data')
|
||
|
|
value_type = config.get('value_type', 'String')
|
||
|
|
|
||
|
|
print(f"📝 Modificando registro: {hive}\\{key}")
|
||
|
|
|
||
|
|
# Mapear hive
|
||
|
|
hive_map = {
|
||
|
|
'HKLM': winreg.HKEY_LOCAL_MACHINE,
|
||
|
|
'HKCU': winreg.HKEY_CURRENT_USER,
|
||
|
|
'HKCR': winreg.HKEY_CLASSES_ROOT,
|
||
|
|
'HKU': winreg.HKEY_USERS
|
||
|
|
}
|
||
|
|
|
||
|
|
# Mapear tipo
|
||
|
|
type_map = {
|
||
|
|
'String': winreg.REG_SZ,
|
||
|
|
'DWORD': winreg.REG_DWORD,
|
||
|
|
'Binary': winreg.REG_BINARY,
|
||
|
|
'MultiString': winreg.REG_MULTI_SZ
|
||
|
|
}
|
||
|
|
|
||
|
|
try:
|
||
|
|
reg_hive = hive_map.get(hive, winreg.HKEY_LOCAL_MACHINE)
|
||
|
|
reg_type = type_map.get(value_type, winreg.REG_SZ)
|
||
|
|
|
||
|
|
# Abrir ou criar chave
|
||
|
|
reg_key = winreg.CreateKey(reg_hive, key)
|
||
|
|
|
||
|
|
# Converter valor se necessário
|
||
|
|
if value_type == 'DWORD':
|
||
|
|
value_data = int(value_data)
|
||
|
|
|
||
|
|
# Setar valor
|
||
|
|
winreg.SetValueEx(reg_key, value_name, 0, reg_type, value_data)
|
||
|
|
winreg.CloseKey(reg_key)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'hive': hive,
|
||
|
|
'key': key,
|
||
|
|
'value_name': value_name,
|
||
|
|
'success': True
|
||
|
|
}
|
||
|
|
except Exception as e:
|
||
|
|
raise Exception(f"Erro ao modificar registro: {str(e)}")
|
||
|
|
|
||
|
|
def execute_reboot(self, config):
|
||
|
|
"""Reinicia o sistema"""
|
||
|
|
delay_minutes = config.get('delay_minutes', 5)
|
||
|
|
force = config.get('force', False)
|
||
|
|
message = config.get('message', 'O sistema será reiniciado')
|
||
|
|
|
||
|
|
print(f"🔄 Agendando reinicialização em {delay_minutes} minutos...")
|
||
|
|
|
||
|
|
delay_seconds = delay_minutes * 60
|
||
|
|
force_flag = '/f' if force else ''
|
||
|
|
|
||
|
|
subprocess.run(
|
||
|
|
['shutdown', '/r', '/t', str(delay_seconds), '/c', message, force_flag],
|
||
|
|
check=False
|
||
|
|
)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'delay_minutes': delay_minutes,
|
||
|
|
'scheduled': True
|
||
|
|
}
|
||
|
|
|
||
|
|
def execute_cleanup(self, config):
|
||
|
|
"""Limpa arquivos temporários"""
|
||
|
|
print("🧹 Limpando arquivos temporários...")
|
||
|
|
|
||
|
|
cleaned = {
|
||
|
|
'temp': False,
|
||
|
|
'windows_temp': False,
|
||
|
|
'browser_cache': False,
|
||
|
|
'recycle_bin': False
|
||
|
|
}
|
||
|
|
|
||
|
|
try:
|
||
|
|
if config.get('clear_temp', True):
|
||
|
|
temp_path = os.environ.get('TEMP')
|
||
|
|
# Limpar %TEMP%
|
||
|
|
subprocess.run(
|
||
|
|
['powershell', '-Command', f'Remove-Item -Path "{temp_path}\\*" -Recurse -Force -ErrorAction SilentlyContinue'],
|
||
|
|
check=False
|
||
|
|
)
|
||
|
|
cleaned['temp'] = True
|
||
|
|
|
||
|
|
if config.get('clear_windows_temp', True):
|
||
|
|
# Limpar C:\Windows\Temp
|
||
|
|
subprocess.run(
|
||
|
|
['powershell', '-Command', 'Remove-Item -Path "C:\\Windows\\Temp\\*" -Recurse -Force -ErrorAction SilentlyContinue'],
|
||
|
|
check=False
|
||
|
|
)
|
||
|
|
cleaned['windows_temp'] = True
|
||
|
|
|
||
|
|
if config.get('clear_recycle_bin', True):
|
||
|
|
# Esvaziar lixeira
|
||
|
|
subprocess.run(
|
||
|
|
['powershell', '-Command', 'Clear-RecycleBin -Force -ErrorAction SilentlyContinue'],
|
||
|
|
check=False
|
||
|
|
)
|
||
|
|
cleaned['recycle_bin'] = True
|
||
|
|
|
||
|
|
return cleaned
|
||
|
|
except Exception as e:
|
||
|
|
raise Exception(f"Erro na limpeza: {str(e)}")
|
||
|
|
|
||
|
|
def execute_firewall(self, config):
|
||
|
|
"""Configura regras de firewall"""
|
||
|
|
action = config.get('action', 'add_rule')
|
||
|
|
rule_name = config.get('rule_name')
|
||
|
|
|
||
|
|
print(f"🛡️ Configurando firewall: {rule_name}")
|
||
|
|
|
||
|
|
if action == 'add_rule':
|
||
|
|
direction = config.get('direction', 'inbound')
|
||
|
|
protocol = config.get('protocol', 'TCP')
|
||
|
|
port = config.get('port')
|
||
|
|
action_type = config.get('action_type', 'allow')
|
||
|
|
|
||
|
|
ps_script = f"""
|
||
|
|
New-NetFirewallRule -DisplayName "{rule_name}" `
|
||
|
|
-Direction {direction} `
|
||
|
|
-Protocol {protocol} `
|
||
|
|
-LocalPort {port} `
|
||
|
|
-Action {action_type}
|
||
|
|
"""
|
||
|
|
|
||
|
|
result = subprocess.run(
|
||
|
|
['powershell', '-Command', ps_script],
|
||
|
|
capture_output=True,
|
||
|
|
text=True
|
||
|
|
)
|
||
|
|
|
||
|
|
return {
|
||
|
|
'rule_name': rule_name,
|
||
|
|
'success': result.returncode == 0,
|
||
|
|
'output': result.stdout
|
||
|
|
}
|
||
|
|
|
||
|
|
return {'action': action, 'success': False}
|
||
|
|
|
||
|
|
def schedule_reboot(self, delay_minutes):
|
||
|
|
"""Agenda reinicialização"""
|
||
|
|
delay_seconds = delay_minutes * 60
|
||
|
|
subprocess.run(
|
||
|
|
['shutdown', '/r', '/t', str(delay_seconds)],
|
||
|
|
check=False
|
||
|
|
)
|
||
|
|
print(f"⏰ Reinicialização agendada em {delay_minutes} minutos")
|
||
|
|
|
||
|
|
|
||
|
|
# Função para integrar no cliente principal
|
||
|
|
def run_mdm_polling(api_url, device_id, interval_seconds=60):
|
||
|
|
"""
|
||
|
|
Função para ser executada em thread separada no cliente principal
|
||
|
|
Faz polling de comandos MDM periodicamente
|
||
|
|
"""
|
||
|
|
executor = MDMExecutor(api_url, device_id)
|
||
|
|
|
||
|
|
while True:
|
||
|
|
try:
|
||
|
|
commands = executor.poll_commands()
|
||
|
|
|
||
|
|
if commands:
|
||
|
|
print(f"📋 Recebidos {len(commands)} comando(s) MDM")
|
||
|
|
|
||
|
|
for command in commands:
|
||
|
|
executor.execute_command(command)
|
||
|
|
|
||
|
|
time.sleep(interval_seconds)
|
||
|
|
|
||
|
|
except KeyboardInterrupt:
|
||
|
|
break
|
||
|
|
except Exception as e:
|
||
|
|
print(f"❌ Erro no polling MDM: {e}")
|
||
|
|
time.sleep(interval_seconds)
|
||
|
|
|