Merge pull request #1510 from master3395/v2.5.5-dev

V2.5.5 dev - Firewall ban button, and management
This commit is contained in:
Usman Nasir
2025-09-23 12:29:21 +05:00
committed by GitHub
109 changed files with 15095 additions and 1944 deletions

View File

@@ -1152,6 +1152,12 @@ Automatic backup failed for %s on %s.
#
# command = 'chattr -R -i /home/%s/incbackup/' % (website.domain)
# ProcessUtilities.executioner(command)
#
# command = 'chattr -R -i /home/%s/lscache/' % (website.domain)
# ProcessUtilities.executioner(command)
#
# command = 'chattr -R -i /home/%s/.cagefs/' % (website.domain)
# ProcessUtilities.executioner(command)
# else:
# command = 'chattr -R -i /home/%s/' % (website.domain)
# ProcessUtilities.executioner(command)

View File

@@ -139,6 +139,134 @@ class CronUtil:
command = 'chmod 1730 /var/spool/cron/crontabs'
ProcessUtilities.outputExecutioner(command)
@staticmethod
def suspendWebsiteCrons(externalApp):
"""
Suspend all cron jobs for a website by backing up and clearing the cron file.
This prevents cron jobs from running when a website is suspended.
"""
try:
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
cronPath = "/var/spool/cron/" + externalApp
backupPath = "/var/spool/cron/" + externalApp + ".suspended"
else:
cronPath = "/var/spool/cron/crontabs/" + externalApp
backupPath = "/var/spool/cron/crontabs/" + externalApp + ".suspended"
# Check if cron file exists
if not os.path.exists(cronPath):
print("1,None") # No cron file to suspend
return
# Create backup of current cron jobs
try:
command = f'cp {cronPath} {backupPath}'
ProcessUtilities.executioner(command, 'root')
except Exception as e:
print(f"0,Warning: Could not backup cron file: {str(e)}")
# Clear the cron file to suspend all jobs
try:
CronUtil.CronPrem(1) # Enable permissions
# Create empty cron file or clear existing one
with open(cronPath, 'w') as f:
f.write('') # Empty file to disable all cron jobs
# Set proper ownership
command = f'chown {externalApp}:{externalApp} {cronPath}'
ProcessUtilities.executioner(command, 'root')
CronUtil.CronPrem(0) # Restore permissions
print("1,Cron jobs suspended successfully")
except Exception as e:
CronUtil.CronPrem(0) # Ensure permissions are restored
print(f"0,Failed to suspend cron jobs: {str(e)}")
except Exception as e:
print(f"0,Error suspending cron jobs: {str(e)}")
@staticmethod
def restoreWebsiteCrons(externalApp):
"""
Restore cron jobs for a website by restoring from backup file.
This restores cron jobs when a website is unsuspended.
"""
try:
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
cronPath = "/var/spool/cron/" + externalApp
backupPath = "/var/spool/cron/" + externalApp + ".suspended"
else:
cronPath = "/var/spool/cron/crontabs/" + externalApp
backupPath = "/var/spool/cron/crontabs/" + externalApp + ".suspended"
# Check if backup file exists
if not os.path.exists(backupPath):
print("1,No suspended cron jobs to restore")
return
try:
CronUtil.CronPrem(1) # Enable permissions
# Restore cron jobs from backup
command = f'cp {backupPath} {cronPath}'
ProcessUtilities.executioner(command, 'root')
# Set proper ownership
command = f'chown {externalApp}:{externalApp} {cronPath}'
ProcessUtilities.executioner(command, 'root')
# Remove backup file
os.remove(backupPath)
CronUtil.CronPrem(0) # Restore permissions
print("1,Cron jobs restored successfully")
except Exception as e:
CronUtil.CronPrem(0) # Ensure permissions are restored
print(f"0,Failed to restore cron jobs: {str(e)}")
except Exception as e:
print(f"0,Error restoring cron jobs: {str(e)}")
@staticmethod
def getCronSuspensionStatus(externalApp):
"""
Check if cron jobs are currently suspended for a website.
Returns 1 if suspended, 0 if active, -1 if error.
"""
try:
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
cronPath = "/var/spool/cron/" + externalApp
backupPath = "/var/spool/cron/" + externalApp + ".suspended"
else:
cronPath = "/var/spool/cron/crontabs/" + externalApp
backupPath = "/var/spool/cron/crontabs/" + externalApp + ".suspended"
# Check if backup file exists (indicates suspension)
if os.path.exists(backupPath):
print("1,Cron jobs are suspended")
return
elif os.path.exists(cronPath):
# Check if cron file is empty (also indicates suspension)
try:
with open(cronPath, 'r') as f:
content = f.read().strip()
if not content:
print("1,Cron jobs are suspended (empty file)")
else:
print("0,Cron jobs are active")
except Exception as e:
print(f"-1,Error reading cron file: {str(e)}")
else:
print("0,No cron jobs configured")
except Exception as e:
print(f"-1,Error checking cron status: {str(e)}")
def main():
@@ -162,6 +290,12 @@ def main():
CronUtil.remCronbyLine(args.externalApp, int(args.line))
elif args.function == "addNewCron":
CronUtil.addNewCron(args.externalApp, args.finalCron)
elif args.function == "suspendWebsiteCrons":
CronUtil.suspendWebsiteCrons(args.externalApp)
elif args.function == "restoreWebsiteCrons":
CronUtil.restoreWebsiteCrons(args.externalApp)
elif args.function == "getCronSuspensionStatus":
CronUtil.getCronSuspensionStatus(args.externalApp)

273
plogical/errorSanitizer.py Normal file
View File

