diff --git a/baseTemplate/static/baseTemplate/custom-js/system-status.js b/baseTemplate/static/baseTemplate/custom-js/system-status.js index fab9a6274..76353b1e4 100644 --- a/baseTemplate/static/baseTemplate/custom-js/system-status.js +++ b/baseTemplate/static/baseTemplate/custom-js/system-status.js @@ -1130,19 +1130,12 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { console.log('Parsed responseData from string:', responseData); } catch (e) { console.error('Failed to parse response as JSON:', e); - console.error('Raw response string:', responseData); - // Try to extract error from string - if (responseData.includes('error')) { - if (typeof PNotify !== 'undefined') { - new PNotify({ - title: 'Error', - text: 'Failed to block IP address: ' + responseData, - type: 'error', - delay: 5000 - }); - } - return; + var errorMsg = responseData && responseData.length ? responseData : 'Failed to block IP address'; + if (typeof PNotify !== 'undefined') { + new PNotify({ title: 'Error', text: errorMsg, type: 'error', delay: 5000 }); } + $scope.blockingIP = null; + return; } } @@ -1217,28 +1210,20 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { $scope.lastErrorTime = Date.now(); var errorMessage = 'Failed to block IP address'; - if (err.data) { - var errData = err.data; - if (typeof errData === 'string') { - try { - errData = JSON.parse(errData); - } catch (e) { - errorMessage = errData || errorMessage; + var errData = err.data; + if (typeof errData === 'string') { + try { + errData = JSON.parse(errData); + } catch (e) { + if (errData && errData.length) { + errorMessage = errData.length > 200 ? errData.substring(0, 200) + '...' : errData; } } - if (errData && typeof errData === 'object') { - if (errData.error_message) { - errorMessage = errData.error_message; - } else if (errData.error) { - errorMessage = errData.error; - } else if (errData.message) { - errorMessage = errData.message; - } - } - } else if (err.statusText) { - errorMessage = err.statusText; + } + if (errData && typeof errData === 'object') { + errorMessage = errData.error_message || errData.error || errData.message || errorMessage; } else if (err.status) { - errorMessage = `HTTP ${err.status}: ${err.statusText || 'Unknown error'}`; + errorMessage = 'HTTP ' + err.status + ': ' + (errorMessage); } console.error('Final error message:', errorMessage); diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index 2373f10ae..3c078bf0f 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -1380,56 +1380,38 @@ } $.ajax({ - url: '/base/blockIPAddress', + url: '/firewall/addBannedIP', type: 'POST', contentType: 'application/json', headers: { 'X-CSRFToken': getCookie('csrftoken') }, data: JSON.stringify({ - 'ip_address': ipAddress, - 'reason': 'Security alert detected from dashboard' + 'ip': ipAddress, + 'reason': 'Brute force attack detected from SSH Security Analysis', + 'duration': 'permanent' }), success: function(data) { // Handle both success and error responses - if (data.status === 1) { - showNotification('success', data.message || 'IP address blocked successfully'); + if (data && data.status === 1) { + showNotification('success', data.message || 'IP address blocked successfully. Manage in Firewall > Banned IPs.'); // Refresh the page to update the blocked IPs list - setTimeout(() => { - location.reload(); - }, 1000); + setTimeout(function() { location.reload(); }, 1000); } else { - // Handle error response - check for both 'error' and 'error_message' fields - var errorMsg = data.error || data.error_message || data.message || 'Failed to block IP address'; + var errorMsg = (data && (data.error_message || data.error || data.message)) || 'Failed to block IP address'; showNotification('error', errorMsg); } }, error: function(xhr, status, error) { - // Handle network errors and parse JSON errors - console.error('Ban IP error:', xhr, status, error); + console.error('Ban IP error:', xhr.status, xhr.responseText); var errorMsg = 'Failed to block IP address. Please try again.'; - - // Log full response for debugging - console.log('Response status:', xhr.status); - console.log('Response text:', xhr.responseText); - - if (xhr.responseJSON) { - errorMsg = xhr.responseJSON.error || xhr.responseJSON.error_message || xhr.responseJSON.message || errorMsg; - console.log('Parsed error from JSON:', errorMsg); - } else if (xhr.responseText) { - try { - var errorData = JSON.parse(xhr.responseText); - errorMsg = errorData.error || errorData.error_message || errorData.message || errorMsg; - console.log('Parsed error from text:', errorMsg); - } catch(e) { - console.error('Failed to parse error response:', e); - // If parsing fails, try to extract error from response text - if (xhr.responseText.includes('error')) { - errorMsg = xhr.responseText.substring(0, 200); - } - } + var data = xhr.responseJSON; + if (!data && xhr.responseText) { + try { data = JSON.parse(xhr.responseText); } catch(e) {} + } + if (data && (data.error_message || data.error || data.message)) { + errorMsg = data.error_message || data.error || data.message; } - showNotification('error', errorMsg); }, complete: function() { diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index ab433a6e5..0802be080 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -1,6 +1,6 @@ {% load i18n %} {% get_current_language as LANGUAGE_CODE %} -{% with CP_VERSION="2.4.4.1" %} +{% with CP_VERSION=CYBERPANEL_FULL_VERSION|default:"2.5.5.dev" %}
diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 1c1c637c2..77ccc5509 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -16,6 +16,7 @@ from plogical.acl import ACLManager from manageServices.models import PDNSStatus from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt from plogical.processUtilities import ProcessUtilities +from plogical.firewallUtilities import FirewallUtilities from plogical.httpProc import httpProc from websiteFunctions.models import Websites, WPSites from databases.models import Databases @@ -1318,54 +1319,14 @@ def blockIPAddress(request): 'error': 'Invalid IP address' }), content_type='application/json', status=400) - # Use firewalld (CSF has been discontinued) + # Use FirewallUtilities so firewall-cmd runs with proper privileges (root/lscpd) firewall_cmd = 'firewalld' + reason = data.get('reason', 'Security alert detected from dashboard') try: - # Verify firewalld is active using subprocess for better security - import subprocess - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - return HttpResponse(json.dumps({ - 'status': 0, - 'error': 'Firewalld is not active. Please enable firewalld service.' - }), content_type='application/json', status=500) - except subprocess.TimeoutExpired: - return HttpResponse(json.dumps({ - 'status': 0, - 'error': 'Timeout checking firewalld status' - }), content_type='application/json', status=500) + success, msg = FirewallUtilities.blockIP(ip_address, reason) except Exception as e: - return HttpResponse(json.dumps({ - 'status': 0, - 'error': f'Cannot check firewalld status: {str(e)}' - }), content_type='application/json', status=500) - - # Block the IP address using firewalld with subprocess for better security - success = False - error_message = '' - - try: - # Use subprocess with explicit argument lists to prevent injection - rich_rule = f'rule family=ipv4 source address={ip_address} drop' - add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule] - - # Execute the add rule command - result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - success = True - else: - error_message = f'Failed to reload firewall rules: {reload_result.stderr}' - else: - error_message = f'Failed to add firewall rule: {result.stderr}' - except subprocess.TimeoutExpired: - error_message = 'Firewall command timed out' - except Exception as e: - error_message = f'Firewall command failed: {str(e)}' + success = False + msg = str(e) if success: # Add to banned IPs JSON file for consistency with firewall page @@ -1430,7 +1391,7 @@ def blockIPAddress(request): else: return HttpResponse(json.dumps({ 'status': 0, - 'error': error_message or 'Failed to block IP address' + 'error': msg or 'Failed to block IP address' }), content_type='application/json', status=500) except json.JSONDecodeError as e: diff --git a/deploy-email-limits-fix.sh b/deploy-email-limits-fix.sh new file mode 100755 index 000000000..51b0f9b1e --- /dev/null +++ b/deploy-email-limits-fix.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Deploy Email Limits fix to a CyberPanel installation. +# Copies recommended mailServer files and optionally restarts lscpd. +# +# Usage (run from anywhere): +# sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh +# sudo bash deploy-email-limits-fix.sh [REPO_DIR] [CP_DIR] +# +# Or from repo root: cd /home/cyberpanel-repo && sudo bash deploy-email-limits-fix.sh + +set -e + +log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; } +err() { log "ERROR: $*" >&2; } + +# Resolve REPO_DIR: explicit arg, then script dir, then common locations +if [[ -n "$1" && -d "$1/mailServer" ]]; then + REPO_DIR="$1" + shift +elif [[ -d "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/mailServer" ]]; then + REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +elif [[ -d "/home/cyberpanel-repo/mailServer" ]]; then + REPO_DIR="/home/cyberpanel-repo" +elif [[ -d "./mailServer" ]]; then + REPO_DIR="$(pwd)" +else + err "Repo not found. Use: sudo bash /home/cyberpanel-repo/deploy-email-limits-fix.sh" + err "Or: cd /path/to/cyberpanel-repo && sudo bash deploy-email-limits-fix.sh" + exit 1 +fi + +CP_DIR="${1:-/usr/local/CyberCP}" +RESTART_LSCPD="${RESTART_LSCPD:-1}" + +if [[ ! -d "$CP_DIR" ]]; then + err "CyberPanel directory not found: $CP_DIR" + exit 1 +fi +if [[ ! -d "$REPO_DIR/mailServer" ]]; then + err "Repo mailServer not found in: $REPO_DIR" + exit 1 +fi + +log "REPO_DIR=$REPO_DIR" +log "CP_DIR=$CP_DIR" + +FILES=( + "mailServer/mailserverManager.py" + "mailServer/templates/mailServer/EmailLimits.html" + "mailServer/static/mailServer/mailServer.js" + "mailServer/static/mailServer/emailLimitsController.js" +) + +for rel in "${FILES[@]}"; do + src="$REPO_DIR/$rel" + dst="$CP_DIR/$rel" + if [[ ! -f "$src" ]]; then + err "Source missing: $src" + exit 1 + fi + mkdir -p "$(dirname "$dst")" + cp -f "$src" "$dst" + log "Copied: $rel" +done + +if [[ "$RESTART_LSCPD" =~ ^(1|yes|true)$ ]]; then + if systemctl is-active --quiet lscpd 2>/dev/null; then + log "Restarting lscpd..." + systemctl restart lscpd || { err "lscpd restart failed"; exit 1; } + log "lscpd restarted." + else + log "lscpd not running or not a systemd service; skip restart." + fi +else + log "Skipping restart (set RESTART_LSCPD=1 to restart lscpd)." +fi + +log "Deploy complete. Hard-refresh /email/EmailLimits in the browser (Ctrl+Shift+R)." diff --git a/deploy-ftp-create-account-fix.sh b/deploy-ftp-create-account-fix.sh new file mode 100644 index 000000000..09ddab347 --- /dev/null +++ b/deploy-ftp-create-account-fix.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Deploy FTP Create Account template fix to a CyberPanel installation. +# Copies the updated createFTPAccount.html and optionally restarts lscpd. +# +# Usage (run from anywhere): +# sudo bash /home/cyberpanel-repo/deploy-ftp-create-account-fix.sh +# sudo bash deploy-ftp-create-account-fix.sh [REPO_DIR] [CP_DIR] +# +# Or from repo root: cd /home/cyberpanel-repo && sudo bash deploy-ftp-create-account-fix.sh + +set -e + +log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; } +err() { log "ERROR: $*" >&2; } + +# Resolve REPO_DIR +if [[ -n "$1" && -d "$1/ftp" ]]; then + REPO_DIR="$1" + shift +elif [[ -d "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/ftp" ]]; then + REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +elif [[ -d "/home/cyberpanel-repo/ftp" ]]; then + REPO_DIR="/home/cyberpanel-repo" +elif [[ -d "./ftp" ]]; then + REPO_DIR="$(pwd)" +else + err "Repo not found. Use: sudo bash /home/cyberpanel-repo/deploy-ftp-create-account-fix.sh" + exit 1 +fi + +CP_DIR="${1:-/usr/local/CyberCP}" +RESTART_LSCPD="${RESTART_LSCPD:-1}" + +if [[ ! -d "$CP_DIR" ]]; then + err "CyberPanel directory not found: $CP_DIR" + exit 1 +fi +if [[ ! -f "$REPO_DIR/ftp/templates/ftp/createFTPAccount.html" ]]; then + err "Source template not found in: $REPO_DIR" + exit 1 +fi + +log "REPO_DIR=$REPO_DIR" +log "CP_DIR=$CP_DIR" + +SRC="$REPO_DIR/ftp/templates/ftp/createFTPAccount.html" +DST="$CP_DIR/ftp/templates/ftp/createFTPAccount.html" +mkdir -p "$(dirname "$DST")" +cp -f "$SRC" "$DST" +log "Copied: ftp/templates/ftp/createFTPAccount.html" + +if [[ "$RESTART_LSCPD" =~ ^(1|yes|true)$ ]]; then + if systemctl is-active --quiet lscpd 2>/dev/null; then + log "Restarting lscpd..." + systemctl restart lscpd || { err "lscpd restart failed"; exit 1; } + log "lscpd restarted." + else + log "lscpd not running or not a systemd service; skip restart." + fi +else + log "Skipping restart (set RESTART_LSCPD=1 to restart lscpd)." +fi + +log "Deploy complete. Hard-refresh /ftp/createFTPAccount in the browser (Ctrl+Shift+R)." diff --git a/deploy-ftp-quotas-table.sh b/deploy-ftp-quotas-table.sh new file mode 100644 index 000000000..390acfe6d --- /dev/null +++ b/deploy-ftp-quotas-table.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Create the missing ftp_quotas table in the CyberPanel database. +# Fixes: (1146, "Table 'cyberpanel.ftp_quotas' doesn't exist") on /ftp/quotaManagement +# +# Usage: +# sudo bash /home/cyberpanel-repo/deploy-ftp-quotas-table.sh +# sudo bash deploy-ftp-quotas-table.sh [REPO_DIR] [CP_DIR] + +set -e + +log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*"; } +err() { log "ERROR: $*" >&2; } + +if [[ -n "$1" && -f "$1/sql/create_ftp_quotas.sql" ]]; then + REPO_DIR="$1" + shift +elif [[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/sql/create_ftp_quotas.sql" ]]; then + REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +elif [[ -f "/home/cyberpanel-repo/sql/create_ftp_quotas.sql" ]]; then + REPO_DIR="/home/cyberpanel-repo" +else + err "sql/create_ftp_quotas.sql not found." + exit 1 +fi + +CP_DIR="${1:-/usr/local/CyberCP}" +SQL_FILE="$REPO_DIR/sql/create_ftp_quotas.sql" + +if [[ ! -d "$CP_DIR" ]]; then + err "CyberPanel directory not found: $CP_DIR" + exit 1 +fi + +log "REPO_DIR=$REPO_DIR" +log "CP_DIR=$CP_DIR" + +mkdir -p "$CP_DIR/sql" +cp -f "$SQL_FILE" "$CP_DIR/sql/create_ftp_quotas.sql" +log "Copied create_ftp_quotas.sql to $CP_DIR/sql/" + +# Run SQL using Django DB connection (no password on command line) +log "Creating ftp_quotas table..." +export CP_DIR +python3 << 'PYEOF' +import os +import sys + +cp_dir = os.environ.get('CP_DIR', '/usr/local/CyberCP') +sys.path.insert(0, cp_dir) +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberCP.settings') + +import django +django.setup() + +from django.db import connection + +with open(os.path.join(cp_dir, 'sql', 'create_ftp_quotas.sql'), 'r') as f: + sql = f.read() + +with connection.cursor() as cursor: + cursor.execute(sql) + print('Executed CREATE TABLE IF NOT EXISTS ftp_quotas.') +PYEOF + +log "Done. Reload https://207.180.193.210:2087/ftp/quotaManagement" diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py index 097e5c0f3..0a50250e4 100644 --- a/firewall/firewallManager.py +++ b/firewall/firewallManager.py @@ -1935,14 +1935,15 @@ class FirewallManager: try: admin = Administrator.objects.get(pk=userID) if admin.acl.adminStatus != 1: - return ACLManager.loadError() + final_dic = {'status': 0, 'error_message': 'You are not authorized to access this resource.', 'error': 'You are not authorized to access this resource.'} + return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403) ip = data.get('ip', '').strip() reason = data.get('reason', '').strip() duration = data.get('duration', '24h') if not ip or not reason: - final_dic = {'status': 0, 'error_message': 'IP address and reason are required'} + final_dic = {'status': 0, 'error_message': 'IP address and reason are required', 'error': 'IP address and reason are required'} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Validate IP address format @@ -1950,7 +1951,7 @@ class FirewallManager: try: ipaddress.ip_address(ip.split('/')[0]) # Handle CIDR notation except ValueError: - final_dic = {'status': 0, 'error_message': 'Invalid IP address format'} + final_dic = {'status': 0, 'error_message': 'Invalid IP address format', 'error': 'Invalid IP address format'} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Calculate expiration time @@ -1973,7 +1974,8 @@ class FirewallManager: # Check if IP is already banned for banned_ip in banned_ips: if banned_ip.get('ip') == ip and banned_ip.get('active', True): - final_dic = {'status': 0, 'error_message': 'IP address %s is already banned' % ip} + msg = 'IP address %s is already banned' % ip + final_dic = {'status': 0, 'error_message': msg, 'error': msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Add new banned IP @@ -1991,54 +1993,38 @@ class FirewallManager: # Save to file self._save_banned_ips_store(banned_ips) - # Apply firewall rule to block the IP using firewalld + # Apply firewall rule using FirewallUtilities (runs with proper privileges via ProcessUtilities/lscpd) try: - import subprocess - # Verify firewalld is active - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - final_dic = {'status': 0, 'error_message': 'Firewalld is not active. Please enable firewalld service.'} + block_ok, block_msg = FirewallUtilities.blockIP(ip, reason) + if not block_ok: + # Rollback: remove the IP we just added from the store + banned_ips_rollback = [b for b in banned_ips if b.get('ip') != ip or not b.get('active', True)] + if len(banned_ips_rollback) < len(banned_ips): + self._save_banned_ips_store(banned_ips_rollback) + logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, block_msg)) + err_msg = block_msg or 'Failed to add firewall rule' + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') - - # Add firewalld rich rule to block the IP - rich_rule = f'rule family=ipv4 source address={ip} drop' - add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule] - - # Execute the add rule command - result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}') - else: - logging.CyberCPLogFileWriter.writeToFile('Failed to reload firewalld for %s: %s' % (ip, reload_result.stderr)) - final_dic = {'status': 0, 'error_message': 'Failed to reload firewall rules: %s' % reload_result.stderr} - return HttpResponse(json.dumps(final_dic), content_type='application/json') - else: - # Check if rule already exists (this is not an error) - if result.stderr and ('ALREADY_ENABLED' in result.stderr or 'already exists' in result.stderr.lower()): - logging.CyberCPLogFileWriter.writeToFile('IP %s already blocked in firewalld' % ip) - else: - logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, result.stderr or '')) - final_dic = {'status': 0, 'error_message': 'Failed to add firewall rule: %s' % (result.stderr or 'Unknown error')} - return HttpResponse(json.dumps(final_dic), content_type='application/json') - except subprocess.TimeoutExpired: - logging.CyberCPLogFileWriter.writeToFile('Timeout adding firewalld rule for %s' % ip) - final_dic = {'status': 0, 'error_message': 'Firewall command timed out'} - return HttpResponse(json.dumps(final_dic), content_type='application/json') + logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}') except Exception as e: + # Rollback store on any exception + try: + banned_ips_rollback = [b for b in banned_ips if b.get('ip') != ip or not b.get('active', True)] + if len(banned_ips_rollback) < len(banned_ips): + self._save_banned_ips_store(banned_ips_rollback) + except Exception: + pass logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, str(e))) - final_dic = {'status': 0, 'error_message': 'Firewall command failed: %s' % str(e)} + err_msg = 'Firewall command failed: %s' % str(e) + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') final_dic = {'status': 1, 'message': 'IP address %s has been banned successfully' % ip} return HttpResponse(json.dumps(final_dic), content_type='application/json') except BaseException as msg: - final_dic = {'status': 0, 'error_message': str(msg)} + err_msg = str(msg) + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') def removeBannedIP(self, userID=None, data=None): @@ -2085,16 +2071,9 @@ class FirewallManager: ip_to_unban = banned_ip.ip_address banned_ip.active = False banned_ip.save() - # Remove firewalld rule + # Remove firewalld rule using FirewallUtilities (runs with proper privileges) try: - import subprocess - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout: - rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop' - subprocess.run(['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule], - capture_output=True, text=True, timeout=30) - subprocess.run(['firewall-cmd', '--reload'], capture_output=True, text=True, timeout=30) + FirewallUtilities.unblockIP(ip_to_unban) except Exception as e: logging.CyberCPLogFileWriter.writeToFile(f'Warning removing firewalld rule: {str(e)}') final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'} @@ -2124,40 +2103,12 @@ class FirewallManager: # Save updated banned IPs self._save_banned_ips_store(banned_ips) - # Remove firewalld rule to unblock the IP + # Remove firewalld rule using FirewallUtilities (runs with proper privileges) try: - import subprocess - # Verify firewalld is active - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - # Firewalld not active, but still mark as unbanned in JSON - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Firewalld not active when unbanned IP {ip_to_unban}') - else: - # Remove firewalld rich rule - rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop' - remove_rule_cmd = ['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule] - - # Execute the remove rule command - result = subprocess.run(remove_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}') - else: - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to reload firewalld after unbanning {ip_to_unban}: {reload_result.stderr}') - else: - # Rule might not exist, which is okay - if 'NOT_ENABLED' in result.stderr or 'not found' in result.stderr.lower(): - logging.CyberCPLogFileWriter.writeToFile(f'IP {ip_to_unban} rule not found in firewalld (may have been removed already)') - else: - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to remove firewalld rule for {ip_to_unban}: {result.stderr}') - except subprocess.TimeoutExpired: - logging.CyberCPLogFileWriter.writeToFile(f'Timeout removing firewalld rule for {ip_to_unban}') + FirewallUtilities.unblockIP(ip_to_unban) + logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}') except Exception as e: - logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove firewalld rule for {ip_to_unban}: {str(e)}') + logging.CyberCPLogFileWriter.writeToFile(f'Warning removing firewalld rule for {ip_to_unban}: {str(e)}') final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'} final_json = json.dumps(final_dic) diff --git a/ftp/static/ftp/ftp.js b/ftp/static/ftp/ftp.js index 6a7cb75da..ef6cd4a4e 100644 --- a/ftp/static/ftp/ftp.js +++ b/ftp/static/ftp/ftp.js @@ -15,7 +15,7 @@ app.controller('createFTPAccount', function ($scope, $http) { $scope.generatedPasswordView = true; $(document).ready(function () { - $( ".ftpDetails" ).hide(); + $( ".ftpDetails, .account-details" ).hide(); $( ".ftpPasswordView" ).hide(); // Only use select2 if it's actually a function (avoids errors when Rocket Loader defers scripts) @@ -26,9 +26,11 @@ app.controller('createFTPAccount', function ($scope, $http) { $sel.select2(); $sel.on('select2:select', function (e) { var data = e.params.data; - $scope.ftpDomain = data.text; - $scope.$apply(); - $(".ftpDetails").show(); + $scope.$evalAsync(function () { + $scope.ftpDomain = data.text; + $scope.ftpDetails = false; + }); + $(".ftpDetails, .account-details").show(); }); } else { initNativeSelect(); @@ -41,20 +43,23 @@ app.controller('createFTPAccount', function ($scope, $http) { } function initNativeSelect() { $('.create-ftp-acct-select').off('select2:select').on('change', function () { - $scope.ftpDomain = $(this).val(); - $scope.$apply(); - $(".ftpDetails").show(); + var val = $(this).val(); + $scope.$evalAsync(function () { + $scope.ftpDomain = val; + $scope.ftpDetails = (val && val !== '') ? false : true; + }); + $(".ftpDetails, .account-details").show(); }); } }); $scope.showFTPDetails = function() { if ($scope.ftpDomain && $scope.ftpDomain !== "") { - $(".ftpDetails").show(); $scope.ftpDetails = false; + $(".ftpDetails, .account-details").show(); } else { - $(".ftpDetails").hide(); $scope.ftpDetails = true; + $(".ftpDetails, .account-details").hide(); } }; diff --git a/ftp/templates/ftp/createFTPAccount.html b/ftp/templates/ftp/createFTPAccount.html index 35e796608..6fc8e8a1c 100644 --- a/ftp/templates/ftp/createFTPAccount.html +++ b/ftp/templates/ftp/createFTPAccount.html @@ -452,18 +452,65 @@Enable and manage individual FTP user quotas. This allows you to set storage limits for each FTP user.
-