@@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
"""
CyberPanel Error Sanitization Utility
=====================================
This module provides secure error handling and sanitization to prevent
information disclosure vulnerabilities while maintaining useful error
reporting for debugging purposes.
Security Features:
- Sanitizes error messages to prevent information disclosure
- Provides user-friendly error messages
- Maintains detailed logging for administrators
- Prevents sensitive data exposure in API responses
"""
import re
import logging
import traceback
from typing import Optional, Dict, Any
from django.conf import settings
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
class ErrorSanitizer:
"""
Centralized error sanitization and handling utility
"""
# Sensitive patterns that should be masked in error messages
SENSITIVE_PATTERNS = [
# File paths
r'/home/[^/]+/',
r'/usr/local/[^/]+/',
r'/var/[^/]+/',
r'/etc/[^/]+/',
# Database credentials
r'password[=\s]*[^\s]+',
r'passwd[=\s]*[^\s]+',
r'pwd[=\s]*[^\s]+',
# API keys and tokens
r'api[_-]?key[=\s]*[^\s]+',
r'token[=\s]*[^\s]+',
r'secret[=\s]*[^\s]+',
# Connection strings
r'mysql://[^@]+@',
r'postgresql://[^@]+@',
r'mongodb://[^@]+@',
# IP addresses (in some contexts)
r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
# Email addresses
r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
]
# Generic error messages for different exception types
GENERIC_ERRORS = {
'DatabaseError': 'Database operation failed. Please try again.',
'ConnectionError': 'Unable to connect to the service. Please check your connection.',
'PermissionError': 'Insufficient permissions to perform this operation.',
'FileNotFoundError': 'Required file not found. Please contact support.',
'OSError': 'System operation failed. Please try again.',
'ValueError': 'Invalid input provided. Please check your data.',
'KeyError': 'Required information is missing. Please try again.',
'TypeError': 'Invalid data type provided. Please check your input.',
'AttributeError': 'System configuration error. Please contact support.',
'ImportError': 'System module error. Please contact support.',
'TimeoutError': 'Operation timed out. Please try again.',
'BaseException': 'An unexpected error occurred. Please try again.',
}
@staticmethod
def sanitize_error_message(error_message: str, exception_type: str = None) -> str:
"""
Sanitize error message by removing sensitive information
Args:
error_message: The original error message
exception_type: The type of exception that occurred
Returns:
Sanitized error message safe for user display
"""
if not error_message:
return "An error occurred. Please try again."
# Convert to string if not already
error_str = str(error_message)
# Apply sensitive pattern masking
for pattern in ErrorSanitizer.SENSITIVE_PATTERNS:
error_str = re.sub(pattern, '[REDACTED]', error_str, flags=re.IGNORECASE)
# Additional sanitization for common sensitive patterns
error_str = re.sub(r'[^\x00-\x7F]+', '[NON-ASCII]', error_str) # Remove non-ASCII chars
error_str = re.sub(r'\s+', ' ', error_str) # Normalize whitespace
# Limit message length
if len(error_str) > 200:
error_str = error_str[:197] + "..."
return error_str.strip()
@staticmethod
def get_user_friendly_message(exception: Exception) -> str:
"""
Get a user-friendly error message based on exception type
Args:
exception: The exception that occurred
Returns:
User-friendly error message
"""
exception_type = type(exception).__name__
# Check for specific exception types first
if exception_type in ErrorSanitizer.GENERIC_ERRORS:
return ErrorSanitizer.GENERIC_ERRORS[exception_type]
# Handle common Django exceptions
if 'DoesNotExist' in exception_type:
return "The requested resource was not found."
elif 'ValidationError' in exception_type:
return "Invalid data provided. Please check your input."
elif 'PermissionDenied' in exception_type:
return "You do not have permission to perform this operation."
# Default generic message
return "An unexpected error occurred. Please try again."
@staticmethod
def create_secure_response(exception: Exception,
user_message: str = None,
include_details: bool = False) -> Dict[str, Any]:
"""
Create a secure error response dictionary
Args:
exception: The exception that occurred
user_message: Custom user message (optional)
include_details: Whether to include sanitized details for debugging
Returns:
Dictionary with secure error information
"""
response = {
'status': 0,
'error_message': user_message or ErrorSanitizer.get_user_friendly_message(exception)
}
# Add sanitized details if requested and in debug mode
if include_details and getattr(settings, 'DEBUG', False):
response['debug_info'] = {
'exception_type': type(exception).__name__,
'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception))
}
return response
@staticmethod
def log_error_securely(exception: Exception,
context: str = None,
user_id: str = None,
request_info: Dict = None):
"""
Log error securely without exposing sensitive information
Args:
exception: The exception that occurred
context: Context where the error occurred
user_id: ID of the user who encountered the error
request_info: Request information (sanitized)
"""
try:
# Create secure log entry
log_entry = {
'timestamp': logging.get_current_timestamp(),
'exception_type': type(exception).__name__,
'context': context or 'Unknown',
'user_id': user_id or 'Anonymous',
'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception))
}
# Add request info if provided
if request_info:
log_entry['request_info'] = {
'method': request_info.get('method', 'Unknown'),
'path': request_info.get('path', 'Unknown'),
'ip': request_info.get('ip', 'Unknown')
}
# Log the error
logging.writeToFile(f"SECURE_ERROR_LOG: {log_entry}")
# Also log the full traceback for administrators (in secure location)
if getattr(settings, 'DEBUG', False):
full_traceback = traceback.format_exc()
sanitized_traceback = ErrorSanitizer.sanitize_error_message(full_traceback)
logging.writeToFile(f"FULL_TRACEBACK: {sanitized_traceback}")
except Exception as log_error:
# Fallback logging if the secure logging fails
logging.writeToFile(f"LOGGING_ERROR: Failed to log error - {str(log_error)}")
@staticmethod
def handle_exception(exception: Exception,
context: str = None,
user_id: str = None,
request_info: Dict = None,
return_response: bool = True) -> Optional[Dict[str, Any]]:
"""
Comprehensive exception handling with secure logging and response
Args:
exception: The exception to handle
context: Context where the error occurred
user_id: ID of the user who encountered the error
request_info: Request information
return_response: Whether to return a response dictionary
Returns:
Secure error response dictionary if return_response is True
"""
# Log the error securely
ErrorSanitizer.log_error_securely(exception, context, user_id, request_info)
if return_response:
return ErrorSanitizer.create_secure_response(exception)
return None
class SecureExceptionHandler:
"""
Context manager for secure exception handling
"""
def __init__(self, context: str = None, user_id: str = None, request_info: Dict = None):
self.context = context
self.user_id = user_id
self.request_info = request_info
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
ErrorSanitizer.handle_exception(
exc_val,
self.context,
self.user_id,
self.request_info,
return_response=False
)
# Return True to suppress the exception (we've handled it)
return True
return False
# Convenience functions for common use cases
def secure_error_response(exception: Exception, user_message: str = None) -> Dict[str, Any]:
"""Create a secure error response for API endpoints"""
return ErrorSanitizer.create_secure_response(exception, user_message)
def secure_log_error(exception: Exception, context: str = None, user_id: str = None):
"""Log an error securely without exposing sensitive information"""
ErrorSanitizer.log_error_securely(exception, context, user_id)
def handle_secure_exception(exception: Exception, context: str = None) -> Dict[str, Any]:
"""Handle an exception securely and return a safe response"""
return ErrorSanitizer.handle_exception(exception, context, return_response=True)

View File

@@ -89,11 +89,35 @@ class FTPUtilities:
@staticmethod
def ftpFunctions(path,externalApp):
try:
command = 'mkdir %s' % (path)
ProcessUtilities.executioner(command, externalApp)
return 1,'None'
# Enhanced path validation and creation
import os
# Check if path already exists
if os.path.exists(path):
# Path exists, ensure it's a directory
if not os.path.isdir(path):
return 0, "Specified path exists but is not a directory"
# Set proper permissions
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
ProcessUtilities.executioner(command, externalApp)
return 1, 'None'
else:
# Create the directory with proper permissions
command = 'mkdir -p %s' % (path)
result = ProcessUtilities.executioner(command, externalApp)
if result == 0:
# Set proper ownership
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
ProcessUtilities.executioner(command, externalApp)
# Set proper permissions (755)
command = 'chmod 755 %s' % (path)
ProcessUtilities.executioner(command, externalApp)
return 1, 'None'
else:
return 0, "Failed to create directory: %s" % path
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(
@@ -118,30 +142,43 @@ class FTPUtilities:
## gid , uid ends
path = path.lstrip("/")
# Enhanced path validation and handling
if path and path.strip() and path != 'None':
# Clean the path
path = path.strip().lstrip("/")
# Additional security checks
if path.find("..") > -1 or path.find("~") > -1 or path.startswith("/"):
raise BaseException("Invalid path: Path must be relative and not contain '..' or '~' or start with '/'")
# Check for dangerous characters
dangerous_chars = [';', '|', '&', '$', '`', '\'', '"', '<', '>', '*', '?']
if any(char in path for char in dangerous_chars):
raise BaseException("Invalid path: Path contains dangerous characters")
# Construct full path
full_path = "/home/" + domainName + "/" + path
# Additional security: ensure path is within domain directory
domain_home = "/home/" + domainName
if not os.path.abspath(full_path).startswith(os.path.abspath(domain_home)):
raise BaseException("Security violation: Path must be within domain directory")
if path != 'None':
path = "/home/" + domainName + "/" + path
## Security Check
if path.find("..") > -1:
raise BaseException("Specified path must be inside virtual host home!")
result = FTPUtilities.ftpFunctions(path, externalApp)
result = FTPUtilities.ftpFunctions(full_path, externalApp)
if result[0] == 1:
pass
path = full_path
else:
raise BaseException(result[1])
raise BaseException("Path validation failed: " + result[1])
else:
path = "/home/" + domainName
# Enhanced symlink handling
if os.path.islink(path):
print("0, %s file is symlinked." % (path))
return 0
logging.CyberCPLogFileWriter.writeToFile(
"FTP path is symlinked: %s" % path)
raise BaseException("Cannot create FTP account: Path is a symbolic link")
ProcessUtilities.decideDistro()
@@ -266,6 +303,9 @@ class FTPUtilities:
ftp.save()
# Apply quota to filesystem if needed
FTPUtilities.applyQuotaToFilesystem(ftp)
return 1, "FTP quota updated successfully"
except Users.DoesNotExist:
@@ -274,6 +314,107 @@ class FTPUtilities:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [updateFTPQuota]")
return 0, str(msg)
@staticmethod
def applyQuotaToFilesystem(ftp_user):
"""
Apply quota settings to the filesystem level
"""
try:
import subprocess
# Get the user's directory
user_dir = ftp_user.dir
if not user_dir or not os.path.exists(user_dir):
return False, "User directory not found"
# Convert quota from MB to KB for setquota command
quota_kb = ftp_user.quotasize * 1024
# Apply quota using setquota command
# Note: This requires quota tools to be installed
try:
# Set both soft and hard limits to the same value
subprocess.run([
'setquota', '-u', str(ftp_user.uid),
f'{quota_kb}K', f'{quota_kb}K',
'0', '0', # inode limits (unlimited)
user_dir
], check=True, capture_output=True)
logging.CyberCPLogFileWriter.writeToFile(f"Applied quota {quota_kb}KB to user {ftp_user.user} in {user_dir}")
return True, "Quota applied successfully"
except subprocess.CalledProcessError as e:
logging.CyberCPLogFileWriter.writeToFile(f"Failed to apply quota: {e}")
return False, f"Failed to apply quota: {e}"
except FileNotFoundError:
# setquota command not found, quota tools not installed
logging.CyberCPLogFileWriter.writeToFile("setquota command not found - quota tools may not be installed")
return False, "Quota tools not installed"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error applying quota to filesystem: {str(e)}")
return False, str(e)
@staticmethod
def getFTPQuotaUsage(ftpUsername):
"""
Get current quota usage for an FTP user
"""
try:
ftp = Users.objects.get(user=ftpUsername)
user_dir = ftp.dir
if not user_dir or not os.path.exists(user_dir):
return 0, "User directory not found"
# Get directory size in MB
import subprocess
result = subprocess.run(['du', '-sm', user_dir], capture_output=True, text=True)
if result.returncode == 0:
usage_mb = int(result.stdout.split()[0])
quota_mb = ftp.quotasize
usage_percent = (usage_mb / quota_mb * 100) if quota_mb > 0 else 0
return {
'usage_mb': usage_mb,
'quota_mb': quota_mb,
'usage_percent': round(usage_percent, 2),
'remaining_mb': max(0, quota_mb - usage_mb)
}
else:
return 0, "Failed to get directory size"
except Users.DoesNotExist:
return 0, "FTP user not found"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error getting quota usage: {str(e)}")
return 0, str(e)
@staticmethod
def migrateExistingFTPUsers():
"""
Migrate existing FTP users to use the new quota system
"""
try:
migrated_count = 0
for ftp_user in Users.objects.all():
# If custom_quota_enabled is not set, set it to False and use package default
if not hasattr(ftp_user, 'custom_quota_enabled') or ftp_user.custom_quota_enabled is None:
ftp_user.custom_quota_enabled = False
ftp_user.custom_quota_size = 0
ftp_user.quotasize = ftp_user.domain.package.diskSpace
ftp_user.save()
migrated_count += 1
return 1, f"Migrated {migrated_count} FTP users to new quota system"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error migrating FTP users: {str(e)}")
return 0, str(e)
def main():

View File

@@ -400,11 +400,12 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
def setupOWASPRules():
try:
pathTOOWASPFolder = os.path.join(virtualHostUtilities.Server_root, "conf/modsec/owasp")
pathToOWASFolderNew = '%s/modsec/owasp-modsecurity-crs-3.0-master' % (virtualHostUtilities.vhostConfPath)
pathToOWASFolderNew = '%s/modsec/owasp-modsecurity-crs-4.18.0' % (virtualHostUtilities.vhostConfPath)
command = 'mkdir -p /usr/local/lsws/conf/modsec'
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to create modsec directory: " + str(result) + " [setupOWASPRules]")
return 0
if os.path.exists(pathToOWASFolderNew):
@@ -416,22 +417,32 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
if os.path.exists('owasp.tar.gz'):
os.remove('owasp.tar.gz')
command = "wget https://github.com/coreruleset/coreruleset/archive/v3.3.2/master.zip -O /usr/local/lsws/conf/modsec/owasp.zip"
# Clean up any existing zip file
if os.path.exists('/usr/local/lsws/conf/modsec/owasp.zip'):
os.remove('/usr/local/lsws/conf/modsec/owasp.zip')
command = "wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.18.0.zip -O /usr/local/lsws/conf/modsec/owasp.zip"
logging.CyberCPLogFileWriter.writeToFile("Downloading OWASP rules: " + command + " [setupOWASPRules]")
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to download OWASP rules: " + str(result) + " [setupOWASPRules]")
return 0
command = "unzip -o /usr/local/lsws/conf/modsec/owasp.zip -d /usr/local/lsws/conf/modsec/"
logging.CyberCPLogFileWriter.writeToFile("Extracting OWASP rules: " + command + " [setupOWASPRules]")
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to extract OWASP rules: " + str(result) + " [setupOWASPRules]")
return 0
command = 'mv /usr/local/lsws/conf/modsec/coreruleset-3.3.2 /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-master'
command = 'mv /usr/local/lsws/conf/modsec/coreruleset-4.18.0 /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.18.0'
logging.CyberCPLogFileWriter.writeToFile("Moving OWASP rules: " + command + " [setupOWASPRules]")
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to move OWASP rules: " + str(result) + " [setupOWASPRules]")
return 0
command = 'mv %s/crs-setup.conf.example %s/crs-setup.conf' % (pathToOWASFolderNew, pathToOWASFolderNew)
@@ -453,32 +464,8 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
if result != 0:
return 0
content = """include {pathToOWASFolderNew}/crs-setup.conf
include {pathToOWASFolderNew}/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
include {pathToOWASFolderNew}/rules/REQUEST-901-INITIALIZATION.conf
include {pathToOWASFolderNew}/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
include {pathToOWASFolderNew}/rules/REQUEST-910-IP-REPUTATION.conf
include {pathToOWASFolderNew}/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
include {pathToOWASFolderNew}/rules/REQUEST-912-DOS-PROTECTION.conf
include {pathToOWASFolderNew}/rules/REQUEST-913-SCANNER-DETECTION.conf
include {pathToOWASFolderNew}/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
include {pathToOWASFolderNew}/rules/REQUEST-921-PROTOCOL-ATTACK.conf
include {pathToOWASFolderNew}/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
include {pathToOWASFolderNew}/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
include {pathToOWASFolderNew}/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
include {pathToOWASFolderNew}/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
include {pathToOWASFolderNew}/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
include {pathToOWASFolderNew}/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
include {pathToOWASFolderNew}/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
include {pathToOWASFolderNew}/rules/REQUEST-949-BLOCKING-EVALUATION.conf
include {pathToOWASFolderNew}/rules/RESPONSE-950-DATA-LEAKAGES.conf
include {pathToOWASFolderNew}/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
include {pathToOWASFolderNew}/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
include {pathToOWASFolderNew}/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
include {pathToOWASFolderNew}/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
include {pathToOWASFolderNew}/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
include {pathToOWASFolderNew}/rules/RESPONSE-980-CORRELATION.conf
include {pathToOWASFolderNew}/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
# CRS v4.0.0 uses a different structure - it has a main crs.conf file
content = """include {pathToOWASFolderNew}/crs.conf
"""
writeToFile = open('%s/owasp-master.conf' % (pathToOWASFolderNew), 'w')
writeToFile.write(content.replace('{pathToOWASFolderNew}', pathToOWASFolderNew))
@@ -501,7 +488,7 @@ include {pathToOWASFolderNew}/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
owaspRulesConf = """
modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-master/owasp-master.conf
modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.18.0/owasp-master.conf
"""
confFile = os.path.join(virtualHostUtilities.Server_root, "conf/httpd_config.conf")
@@ -519,6 +506,14 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-mas
conf.writelines(items)
conf.close()
# Verify the installation
owaspPath = os.path.join(virtualHostUtilities.Server_root, "conf/modsec/owasp-modsecurity-crs-4.18.0")
if not os.path.exists(owaspPath) or not os.path.exists(os.path.join(owaspPath, "owasp-master.conf")):
logging.CyberCPLogFileWriter.writeToFile("OWASP installation verification failed - files not found [installOWASP]")
print("0, OWASP installation verification failed")
return
else:
confFile = os.path.join('/usr/local/lsws/conf/modsec.conf')
confData = open(confFile).readlines()
@@ -528,7 +523,7 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-mas
for items in confData:
if items.find('/conf/comodo_litespeed/') > -1:
conf.writelines(items)
conf.write('Include /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-master/*.conf\n')
conf.write('Include /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.18.0/*.conf\n')
continue
else:
conf.writelines(items)
@@ -536,7 +531,8 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-mas
conf.close()
installUtilities.reStartLiteSpeed()
logging.CyberCPLogFileWriter.writeToFile("OWASP ModSecurity rules installed successfully [installOWASP]")
print("1,None")
except BaseException as msg:

View File

@@ -150,10 +150,10 @@ class remoteBackup:
return [0, msg]
@staticmethod
def postRemoteTransfer(ipAddress, ownIP ,password, sshkey):
def postRemoteTransfer(ipAddress, ownIP ,password, sshkey, cyberPanelPort=8090):
try:
finalData = json.dumps({'username': "admin", "ipAddress": ownIP, "password": password})
url = "https://" + ipAddress + ":8090/api/remoteTransfer"
url = "https://" + ipAddress + ":" + str(cyberPanelPort) + "/api/remoteTransfer"
r = requests.post(url, data=finalData, verify=False)
data = json.loads(r.text)

View File

@@ -78,6 +78,9 @@ class Renew:
try:
logging.writeToFile('Restarting mail services for them to see new SSL.', 0)
# Update mail SSL configuration for all domains
self._update_all_mail_ssl_configs()
commands = [
'postmap -F hash:/etc/postfix/vmail_ssl.map',
'systemctl restart postfix',
@@ -93,6 +96,22 @@ class Renew:
except Exception as e:
logging.writeToFile(f'Error restarting services: {str(e)}', 1)
def _update_all_mail_ssl_configs(self) -> None:
"""Update mail SSL configuration for all domains after renewal"""
try:
logging.writeToFile('Updating mail SSL configurations for all domains.', 0)
# Update mail SSL config for all websites
for website in Websites.objects.filter(state=1):
virtualHostUtilities.updateMailSSLConfig(website.domain)
# Update mail SSL config for all child domains
for child in ChildDomains.objects.all():
virtualHostUtilities.updateMailSSLConfig(child.domain)
except Exception as e:
logging.writeToFile(f'Error updating mail SSL configs: {str(e)}', 1)
def SSLObtainer(self):
try:
logging.writeToFile('Running SSL Renew Utility')

View File

@@ -9,6 +9,7 @@ import re
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
from plogical.errorSanitizer import ErrorSanitizer
import shlex
import subprocess
import shutil
@@ -567,8 +568,9 @@ class Upgrade:
writeToFile.writelines(varTmp)
writeToFile.close()
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [mountTemp]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'mountTemp')
Upgrade.stdOut("Failed to mount temporary filesystem [mountTemp]", 0)
@staticmethod
def dockerUsers():
@@ -738,8 +740,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
os.chdir(cwd)
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [download_install_phpmyadmin]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'download_install_phpmyadmin')
Upgrade.stdOut("Failed to download and install phpMyAdmin [download_install_phpmyadmin]", 0)
@staticmethod
def setupComposer():
@@ -1028,8 +1031,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
Upgrade.stdOut("SnappyMail installation completed.", 0)
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [downoad_and_install_raindloop]", 0)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'downoad_and_install_raindloop')
Upgrade.stdOut("Failed to download and install Rainloop [downoad_and_install_raindloop]", 0)
return 1
@@ -1049,8 +1053,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
pass
return (version_number + "." + version_build + ".tar.gz")
except BaseException as msg:
Upgrade.stdOut(str(msg) + ' [downloadLink]')
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'downloadLink')
Upgrade.stdOut("Failed to download required files [downloadLink]")
os._exit(0)
@staticmethod
@@ -1063,8 +1068,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
command = "chmod +x /usr/local/CyberCP/cli/cyberPanel.py"
Upgrade.executioner(command, 'CLI Permissions', 0)
except OSError as msg:
Upgrade.stdOut(str(msg) + " [setupCLI]")
except OSError as e:
ErrorSanitizer.log_error_securely(e, 'setupCLI')
Upgrade.stdOut("Failed to setup CLI [setupCLI]")
return 0
@staticmethod
@@ -1136,8 +1142,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
cursor = conn.cursor()
return conn, cursor
except BaseException as msg:
Upgrade.stdOut(str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'database_connection')
Upgrade.stdOut("Failed to establish database connection")
return 0, 0
@staticmethod
@@ -1381,8 +1388,8 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
try:
cursor.execute("UPDATE loginSystem_acl SET config = '%s' where name = 'admin'" % (Upgrade.AdminACL))
except BaseException as msg:
print(str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'applyLoginSystemMigrations')
try:
import sleep
except:
@@ -2197,6 +2204,22 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
except:
pass
# Add new fields for network configuration and extra options
try:
cursor.execute('ALTER TABLE dockerManager_containers ADD network VARCHAR(100) DEFAULT "bridge"')
except:
pass
try:
cursor.execute('ALTER TABLE dockerManager_containers ADD network_mode VARCHAR(50) DEFAULT "bridge"')
except:
pass
try:
cursor.execute('ALTER TABLE dockerManager_containers ADD extra_options LONGTEXT DEFAULT "{}"')
except:
pass
try:
connection.close()
except:
@@ -2983,8 +3006,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
return 1, None
except BaseException as msg:
return 0, str(msg)
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installLSCPD')
return 0, "Failed to install LSCPD"
@staticmethod
def installLSCPD(branch):
@@ -3074,8 +3098,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
Upgrade.stdOut("LSCPD successfully installed!")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [installLSCPD]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installLSCPD')
Upgrade.stdOut("Failed to install LSCPD [installLSCPD]")
### disable dkim signing in rspamd in ref to https://github.com/usmannasir/cyberpanel/issues/1176
@staticmethod
@@ -3363,8 +3388,9 @@ echo $oConfig->Save() ? 'Done' : 'Error';
Upgrade.stdOut("Permissions updated.")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [fixPermissions]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'fixPermissions')
Upgrade.stdOut("Failed to fix permissions [fixPermissions]")
@staticmethod
def AutoUpgradeAcme():
@@ -3807,8 +3833,9 @@ echo $oConfig->Save() ? 'Done' : 'Error';
Upgrade.stdOut("Dovecot upgraded.")
except BaseException as msg:
Upgrade.stdOut(str(msg) + " [upgradeDovecot]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'upgradeDovecot')
Upgrade.stdOut("Failed to upgrade Dovecot [upgradeDovecot]")
@staticmethod
def installRestic():
@@ -4052,317 +4079,181 @@ vmail
@staticmethod
def CreateMissingPoolsforFPM():
##### apache configs
"""
Create missing PHP-FPM pool configurations for all PHP versions.
This function ensures all PHP versions have proper pool configurations
to prevent ImunifyAV/Imunify360 installation failures.
"""
try:
# Detect OS and set paths
CentOSPath = '/etc/redhat-release'
if os.path.exists(CentOSPath):
# CentOS/RHEL/CloudLinux paths
serverRootPath = '/etc/httpd'
configBasePath = '/etc/httpd/conf.d/'
sockPath = '/var/run/php-fpm/'
runAsUser = 'apache'
group = 'nobody'
# Define PHP pool paths for CentOS
php_paths = {
'5.4': '/opt/remi/php54/root/etc/php-fpm.d/',
'5.5': '/opt/remi/php55/root/etc/php-fpm.d/',
'5.6': '/etc/opt/remi/php56/php-fpm.d/',
'7.0': '/etc/opt/remi/php70/php-fpm.d/',
'7.1': '/etc/opt/remi/php71/php-fpm.d/',
'7.2': '/etc/opt/remi/php72/php-fpm.d/',
'7.3': '/etc/opt/remi/php73/php-fpm.d/',
'7.4': '/etc/opt/remi/php74/php-fpm.d/',
'8.0': '/etc/opt/remi/php80/php-fpm.d/',
'8.1': '/etc/opt/remi/php81/php-fpm.d/',
'8.2': '/etc/opt/remi/php82/php-fpm.d/',
'8.3': '/etc/opt/remi/php83/php-fpm.d/',
'8.4': '/etc/opt/remi/php84/php-fpm.d/',
'8.5': '/etc/opt/remi/php85/php-fpm.d/'
}
else:
# Ubuntu/Debian paths
serverRootPath = '/etc/apache2'
configBasePath = '/etc/apache2/sites-enabled/'
sockPath = '/var/run/php/'
runAsUser = 'www-data'
group = 'nogroup'
# Define PHP pool paths for Ubuntu
php_paths = {
'5.4': '/etc/php/5.4/fpm/pool.d/',
'5.5': '/etc/php/5.5/fpm/pool.d/',
'5.6': '/etc/php/5.6/fpm/pool.d/',
'7.0': '/etc/php/7.0/fpm/pool.d/',
'7.1': '/etc/php/7.1/fpm/pool.d/',
'7.2': '/etc/php/7.2/fpm/pool.d/',
'7.3': '/etc/php/7.3/fpm/pool.d/',
'7.4': '/etc/php/7.4/fpm/pool.d/',
'8.0': '/etc/php/8.0/fpm/pool.d/',
'8.1': '/etc/php/8.1/fpm/pool.d/',
'8.2': '/etc/php/8.2/fpm/pool.d/',
'8.3': '/etc/php/8.3/fpm/pool.d/',
'8.4': '/etc/php/8.4/fpm/pool.d/',
'8.5': '/etc/php/8.5/fpm/pool.d/'
}
CentOSPath = '/etc/redhat-release'
# Check if server root exists
if not os.path.exists(serverRootPath):
logging.CyberCPLogFileWriter.writeToFile(f'Server root path not found: {serverRootPath}')
return 1
if os.path.exists(CentOSPath):
# Create pool configurations for all PHP versions
for version, pool_path in php_paths.items():
if os.path.exists(pool_path):
www_conf = os.path.join(pool_path, 'www.conf')
# Skip if www.conf already exists
if os.path.exists(www_conf):
logging.CyberCPLogFileWriter.writeToFile(f'PHP {version} pool config already exists: {www_conf}')
continue
# Create the pool configuration
pool_name = f'php{version.replace(".", "")}default'
sock_name = f'php{version}-fpm.sock'
content = f'''[{pool_name}]
user = {runAsUser}
group = {runAsUser}
listen = {sockPath}{sock_name}
listen.owner = {runAsUser}
listen.group = {group}
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 1000
pm.status_path = /status
ping.path = /ping
ping.response = pong
request_terminate_timeout = 300
request_slowlog_timeout = 10
slowlog = /var/log/php{version}-fpm-slow.log
'''
try:
# Write the configuration file
with open(www_conf, 'w') as f:
f.write(content)
# Set proper permissions
os.chown(www_conf, 0, 0) # root:root
os.chmod(www_conf, 0o644)
logging.CyberCPLogFileWriter.writeToFile(f'Created PHP {version} pool config: {www_conf}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error creating PHP {version} pool config: {str(e)}')
else:
logging.CyberCPLogFileWriter.writeToFile(f'PHP {version} pool directory not found: {pool_path}')
serverRootPath = '/etc/httpd'
configBasePath = '/etc/httpd/conf.d/'
php54Path = '/opt/remi/php54/root/etc/php-fpm.d/'
php55Path = '/opt/remi/php55/root/etc/php-fpm.d/'
php56Path = '/etc/opt/remi/php56/php-fpm.d/'
php70Path = '/etc/opt/remi/php70/php-fpm.d/'
php71Path = '/etc/opt/remi/php71/php-fpm.d/'
php72Path = '/etc/opt/remi/php72/php-fpm.d/'
php73Path = '/etc/opt/remi/php73/php-fpm.d/'
php74Path = '/etc/opt/remi/php74/php-fpm.d/'
php80Path = '/etc/opt/remi/php80/php-fpm.d/'
php81Path = '/etc/opt/remi/php81/php-fpm.d/'
php82Path = '/etc/opt/remi/php82/php-fpm.d/'
php83Path = '/etc/opt/remi/php83/php-fpm.d/'
php84Path = '/etc/opt/remi/php84/php-fpm.d/'
php85Path = '/etc/opt/remi/php85/php-fpm.d/'
serviceName = 'httpd'
sockPath = '/var/run/php-fpm/'
runAsUser = 'apache'
else:
serverRootPath = '/etc/apache2'
configBasePath = '/etc/apache2/sites-enabled/'
php54Path = '/etc/php/5.4/fpm/pool.d/'
php55Path = '/etc/php/5.5/fpm/pool.d/'
php56Path = '/etc/php/5.6/fpm/pool.d/'
php70Path = '/etc/php/7.0/fpm/pool.d/'
php71Path = '/etc/php/7.1/fpm/pool.d/'
php72Path = '/etc/php/7.2/fpm/pool.d/'
php73Path = '/etc/php/7.3/fpm/pool.d/'
php74Path = '/etc/php/7.4/fpm/pool.d/'
php80Path = '/etc/php/8.0/fpm/pool.d/'
php81Path = '/etc/php/8.1/fpm/pool.d/'
php82Path = '/etc/php/8.2/fpm/pool.d/'
php83Path = '/etc/php/8.3/fpm/pool.d/'
php84Path = '/etc/php/8.4/fpm/pool.d/'
php85Path = '/etc/php/8.5/fpm/pool.d/'
serviceName = 'apache2'
sockPath = '/var/run/php/'
runAsUser = 'www-data'
#####
if not os.path.exists(serverRootPath):
# Restart PHP-FPM services to apply configurations
Upgrade.restartPHPFPMServices()
return 0
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error in CreateMissingPoolsforFPM: {str(e)}')
return 1
if os.path.exists(php54Path):
content = f"""
[php54default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php5.4-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
"""
WriteToFile = open(f'{php54Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php55Path):
content = f'''
[php55default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php5.5-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php55Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php56Path):
content = f'''
[php56default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php5.6-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php56Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php70Path):
content = f'''
[php70default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php7.0-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php70Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php71Path):
content = f'''
[php71default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php7.1-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php71Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php72Path):
content = f'''
[php72default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php7.2-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php72Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php73Path):
content = f'''
[php73default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php7.3-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php73Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php74Path):
content = f'''
[php74default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php7.4-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php74Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php80Path):
content = f'''
[php80default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.0-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php80Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php81Path):
content = f'''
[php81default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.1-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php81Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php82Path):
content = f'''
[php82default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.2-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
@staticmethod
def restartPHPFPMServices():
"""
Restart all PHP-FPM services to apply new pool configurations.
This ensures that ImunifyAV/Imunify360 installation will work properly.
"""
try:
# Define all possible PHP versions
php_versions = ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
'''
WriteToFile = open(f'{php82Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php83Path):
content = f'''
[php83default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.3-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php83Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php84Path):
content = f'''
[php84default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.4-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php84Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
if os.path.exists(php85Path):
content = f'''
[php85default]
user = {runAsUser}
group = {runAsUser}
listen ={sockPath}php8.5-fpm.sock
listen.owner = {runAsUser}
listen.group = {runAsUser}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''
WriteToFile = open(f'{php85Path}www.conf', 'w')
WriteToFile.write(content)
WriteToFile.close()
restarted_count = 0
total_count = 0
for version in php_versions:
service_name = f'php{version}-fpm'
# Check if service exists
try:
result = subprocess.run(['systemctl', 'list-unit-files', service_name],
capture_output=True, text=True, timeout=10)
if result.returncode == 0 and service_name in result.stdout:
total_count += 1
# Restart the service
restart_result = subprocess.run(['systemctl', 'restart', service_name],
capture_output=True, text=True, timeout=30)
if restart_result.returncode == 0:
# Check if service is actually running
status_result = subprocess.run(['systemctl', 'is-active', service_name],
capture_output=True, text=True, timeout=10)
if status_result.returncode == 0 and 'active' in status_result.stdout:
restarted_count += 1
logging.CyberCPLogFileWriter.writeToFile(f'Successfully restarted {service_name}')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: {service_name} restarted but not active')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to restart {service_name}: {restart_result.stderr}')
except subprocess.TimeoutExpired:
logging.CyberCPLogFileWriter.writeToFile(f'Timeout restarting {service_name}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error restarting {service_name}: {str(e)}')
logging.CyberCPLogFileWriter.writeToFile(f'PHP-FPM restart summary: {restarted_count}/{total_count} services restarted successfully')
return restarted_count, total_count
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error in restartPHPFPMServices: {str(e)}')
return 0, 0
@staticmethod
def setupPHPSymlink():
@@ -4406,8 +4297,9 @@ pm.max_spare_servers = 3
Upgrade.stdOut(f"PHP symlink updated to PHP {selected_php} successfully.")
except BaseException as msg:
Upgrade.stdOut('[ERROR] ' + str(msg) + " [setupPHPSymlink]")
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'setupPHPSymlink')
Upgrade.stdOut('[ERROR] Failed to setup PHP symlink [setupPHPSymlink]')
return 0
return 1
@@ -5219,8 +5111,9 @@ extprocessor proxyApacheBackendSSL {
return 1
except BaseException as msg:
print("[ERROR] installQuota. " + str(msg))
except Exception as e:
ErrorSanitizer.log_error_securely(e, 'installQuota')
print("[ERROR] installQuota. Failed to install quota")
return 0
@staticmethod

View File

@@ -196,9 +196,15 @@ class vhost:
command = 'mkdir -p /usr/local/lsws/Example/html/.well-known/acme-challenge'
ProcessUtilities.normalExecutioner(command)
path = "/home/" + virtualHostName
pathHTML = "/home/" + virtualHostName + "/public_html"
pathLogs = "/home/" + virtualHostName + "/logs"
# Get user's home directory dynamically
from userManagment.homeDirectoryUtils import HomeDirectoryUtils
home_path = HomeDirectoryUtils.getUserHomeDirectory(virtualHostUser)
if not home_path:
home_path = "/home" # Fallback to default
path = os.path.join(home_path, virtualHostName)
pathHTML = os.path.join(home_path, virtualHostName, "public_html")
pathLogs = os.path.join(home_path, virtualHostName, "logs")
confPath = vhost.Server_root + "/conf/vhosts/"+virtualHostName
completePathToConfigFile = confPath +"/vhost.conf"

View File

@@ -796,6 +796,12 @@ local_name %s {
print("0," + parsed_error)
return 0, parsed_error
# Update vhost SSL configuration with new certificate paths
virtualHostUtilities.updateVhostSSLConfig(virtualHost)
# Update mail SSL configuration for this domain
virtualHostUtilities.updateMailSSLConfig(virtualHost)
installUtilities.installUtilities.reStartLiteSpeed()
command = 'systemctl restart postfix'
@@ -891,8 +897,50 @@ local_name %s {
print("0, %s file is symlinked." % (fileName))
return 0
numberOfTotalLines = int(
ProcessUtilities.outputExecutioner('wc -l %s' % (fileName), externalApp).split(" ")[0])
# Improved wc -l parsing with better error handling
wc_output = ProcessUtilities.outputExecutioner('wc -l %s' % (fileName), externalApp)
# Handle different wc output formats and potential errors
if wc_output and wc_output.strip():
# Split by whitespace and take the first part that looks like a number
wc_parts = wc_output.strip().split()
numberOfTotalLines = 0
for part in wc_parts:
try:
numberOfTotalLines = int(part)
break
except ValueError:
continue
# If no valid number found, try to extract from common wc error formats
if numberOfTotalLines == 0:
# Handle cases like "wc: filename: No such file or directory"
if "No such file or directory" in wc_output:
print("1,None")
return "1,None"
# Handle cases where wc returns just "wc:" or similar
if "wc:" in wc_output:
# Try to get line count using alternative method
try:
alt_output = ProcessUtilities.outputExecutioner('cat %s | wc -l' % (fileName), externalApp)
if alt_output and alt_output.strip():
alt_parts = alt_output.strip().split()
for part in alt_parts:
try:
numberOfTotalLines = int(part)
break
except ValueError:
continue
except:
pass
if numberOfTotalLines == 0:
print("1,None")
return "1,None"
else:
print("1,None")
return "1,None"
if numberOfTotalLines < 25:
data = ProcessUtilities.outputExecutioner('cat %s' % (fileName), externalApp)
@@ -1079,6 +1127,84 @@ local_name %s {
print("0," + str(msg))
return 0, str(msg)
@staticmethod
def updateVhostSSLConfig(virtualHost):
"""Update vhost SSL configuration with new certificate paths"""
try:
logging.CyberCPLogFileWriter.writeToFile(f"Updating vhost SSL configuration for {virtualHost}")
# Update vhost configuration file
vhostConfPath = f'/usr/local/lsws/conf/vhosts/{virtualHost}/vhost.conf'
if os.path.exists(vhostConfPath):
with open(vhostConfPath, 'r') as f:
content = f.read()
# Update SSL certificate paths in vhost configuration
new_ssl_config = f"""vhssl {{
keyFile /etc/letsencrypt/live/{virtualHost}/privkey.pem
certFile /etc/letsencrypt/live/{virtualHost}/fullchain.pem
certChain 1
sslProtocol 24
enableECDHE 1
renegProtection 1
sslSessionCache 1
enableSpdy 15
enableStapling 1
ocspRespMaxAge 86400
}}"""
# Replace existing vhssl block
import re
pattern = r'vhssl\s*\{[^}]*\}'
if re.search(pattern, content, re.DOTALL):
content = re.sub(pattern, new_ssl_config, content, flags=re.DOTALL)
else:
# Add vhssl block if it doesn't exist
content += f"\n{new_ssl_config}\n"
with open(vhostConfPath, 'w') as f:
f.write(content)
logging.CyberCPLogFileWriter.writeToFile(f"Updated vhost SSL configuration for {virtualHost}")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error updating vhost SSL config for {virtualHost}: {str(e)}")
@staticmethod
def updateMailSSLConfig(virtualHost):
"""Update mail SSL configuration with new certificate paths"""
try:
logging.CyberCPLogFileWriter.writeToFile(f"Updating mail SSL configuration for {virtualHost}")
# Update vmail_ssl.map file
postfixMapFile = '/etc/postfix/vmail_ssl.map'
if os.path.exists(postfixMapFile):
with open(postfixMapFile, 'r') as f:
content = f.read()
# Remove old entries for this domain
lines = content.split('\n')
new_lines = []
for line in lines:
if not line.startswith(f'{virtualHost} ') and not line.startswith(f'mail.{virtualHost} '):
new_lines.append(line)
# Add new entries
new_lines.append(f'{virtualHost} /etc/letsencrypt/live/{virtualHost}/privkey.pem /etc/letsencrypt/live/{virtualHost}/fullchain.pem')
new_lines.append(f'mail.{virtualHost} /etc/letsencrypt/live/{virtualHost}/privkey.pem /etc/letsencrypt/live/{virtualHost}/fullchain.pem')
with open(postfixMapFile, 'w') as f:
f.write('\n'.join(new_lines))
# Update postfix map database
command = 'postmap -F hash:/etc/postfix/vmail_ssl.map'
ProcessUtilities.executioner(command)
logging.CyberCPLogFileWriter.writeToFile(f"Updated mail SSL configuration for {virtualHost}")
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error updating mail SSL config for {virtualHost}: {str(e)}")
@staticmethod
def issueSSLForMailServer(virtualHost, path):
try: