diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 5433f22ce..e8798ff3f 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -825,6 +825,191 @@ top: 130px; } + /* .htaccess Feature Banner */ + .htaccess-feature-banner { + position: fixed; + top: 80px; + left: 260px; + right: 0; + background: linear-gradient(135deg, #10b981 0%, #059669 50%, #047857 100%); + border-bottom: 2px solid #065f46; + padding: 18px 30px; + z-index: 997; + box-shadow: 0 6px 25px rgba(16, 185, 129, 0.3); + animation: slideDown 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); + display: none; + } + + .htaccess-feature-banner.show { + display: block; + } + + .htaccess-content { + display: flex; + align-items: center; + gap: 1.5rem; + max-width: 1400px; + margin: 0 auto; + } + + .htaccess-icon { + background: rgba(255, 255, 255, 0.25); + border-radius: 14px; + padding: 14px; + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.4); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + } + + .htaccess-icon i { + color: white; + font-size: 1.75rem; + display: block; + } + + .htaccess-text { + flex: 1; + display: flex; + flex-direction: column; + gap: 5px; + } + + .htaccess-main-text { + color: white; + font-size: 1.1rem; + font-weight: 700; + line-height: 1.4; + letter-spacing: 0.3px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .htaccess-sub-text { + color: rgba(255, 255, 255, 0.9); + font-size: 0.875rem; + font-weight: 500; + display: flex; + align-items: center; + gap: 1rem; + } + + .htaccess-sub-text span { + display: flex; + align-items: center; + gap: 0.4rem; + } + + .htaccess-sub-text i { + font-size: 0.75rem; + } + + .htaccess-actions { + display: flex; + gap: 12px; + } + + .htaccess-btn { + background: white; + color: #047857; + padding: 14px 28px; + border-radius: 10px; + text-decoration: none; + font-weight: 700; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 10px; + transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); + position: relative; + overflow: hidden; + border: 2px solid transparent; + } + + .htaccess-btn:hover { + background: #f0fdf4; + color: #065f46; + transform: translateY(-2px) scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2); + text-decoration: none; + border-color: white; + } + + .htaccess-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(16, 185, 129, 0.1); + transform: translate(-50%, -50%); + transition: width 0.6s ease, height 0.6s ease; + } + + .htaccess-btn:hover::before { + width: 300px; + height: 300px; + } + + .htaccess-btn span { + position: relative; + z-index: 1; + } + + .htaccess-btn i { + font-size: 1rem; + position: relative; + z-index: 1; + } + + .htaccess-close { + background: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + font-size: 1.1rem; + cursor: pointer; + padding: 10px; + border-radius: 8px; + transition: all 0.3s ease; + backdrop-filter: blur(12px); + } + + .htaccess-close:hover { + background: rgba(255, 255, 255, 0.25); + transform: scale(1.1) rotate(90deg); + } + + /* Adjust main content when .htaccess banner is shown */ + .htaccess-shown #main-content { + padding-top: 190px; + } + + /* Multiple banners adjustments */ + .notification-shown.htaccess-shown #main-content { + padding-top: 240px; + } + + .ai-scanner-shown.htaccess-shown #main-content { + padding-top: 270px; + } + + .notification-shown.ai-scanner-shown.htaccess-shown #main-content { + padding-top: 320px; + } + + .notification-shown .htaccess-feature-banner { + top: 130px; + } + + .ai-scanner-shown .htaccess-feature-banner { + top: 160px; + } + + .notification-shown.ai-scanner-shown .htaccess-feature-banner { + top: 210px; + } + /* Mobile responsive styles for AI Scanner banner */ @media (max-width: 768px) { .ai-scanner-banner { @@ -1832,6 +2017,32 @@ + +
+
+
+ +
+
+ ✨ Revolutionary .htaccess Support Now Live! + + Full .htaccess support + PHP configuration now works + Zero rule rewrites needed + +
+ + +
+
+
{% block content %}{% endblock %} @@ -2053,11 +2264,49 @@ }); } - // Check both notification statuses when page loads + // .htaccess Feature Notification Functions + function checkHtaccessStatus() { + // Check if user has dismissed the notification permanently (localStorage for longer persistence) + if (localStorage.getItem('htaccessNotificationDismissed') === 'true') { + return; + } + + // Check if notification has been shown today + const lastShown = localStorage.getItem('htaccessNotificationLastShown'); + const today = new Date().toDateString(); + + if (lastShown === today) { + return; + } + + // Show the notification + showHtaccessNotification(); + localStorage.setItem('htaccessNotificationLastShown', today); + } + + function showHtaccessNotification() { + const banner = document.getElementById('htaccess-notification'); + const body = document.body; + banner.classList.add('show'); + body.classList.add('htaccess-shown'); + } + + function dismissHtaccessNotification() { + const banner = document.getElementById('htaccess-notification'); + const body = document.body; + banner.classList.remove('show'); + body.classList.remove('htaccess-shown'); + // Remember dismissal permanently + localStorage.setItem('htaccessNotificationDismissed', 'true'); + } + + // Check all notification statuses when page loads document.addEventListener('DOMContentLoaded', function() { checkBackupStatus(); // Show AI Scanner notification with a slight delay for better UX setTimeout(checkAIScannerStatus, 1000); + // Show .htaccess notification with additional delay for staggered effect + setTimeout(checkHtaccessStatus, 1500); // Set active menu state based on current URL setActiveMenuState(); diff --git a/cyberpanel-mods/security/ModSecurity-Fix-README.md b/cyberpanel-mods/security/ModSecurity-Fix-README.md deleted file mode 100644 index cc5b9c5ae..000000000 --- a/cyberpanel-mods/security/ModSecurity-Fix-README.md +++ /dev/null @@ -1,185 +0,0 @@ -# CyberPanel ModSecurity Rules Fix - -## Overview - -This fix addresses common issues with ModSecurity Rules Packages in CyberPanel where OWASP ModSecurity Core Rules show as "off" even after installation. The problem typically occurs due to: - -1. **Incorrect status detection logic** - The system doesn't properly detect installed OWASP rules -2. **Outdated download URLs** - The OWASP rules download URL was incorrect -3. **JavaScript state synchronization issues** - Frontend toggle state doesn't sync with backend -4. **Missing error handling** - Insufficient logging and error reporting - -## Issues Fixed - -### 1. Status Detection Logic (`firewallManager.py`) -- **Problem**: The `getOWASPAndComodoStatus` method only checked for `modsec/owasp` in configuration files -- **Fix**: Added multiple detection methods: - - Check for `modsec/owasp` in configuration - - Check for `owasp-modsecurity-crs` in configuration - - Verify actual file existence in filesystem - - Added similar verification for Comodo rules - -### 2. OWASP Rules Download (`modSec.py`) -- **Problem**: Used incorrect GitHub URL that resulted in 404 errors -- **Fix**: Updated to use correct GitHub repository URL: - - Old: `https://github.com/coreruleset/coreruleset/archive/v3.3.2/master.zip` - - New: `https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0.zip` - -### 3. JavaScript State Synchronization (`firewall.js`) -- **Problem**: Toggle state variables weren't properly updated when status was fetched -- **Fix**: Added proper state variable updates (`owaspInstalled`, `comodoInstalled`) in both update scenarios - -### 4. Error Handling and Logging (`modSec.py`) -- **Problem**: Insufficient logging made debugging difficult -- **Fix**: Added comprehensive logging throughout the installation process: - - Download progress logging - - Extraction progress logging - - File verification logging - - Installation verification - -## Files Modified - -1. **`cyberpanel/firewall/firewallManager.py`** - - Enhanced `getOWASPAndComodoStatus` method - - Added filesystem verification for rule packages - -2. **`cyberpanel/plogical/modSec.py`** - - Updated OWASP download URL to v4.0.0 - - Added comprehensive logging - - Added installation verification - - Improved error handling - - Updated to use simplified CRS v4.0.0 structure - -3. **`cyberpanel/firewall/static/firewall/firewall.js`** - - Fixed JavaScript state synchronization - - Added proper variable updates - -## Manual Fix Script - -A comprehensive fix script is provided at `cyberpanel/cyberpanel-mods/security/modsecurity-fix.sh` that: - -1. **Backs up** current configuration -2. **Downloads and installs** OWASP ModSecurity Core Rules v3.3.4 -3. **Creates proper configuration files** -4. **Sets correct permissions** -5. **Updates LiteSpeed configuration** -6. **Restarts LiteSpeed** -7. **Verifies installation** - -### Running the Fix Script - -```bash -# Make the script executable -chmod +x cyberpanel/cyberpanel-mods/security/modsecurity-fix.sh - -# Run the fix script -./cyberpanel/cyberpanel-mods/security/modsecurity-fix.sh -``` - -## Manual Installation Steps - -If you prefer to fix the issue manually: - -### 1. Download OWASP Rules -```bash -cd /tmp -wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0.zip -O owasp.zip -unzip owasp.zip -d /usr/local/lsws/conf/modsec/ -mv /usr/local/lsws/conf/modsec/coreruleset-4.0.0 /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.0.0 -``` - -### 2. Set Up Configuration Files -```bash -cd /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.0.0 -cp crs-setup.conf.example crs-setup.conf -cp rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf -cp rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf -``` - -### 3. Create Master Configuration -Create `/usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.0.0/owasp-master.conf`: - -```apache -include /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.0.0/crs.conf -``` - -**Note**: CRS v4.0.0 uses a simplified structure with a single `crs.conf` file that includes all necessary rules, unlike v3.x which required individual rule file includes. - -### Key Differences in CRS v4.0.0: -- **Simplified Configuration**: Single `crs.conf` file instead of multiple individual rule files -- **Plugin System**: Replaced application exclusion packages with a plugin system -- **Improved Performance**: Better rule organization and execution -- **Enhanced Security**: Updated attack patterns and detection methods -- **Better Documentation**: Improved configuration examples and guides - -### 4. Update LiteSpeed Configuration -Add to `/usr/local/lsws/conf/httpd_config.conf`: - -```apache -modsecurity_rules_file /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-4.0.0/owasp-master.conf -``` - -### 5. Set Permissions and Restart -```bash -chown -R lsadm:lsadm /usr/local/lsws/conf/modsec -chmod -R 755 /usr/local/lsws/conf/modsec -systemctl restart lsws -``` - -## Verification - -After applying the fix: - -1. **Access CyberPanel** → Security → ModSecurity Rules Packages -2. **Check Status**: OWASP ModSecurity Core Rules should show as "enabled" -3. **Test Toggle**: The toggle should work properly (enable/disable) -4. **Check Logs**: Verify no errors in ModSecurity logs - -## Troubleshooting - -### Common Issues - -1. **Rules still show as disabled** - - Check file permissions: `ls -la /usr/local/lsws/conf/modsec/owasp-modsecurity-crs-3.0-master/` - - Verify configuration: `grep -i owasp /usr/local/lsws/conf/httpd_config.conf` - - Check LiteSpeed logs: `tail -f /usr/local/lsws/logs/error.log` - -2. **Download fails** - - Check internet connectivity - - Verify GitHub access: `curl -I https://github.com/coreruleset/coreruleset/archive/refs/tags/v3.3.4.zip` - - Try manual download and extraction - -3. **LiteSpeed won't start** - - Check configuration syntax: `/usr/local/lsws/bin/lshttpd -t` - - Restore backup: `cp /usr/local/lsws/conf/httpd_config.conf.backup.* /usr/local/lsws/conf/httpd_config.conf` - - Check ModSecurity syntax - -### Log Files - -- **ModSecurity Log**: `/usr/local/lsws/logs/modsec.log` -- **Audit Log**: `/usr/local/lsws/logs/auditmodsec.log` -- **Installation Log**: `/home/cyberpanel/modSecInstallLog` -- **LiteSpeed Error Log**: `/usr/local/lsws/logs/error.log` - -## Security Considerations - -1. **Rule Updates**: Regularly update OWASP rules for latest security patterns -2. **False Positives**: Monitor logs for legitimate traffic being blocked -3. **Performance**: OWASP rules can impact performance - monitor server resources -4. **Custom Rules**: Add custom rules in `/usr/local/lsws/conf/modsec/rules.conf` - -## Support - -If you encounter issues after applying this fix: - -1. Check the troubleshooting section above -2. Review log files for specific error messages -3. Verify all file permissions and ownership -4. Test with a simple configuration first - -## Changelog - -- **v1.0**: Initial fix for ModSecurity status detection issues -- **v1.1**: Added comprehensive logging and error handling -- **v1.2**: Updated to OWASP CRS v4.0.0 and improved verification -- **v1.3**: Simplified configuration structure for CRS v4.0.0 compatibility diff --git a/filemanager/filemanager.py b/filemanager/filemanager.py index 14f89fe54..3af88798f 100644 --- a/filemanager/filemanager.py +++ b/filemanager/filemanager.py @@ -1043,6 +1043,13 @@ class FileManager: if self.data['extractionType'] == 'zip': command = 'unzip -o ' + self.returnPathEnclosed( self.data['fileToExtract']) + ' -d ' + self.returnPathEnclosed(self.data['extractionLocation']) + elif self.data['extractionType'] == '7z': + command = '7z x ' + self.returnPathEnclosed( + self.data['fileToExtract']) + ' -o' + self.returnPathEnclosed(self.data['extractionLocation']) + ' -y' + elif self.data['extractionType'] == 'rar': + # Try unrar first (free), fallback to 7z if unrar not available + command = 'unrar x ' + self.returnPathEnclosed( + self.data['fileToExtract']) + ' ' + self.returnPathEnclosed(self.data['extractionLocation']) + ' -y' else: command = 'tar -xf ' + self.returnPathEnclosed( self.data['fileToExtract']) + ' -C ' + self.returnPathEnclosed(self.data['extractionLocation']) @@ -1068,6 +1075,13 @@ class FileManager: if self.data['extractionType'] == 'zip': command = 'unzip -o ' + self.returnPathEnclosed( self.data['fileToExtract']) + ' -d ' + self.returnPathEnclosed(self.data['extractionLocation']) + elif self.data['extractionType'] == '7z': + command = '7z x ' + self.returnPathEnclosed( + self.data['fileToExtract']) + ' -o' + self.returnPathEnclosed(self.data['extractionLocation']) + ' -y' + elif self.data['extractionType'] == 'rar': + # Try unrar first (free), fallback to 7z if unrar not available + command = 'unrar x ' + self.returnPathEnclosed( + self.data['fileToExtract']) + ' ' + self.returnPathEnclosed(self.data['extractionLocation']) + ' -y' else: command = 'tar -xf ' + self.returnPathEnclosed( self.data['fileToExtract']) + ' -C ' + self.returnPathEnclosed(self.data['extractionLocation']) @@ -1098,6 +1112,15 @@ class FileManager: compressedFileName = self.returnPathEnclosed( self.data['basePath'] + '/' + self.data['compressedFileName'] + '.zip') command = 'zip -r ' + compressedFileName + ' ' + elif self.data['compressionType'] == '7z': + compressedFileName = self.returnPathEnclosed( + self.data['basePath'] + '/' + self.data['compressedFileName'] + '.7z') + command = '7z a -t7z ' + compressedFileName + ' ' + elif self.data['compressionType'] == 'rar': + compressedFileName = self.returnPathEnclosed( + self.data['basePath'] + '/' + self.data['compressedFileName'] + '.rar') + # Use 7z to create RAR format (7z can create RAR archives) + command = '7z a -trar ' + compressedFileName + ' ' else: compressedFileName = self.returnPathEnclosed( self.data['basePath'] + '/' + self.data['compressedFileName'] + '.tar.gz') @@ -1127,6 +1150,15 @@ class FileManager: compressedFileName = self.returnPathEnclosed( self.data['basePath'] + '/' + self.data['compressedFileName'] + '.zip') command = 'zip -r ' + compressedFileName + ' ' + elif self.data['compressionType'] == '7z': + compressedFileName = self.returnPathEnclosed( + self.data['basePath'] + '/' + self.data['compressedFileName'] + '.7z') + command = '7z a -t7z ' + compressedFileName + ' ' + elif self.data['compressionType'] == 'rar': + compressedFileName = self.returnPathEnclosed( + self.data['basePath'] + '/' + self.data['compressedFileName'] + '.rar') + # Use 7z to create RAR format (7z can create RAR archives) + command = '7z a -trar ' + compressedFileName + ' ' else: compressedFileName = self.returnPathEnclosed( self.data['basePath'] + '/' + self.data['compressedFileName'] + '.tar.gz') diff --git a/filemanager/static/filemanager/js/fileManager.js b/filemanager/static/filemanager/js/fileManager.js index 4766d96e4..7ea1bc575 100644 --- a/filemanager/static/filemanager/js/fileManager.js +++ b/filemanager/static/filemanager/js/fileManager.js @@ -1251,9 +1251,14 @@ fileManager.controller('fileManagerCtrl', function ($scope, $http, FileUploader, var completeFileToExtract = pathbase + "/" + allFilesAndFolders[0]; var extractionType = ""; + var fileExt = findFileExtension(completeFileToExtract); - if (findFileExtension(completeFileToExtract) == "gz") { + if (fileExt == "gz") { extractionType = "tar.gz"; + } else if (fileExt == "7z") { + extractionType = "7z"; + } else if (fileExt == "rar") { + extractionType = "rar"; } else { extractionType = "zip"; } diff --git a/filemanager/static/filemanager/js/newFileManager.js b/filemanager/static/filemanager/js/newFileManager.js index c757efb6a..bfa8bed5b 100644 --- a/filemanager/static/filemanager/js/newFileManager.js +++ b/filemanager/static/filemanager/js/newFileManager.js @@ -976,12 +976,25 @@ function findFileExtension(fileName) { var extractionLocation = $scope.extractionLocation; var fileToExtract = allFilesAndFolders[0]; + var extractionType = ""; + var fileExt = findFileExtension(fileToExtract); + + if (fileExt == "gz") { + extractionType = "tar.gz"; + } else if (fileExt == "7z") { + extractionType = "7z"; + } else if (fileExt == "rar") { + extractionType = "rar"; + } else { + extractionType = "zip"; + } var data = { method: "extract", domainRandomSeed: domainRandomSeed, domainName: domainName, fileToExtract: fileToExtract, + extractionType: extractionType, extractionLocation: extractionLocation, completeStartingPath: $scope.completeStartingPath }; diff --git a/filemanager/templates/filemanager/index.html b/filemanager/templates/filemanager/index.html index a175ba8f2..1cbd212a1 100644 --- a/filemanager/templates/filemanager/index.html +++ b/filemanager/templates/filemanager/index.html @@ -463,6 +463,8 @@
diff --git a/filemanager/templates/filemanager/indexIntegrated.html b/filemanager/templates/filemanager/indexIntegrated.html index 662f6bfb4..c1aba93c6 100644 --- a/filemanager/templates/filemanager/indexIntegrated.html +++ b/filemanager/templates/filemanager/indexIntegrated.html @@ -807,6 +807,8 @@ diff --git a/filemanager/templates/filemanager/modals.html b/filemanager/templates/filemanager/modals.html index ad8c91aad..4e15ae8d6 100644 --- a/filemanager/templates/filemanager/modals.html +++ b/filemanager/templates/filemanager/modals.html @@ -330,6 +330,8 @@ diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py index d3127b586..6a90fe4a4 100644 --- a/firewall/firewallManager.py +++ b/firewall/firewallManager.py @@ -1040,6 +1040,25 @@ class FirewallManager: elif items.find('modsec/owasp') > -1 or items.find('owasp-modsecurity-crs') > -1: owaspInstalled = 1 + if owaspInstalled == 1 and comodoInstalled == 1: + break + + # Also check rules.conf for manual OWASP installations (case-insensitive) + if owaspInstalled == 0: + rulesConfPath = os.path.join(virtualHostUtilities.Server_root, "conf/modsec/rules.conf") + if os.path.exists(rulesConfPath): + try: + command = "sudo cat " + rulesConfPath + rulesConfig = ProcessUtilities.outputExecutioner(command).splitlines() + for items in rulesConfig: + # Check for OWASP includes in rules.conf (case-insensitive) + if ('owasp' in items.lower() or 'crs-setup' in items.lower()) and \ + ('include' in items.lower() or 'modsecurity_rules_file' in items.lower()): + owaspInstalled = 1 + break + except: + pass + # Additional check: verify OWASP files actually exist if owaspInstalled == 0: owaspPath = os.path.join(virtualHostUtilities.Server_root, "conf/modsec/owasp-modsecurity-crs-4.18.0") diff --git a/install/install.py b/install/install.py index 94f73072e..b94cb671b 100644 --- a/install/install.py +++ b/install/install.py @@ -794,6 +794,274 @@ class preFlightsChecks: self.stdOut(f"Error applying OS-specific fixes: {str(e)}", 0) return False + def detectArchitecture(self): + """Detect system architecture - custom binaries only for x86_64""" + try: + import platform + arch = platform.machine() + return arch == "x86_64" + except Exception as msg: + self.stdOut(str(msg) + " [detectArchitecture]", 0) + return False + + def detectPlatform(self): + """Detect OS platform for binary selection (rhel8, rhel9, ubuntu)""" + try: + # Check for Ubuntu + if os.path.exists('/etc/lsb-release'): + with open('/etc/lsb-release', 'r') as f: + content = f.read() + if 'Ubuntu' in content or 'ubuntu' in content: + return 'ubuntu' + + # Check for RHEL-based distributions + if os.path.exists('/etc/os-release'): + with open('/etc/os-release', 'r') as f: + content = f.read().lower() + + # Check for version 8.x (RHEL, AlmaLinux, Rocky, CloudLinux, CentOS 8) + if 'version="8.' in content or 'version_id="8.' in content: + if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']): + return 'rhel8' + + # Check for version 9.x + if 'version="9.' in content or 'version_id="9.' in content: + if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']): + return 'rhel9' + + # Default to rhel9 if can't detect (safer default for newer systems) + self.stdOut("WARNING: Could not detect platform, defaulting to rhel9", 1) + return 'rhel9' + + except Exception as msg: + self.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel9", 0) + return 'rhel9' + + def downloadCustomBinary(self, url, destination, expected_sha256=None): + """Download custom binary file with optional checksum verification""" + try: + self.stdOut(f"Downloading {os.path.basename(destination)}...", 1) + + # Use wget for better progress display + command = f'wget -q --show-progress {url} -O {destination}' + res = self.call(command, self.distro, command, command, 1, 0, os.EX_OSERR) + + # Check if file was downloaded successfully by verifying it exists and has reasonable size + if os.path.exists(destination): + file_size = os.path.getsize(destination) + # Verify file size is reasonable (at least 10KB to avoid error pages/empty files) + if file_size > 10240: # 10KB + if file_size > 1048576: # 1MB + self.stdOut(f"Downloaded successfully ({file_size / (1024*1024):.2f} MB)", 1) + else: + self.stdOut(f"Downloaded successfully ({file_size / 1024:.2f} KB)", 1) + + # Verify checksum if provided + if expected_sha256: + self.stdOut("Verifying checksum...", 1) + import hashlib + sha256_hash = hashlib.sha256() + with open(destination, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + actual_sha256 = sha256_hash.hexdigest() + + if actual_sha256 == expected_sha256: + self.stdOut("Checksum verified successfully", 1) + return True + else: + self.stdOut(f"ERROR: Checksum mismatch!", 1) + self.stdOut(f"Expected: {expected_sha256}", 1) + self.stdOut(f"Got: {actual_sha256}", 1) + return False + else: + return True + else: + self.stdOut(f"ERROR: Downloaded file too small ({file_size} bytes)", 1) + return False + else: + self.stdOut("ERROR: Download failed - file not found", 1) + return False + + except Exception as msg: + self.stdOut(f"ERROR: {msg} [downloadCustomBinary]", 0) + return False + + def installCustomOLSBinaries(self): + """Install custom OpenLiteSpeed binaries with PHP config support""" + try: + self.stdOut("Installing Custom OpenLiteSpeed Binaries", 1) + self.stdOut("=" * 50, 1) + + # Check architecture + if not self.detectArchitecture(): + self.stdOut("WARNING: Custom binaries only available for x86_64", 1) + self.stdOut("Skipping custom binary installation", 1) + self.stdOut("Standard OLS will be used", 1) + return True # Not a failure, just skip + + # Detect platform + platform = self.detectPlatform() + self.stdOut(f"Detected platform: {platform}", 1) + + # Platform-specific URLs and checksums (OpenLiteSpeed v1.8.4.1 - v2.0.5 Static Build) + BINARY_CONFIGS = { + 'rhel8': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static', + 'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a', + 'module_url': None, # RHEL 8 doesn't have module (use RHEL 9 if needed) + 'module_sha256': None + }, + 'rhel9': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static', + 'sha256': '90468fb38767505185013024678d9144ae13100d2355097657f58719d98fbbc4', + 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_rhel.so', + 'module_sha256': '127227db81bcbebf80b225fc747b69cfcd4ad2f01cea486aa02d5c9ba6c18109' + }, + 'ubuntu': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static', + 'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb', + 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_ubuntu.so', + 'module_sha256': 'e7734f1e6226c2a0a8e00c1f6534ea9f577df9081b046736a774b1c52c28e7e5' + } + } + + config = BINARY_CONFIGS.get(platform) + if not config: + self.stdOut(f"ERROR: No binaries available for platform {platform}", 1) + self.stdOut("Skipping custom binary installation", 1) + return True # Not fatal + + OLS_BINARY_URL = config['url'] + OLS_BINARY_SHA256 = config['sha256'] + MODULE_URL = config['module_url'] + MODULE_SHA256 = config['module_sha256'] + OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" + MODULE_PATH = "/usr/local/lsws/modules/cyberpanel_ols.so" + + # Create backup + from datetime import datetime + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + backup_dir = f"/usr/local/lsws/backup-{timestamp}" + + try: + os.makedirs(backup_dir, exist_ok=True) + if os.path.exists(OLS_BINARY_PATH): + shutil.copy2(OLS_BINARY_PATH, f"{backup_dir}/openlitespeed.backup") + self.stdOut(f"Backup created at: {backup_dir}", 1) + except Exception as e: + self.stdOut(f"WARNING: Could not create backup: {e}", 1) + + # Download binaries to temp location + tmp_binary = "/tmp/openlitespeed-custom" + tmp_module = "/tmp/cyberpanel_ols.so" + + self.stdOut("Downloading custom binaries...", 1) + + # Download OpenLiteSpeed binary with checksum verification + if not self.downloadCustomBinary(OLS_BINARY_URL, tmp_binary, OLS_BINARY_SHA256): + self.stdOut("ERROR: Failed to download or verify OLS binary", 1) + self.stdOut("Continuing with standard OLS", 1) + return True # Not fatal, continue with standard OLS + + # Download module with checksum verification (if available) + module_downloaded = False + if MODULE_URL and MODULE_SHA256: + if not self.downloadCustomBinary(MODULE_URL, tmp_module, MODULE_SHA256): + self.stdOut("ERROR: Failed to download or verify module", 1) + self.stdOut("Continuing with standard OLS", 1) + return True # Not fatal, continue with standard OLS + module_downloaded = True + else: + self.stdOut("Note: No CyberPanel module for this platform", 1) + + # Install OpenLiteSpeed binary + self.stdOut("Installing custom binaries...", 1) + + try: + shutil.move(tmp_binary, OLS_BINARY_PATH) + os.chmod(OLS_BINARY_PATH, 0o755) + self.stdOut("Installed OpenLiteSpeed binary", 1) + except Exception as e: + self.stdOut(f"ERROR: Failed to install binary: {e}", 1) + return False + + # Install module (if downloaded) + if module_downloaded: + try: + os.makedirs(os.path.dirname(MODULE_PATH), exist_ok=True) + shutil.move(tmp_module, MODULE_PATH) + os.chmod(MODULE_PATH, 0o644) + self.stdOut("Installed CyberPanel module", 1) + except Exception as e: + self.stdOut(f"ERROR: Failed to install module: {e}", 1) + return False + + # Verify installation + if os.path.exists(OLS_BINARY_PATH): + if not module_downloaded or os.path.exists(MODULE_PATH): + self.stdOut("=" * 50, 1) + self.stdOut("Custom Binaries Installed Successfully", 1) + self.stdOut("Features enabled:", 1) + self.stdOut(" - Static-linked cross-platform binary", 1) + if module_downloaded: + self.stdOut(" - Apache-style .htaccess support", 1) + self.stdOut(" - php_value/php_flag directives", 1) + self.stdOut(" - Enhanced header control", 1) + self.stdOut(f"Backup: {backup_dir}", 1) + self.stdOut("=" * 50, 1) + # Configure module after installation + self.configureCustomModule() + return True + + self.stdOut("ERROR: Installation verification failed", 1) + return False + + except Exception as msg: + self.stdOut(f"ERROR: {msg} [installCustomOLSBinaries]", 0) + self.stdOut("Continuing with standard OLS", 1) + return True # Non-fatal error, continue + + def configureCustomModule(self): + """Configure CyberPanel module in OpenLiteSpeed config""" + try: + self.stdOut("Configuring CyberPanel module...", 1) + + CONFIG_FILE = "/usr/local/lsws/conf/httpd_config.conf" + + if not os.path.exists(CONFIG_FILE): + self.stdOut("WARNING: Config file not found", 1) + self.stdOut("Module will be auto-loaded", 1) + return True + + # Check if module is already configured + with open(CONFIG_FILE, 'r') as f: + content = f.read() + if 'cyberpanel_ols' in content: + self.stdOut("Module already configured", 1) + return True + + # Add module configuration + module_config = """ +module cyberpanel_ols { + ls_enabled 1 +} +""" + # Backup config + shutil.copy2(CONFIG_FILE, f"{CONFIG_FILE}.backup") + + # Append module config + with open(CONFIG_FILE, 'a') as f: + f.write(module_config) + + self.stdOut("Module configured successfully", 1) + return True + + except Exception as msg: + self.stdOut(f"WARNING: Module configuration failed: {msg}", 1) + self.stdOut("Module may still work via auto-load", 1) + return True # Non-fatal + def installLiteSpeed(self, ent, serial): """Install LiteSpeed Web Server (OpenLiteSpeed or Enterprise)""" try: @@ -807,6 +1075,10 @@ class preFlightsChecks: else: self.install_package('openlitespeed') + # Install custom binaries with PHP config support + # This replaces the standard binary with enhanced version + self.installCustomOLSBinaries() + # Configure OpenLiteSpeed self.fix_ols_configs() self.changePortTo80() diff --git a/plogical/mailUtilities.py b/plogical/mailUtilities.py index 4b727c1c7..00df0c6f3 100644 --- a/plogical/mailUtilities.py +++ b/plogical/mailUtilities.py @@ -1635,51 +1635,130 @@ LogFile /var/log/clamav/clamav.log @staticmethod def reverse_dns_lookup(ip_address): + """ + Perform reverse DNS lookup for the given IP address using external DNS servers. + + Args: + ip_address: The IP address to perform reverse DNS lookup on + + Returns: + list: List of rDNS hostnames found, or empty list if lookup fails + """ try: import requests + from requests.exceptions import RequestException, Timeout, ConnectionError - fetchURLs = requests.get('https://cyberpanel.net/dnsServers.txt') + # Fetch DNS server URLs with proper error handling + try: + fetchURLs = requests.get('https://cyberpanel.net/dnsServers.txt', timeout=10) + except (ConnectionError, Timeout) as e: + logging.CyberCPLogFileWriter.writeToFile(f'Failed to fetch DNS server list from cyberpanel.net: {str(e)}') + return [] + except RequestException as e: + logging.CyberCPLogFileWriter.writeToFile(f'Request error while fetching DNS server list: {str(e)}') + return [] - if fetchURLs.status_code == 200: + if fetchURLs.status_code != 200: + logging.CyberCPLogFileWriter.writeToFile(f'Failed to fetch DNS server list: HTTP {fetchURLs.status_code}') + return [] - urls = fetchURLs.json()['urls'] + try: + urls_data = fetchURLs.json() + if 'urls' not in urls_data: + logging.CyberCPLogFileWriter.writeToFile('DNS server list response missing "urls" key') + return [] + urls = urls_data['urls'] + except (ValueError, KeyError) as e: + logging.CyberCPLogFileWriter.writeToFile(f'Failed to parse DNS server list JSON: {str(e)}') + return [] - if os.path.exists(ProcessUtilities.debugPath): - logging.CyberCPLogFileWriter.writeToFile(f'DNS urls {urls}.') + if not isinstance(urls, list) or len(urls) == 0: + logging.CyberCPLogFileWriter.writeToFile('DNS server list is empty or invalid') + return [] - results = [] + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'DNS urls {urls}.') - ### + results = [] + successful_queries = 0 - for url in urls: - try: - response = requests.get(f'{url}/index.php?ip={ip_address}', timeout=5) + # Query each DNS server + for url in urls: + try: + response = requests.get(f'{url}/index.php?ip={ip_address}', timeout=5) - if os.path.exists(ProcessUtilities.debugPath): - logging.CyberCPLogFileWriter.writeToFile(f'url to call {ip_address} is {url}') + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'url to call {ip_address} is {url}') - if response.status_code == 200: + if response.status_code == 200: + try: data = response.json() if os.path.exists(ProcessUtilities.debugPath): logging.CyberCPLogFileWriter.writeToFile(f'response from dns system {str(data)}') + # Validate response structure + if not isinstance(data, dict): + logging.CyberCPLogFileWriter.writeToFile(f'Invalid response format from {url}: not a dictionary') + continue + + if 'status' not in data: + logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing "status" key') + continue + if data['status'] == 1: - results.append(data['results']['8.8.8.8']) - results.append(data['results']['1.1.1.1']) - results.append(data['results']['9.9.9.9']) - except: - pass + # Validate results structure + if 'results' not in data or not isinstance(data['results'], dict): + logging.CyberCPLogFileWriter.writeToFile(f'Response from {url} missing or invalid "results" key') + continue - if os.path.exists(ProcessUtilities.debugPath): - logging.CyberCPLogFileWriter.writeToFile(f'rDNS result of {ip_address} is {str(results)}') + results_dict = data['results'] + + # Safely extract results from different DNS servers + dns_servers = ['8.8.8.8', '1.1.1.1', '9.9.9.9'] + for dns_server in dns_servers: + if dns_server in results_dict: + result_value = results_dict[dns_server] + if result_value and result_value not in results: + results.append(result_value) + + successful_queries += 1 + else: + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned status != 1: {data.get("status", "unknown")}') + except ValueError as e: + logging.CyberCPLogFileWriter.writeToFile(f'Failed to parse JSON response from {url}: {str(e)}') + continue + except KeyError as e: + logging.CyberCPLogFileWriter.writeToFile(f'Missing key in response from {url}: {str(e)}') + continue + else: + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'DNS server {url} returned HTTP {response.status_code}') + except Timeout as e: + logging.CyberCPLogFileWriter.writeToFile(f'Timeout while querying DNS server {url}: {str(e)}') + continue + except ConnectionError as e: + logging.CyberCPLogFileWriter.writeToFile(f'Connection error while querying DNS server {url}: {str(e)}') + continue + except RequestException as e: + logging.CyberCPLogFileWriter.writeToFile(f'Request error while querying DNS server {url}: {str(e)}') + continue + except Exception as e: + logging.CyberCPLogFileWriter.writeToFile(f'Unexpected error while querying DNS server {url}: {str(e)}') + continue - ### + if os.path.exists(ProcessUtilities.debugPath): + logging.CyberCPLogFileWriter.writeToFile(f'rDNS result of {ip_address} is {str(results)} (successful queries: {successful_queries}/{len(urls)})') - return results + # Return results (empty list if no successful queries) + return results + + except ImportError as e: + logging.CyberCPLogFileWriter.writeToFile(f'Failed to import requests library: {str(e)}') + return [] except BaseException as e: - logging.CyberCPLogFileWriter.writeToFile(f'Error in fetch rDNS {str(msg)}') - # Handle errors, e.g., if reverse DNS lookup fails + logging.CyberCPLogFileWriter.writeToFile(f'Unexpected error in reverse_dns_lookup for IP {ip_address}: {str(e)}') return [] @staticmethod diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 7bdf0bded..130031bd4 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -637,6 +637,279 @@ class Upgrade: except IOError as err: pass + @staticmethod + def detectArchitecture(): + """Detect system architecture - custom binaries only for x86_64""" + try: + import platform + arch = platform.machine() + return arch == "x86_64" + except Exception as msg: + Upgrade.stdOut(str(msg) + " [detectArchitecture]", 0) + return False + + @staticmethod + def detectPlatform(): + """Detect OS platform for binary selection (rhel8, rhel9, ubuntu)""" + try: + # Check for Ubuntu + if os.path.exists('/etc/lsb-release'): + with open('/etc/lsb-release', 'r') as f: + content = f.read() + if 'Ubuntu' in content or 'ubuntu' in content: + return 'ubuntu' + + # Check for RHEL-based distributions + if os.path.exists('/etc/os-release'): + with open('/etc/os-release', 'r') as f: + content = f.read().lower() + + # Check for version 8.x (RHEL, AlmaLinux, Rocky, CloudLinux, CentOS 8) + if 'version="8.' in content or 'version_id="8.' in content: + if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']): + return 'rhel8' + + # Check for version 9.x + if 'version="9.' in content or 'version_id="9.' in content: + if any(distro in content for distro in ['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']): + return 'rhel9' + + # Default to rhel9 if can't detect (safer default for newer systems) + Upgrade.stdOut("WARNING: Could not detect platform, defaulting to rhel9", 0) + return 'rhel9' + + except Exception as msg: + Upgrade.stdOut(f"ERROR detecting platform: {msg}, defaulting to rhel9", 0) + return 'rhel9' + + @staticmethod + def downloadCustomBinary(url, destination, expected_sha256=None): + """Download custom binary file with optional checksum verification""" + try: + Upgrade.stdOut(f"Downloading {os.path.basename(destination)}...", 0) + + # Use wget for better progress display + command = f'wget -q --show-progress {url} -O {destination}' + res = subprocess.call(shlex.split(command)) + + # Check if file was downloaded successfully by verifying it exists and has reasonable size + if os.path.exists(destination): + file_size = os.path.getsize(destination) + # Verify file size is reasonable (at least 10KB to avoid error pages/empty files) + if file_size > 10240: # 10KB + if file_size > 1048576: # 1MB + Upgrade.stdOut(f"Downloaded successfully ({file_size / (1024*1024):.2f} MB)", 0) + else: + Upgrade.stdOut(f"Downloaded successfully ({file_size / 1024:.2f} KB)", 0) + + # Verify checksum if provided + if expected_sha256: + Upgrade.stdOut("Verifying checksum...", 0) + import hashlib + sha256_hash = hashlib.sha256() + with open(destination, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + actual_sha256 = sha256_hash.hexdigest() + + if actual_sha256 == expected_sha256: + Upgrade.stdOut("Checksum verified successfully", 0) + return True + else: + Upgrade.stdOut(f"ERROR: Checksum mismatch!", 0) + Upgrade.stdOut(f"Expected: {expected_sha256}", 0) + Upgrade.stdOut(f"Got: {actual_sha256}", 0) + return False + else: + return True + else: + Upgrade.stdOut(f"ERROR: Downloaded file too small ({file_size} bytes)", 0) + return False + else: + Upgrade.stdOut("ERROR: Download failed - file not found", 0) + return False + + except Exception as msg: + Upgrade.stdOut(f"ERROR: {msg} [downloadCustomBinary]", 0) + return False + + @staticmethod + def installCustomOLSBinaries(): + """Install custom OpenLiteSpeed binaries with PHP config support""" + try: + Upgrade.stdOut("Installing Custom OpenLiteSpeed Binaries", 0) + Upgrade.stdOut("=" * 50, 0) + + # Check architecture + if not Upgrade.detectArchitecture(): + Upgrade.stdOut("WARNING: Custom binaries only available for x86_64", 0) + Upgrade.stdOut("Skipping custom binary installation", 0) + Upgrade.stdOut("Standard OLS will be used", 0) + return True # Not a failure, just skip + + # Detect platform + platform = Upgrade.detectPlatform() + Upgrade.stdOut(f"Detected platform: {platform}", 0) + + # Platform-specific URLs and checksums (OpenLiteSpeed v1.8.4.1 - v2.0.5 Static Build) + BINARY_CONFIGS = { + 'rhel8': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static', + 'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a', + 'module_url': None, # RHEL 8 doesn't have module (use RHEL 9 if needed) + 'module_sha256': None + }, + 'rhel9': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static', + 'sha256': '90468fb38767505185013024678d9144ae13100d2355097657f58719d98fbbc4', + 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_rhel.so', + 'module_sha256': '127227db81bcbebf80b225fc747b69cfcd4ad2f01cea486aa02d5c9ba6c18109' + }, + 'ubuntu': { + 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static', + 'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb', + 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_ubuntu.so', + 'module_sha256': 'e7734f1e6226c2a0a8e00c1f6534ea9f577df9081b046736a774b1c52c28e7e5' + } + } + + config = BINARY_CONFIGS.get(platform) + if not config: + Upgrade.stdOut(f"ERROR: No binaries available for platform {platform}", 0) + Upgrade.stdOut("Skipping custom binary installation", 0) + return True # Not fatal + + OLS_BINARY_URL = config['url'] + OLS_BINARY_SHA256 = config['sha256'] + MODULE_URL = config['module_url'] + MODULE_SHA256 = config['module_sha256'] + OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" + MODULE_PATH = "/usr/local/lsws/modules/cyberpanel_ols.so" + + # Create backup + from datetime import datetime + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") + backup_dir = f"/usr/local/lsws/backup-{timestamp}" + + try: + os.makedirs(backup_dir, exist_ok=True) + if os.path.exists(OLS_BINARY_PATH): + shutil.copy2(OLS_BINARY_PATH, f"{backup_dir}/openlitespeed.backup") + Upgrade.stdOut(f"Backup created at: {backup_dir}", 0) + except Exception as e: + Upgrade.stdOut(f"WARNING: Could not create backup: {e}", 0) + + # Download binaries to temp location + tmp_binary = "/tmp/openlitespeed-custom" + tmp_module = "/tmp/cyberpanel_ols.so" + + Upgrade.stdOut("Downloading custom binaries...", 0) + + # Download OpenLiteSpeed binary with checksum verification + if not Upgrade.downloadCustomBinary(OLS_BINARY_URL, tmp_binary, OLS_BINARY_SHA256): + Upgrade.stdOut("ERROR: Failed to download or verify OLS binary", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Not fatal, continue with standard OLS + + # Download module with checksum verification (if available) + module_downloaded = False + if MODULE_URL and MODULE_SHA256: + if not Upgrade.downloadCustomBinary(MODULE_URL, tmp_module, MODULE_SHA256): + Upgrade.stdOut("ERROR: Failed to download or verify module", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Not fatal, continue with standard OLS + module_downloaded = True + else: + Upgrade.stdOut("Note: No CyberPanel module for this platform", 0) + + # Install OpenLiteSpeed binary + Upgrade.stdOut("Installing custom binaries...", 0) + + try: + shutil.move(tmp_binary, OLS_BINARY_PATH) + os.chmod(OLS_BINARY_PATH, 0o755) + Upgrade.stdOut("Installed OpenLiteSpeed binary", 0) + except Exception as e: + Upgrade.stdOut(f"ERROR: Failed to install binary: {e}", 0) + return False + + # Install module (if downloaded) + if module_downloaded: + try: + os.makedirs(os.path.dirname(MODULE_PATH), exist_ok=True) + shutil.move(tmp_module, MODULE_PATH) + os.chmod(MODULE_PATH, 0o644) + Upgrade.stdOut("Installed CyberPanel module", 0) + except Exception as e: + Upgrade.stdOut(f"ERROR: Failed to install module: {e}", 0) + return False + + # Verify installation + if os.path.exists(OLS_BINARY_PATH): + if not module_downloaded or os.path.exists(MODULE_PATH): + Upgrade.stdOut("=" * 50, 0) + Upgrade.stdOut("Custom Binaries Installed Successfully", 0) + Upgrade.stdOut("Features enabled:", 0) + Upgrade.stdOut(" - Static-linked cross-platform binary", 0) + if module_downloaded: + Upgrade.stdOut(" - Apache-style .htaccess support", 0) + Upgrade.stdOut(" - php_value/php_flag directives", 0) + Upgrade.stdOut(" - Enhanced header control", 0) + Upgrade.stdOut(f"Backup: {backup_dir}", 0) + Upgrade.stdOut("=" * 50, 0) + # Configure module after installation + Upgrade.configureCustomModule() + return True + + Upgrade.stdOut("ERROR: Installation verification failed", 0) + return False + + except Exception as msg: + Upgrade.stdOut(f"ERROR: {msg} [installCustomOLSBinaries]", 0) + Upgrade.stdOut("Continuing with standard OLS", 0) + return True # Non-fatal error, continue + + @staticmethod + def configureCustomModule(): + """Configure CyberPanel module in OpenLiteSpeed config""" + try: + Upgrade.stdOut("Configuring CyberPanel module...", 0) + + CONFIG_FILE = "/usr/local/lsws/conf/httpd_config.conf" + + if not os.path.exists(CONFIG_FILE): + Upgrade.stdOut("WARNING: Config file not found", 0) + Upgrade.stdOut("Module will be auto-loaded", 0) + return True + + # Check if module is already configured + with open(CONFIG_FILE, 'r') as f: + content = f.read() + if 'cyberpanel_ols' in content: + Upgrade.stdOut("Module already configured", 0) + return True + + # Add module configuration + module_config = """ +module cyberpanel_ols { + ls_enabled 1 +} +""" + # Backup config + shutil.copy2(CONFIG_FILE, f"{CONFIG_FILE}.backup") + + # Append module config + with open(CONFIG_FILE, 'a') as f: + f.write(module_config) + + Upgrade.stdOut("Module configured successfully", 0) + return True + + except Exception as msg: + Upgrade.stdOut(f"WARNING: Module configuration failed: {msg}", 0) + Upgrade.stdOut("Module may still work via auto-load", 0) + return True # Non-fatal + @staticmethod def download_install_phpmyadmin(): try: @@ -4892,6 +5165,10 @@ slowlog = /var/log/php{version}-fpm-slow.log Upgrade.dockerUsers() Upgrade.setupPHPSymlink() Upgrade.setupComposer() + + # Install custom OpenLiteSpeed binaries if OLS is installed + if os.path.exists('/usr/local/lsws/bin/openlitespeed'): + Upgrade.installCustomOLSBinaries() ## diff --git a/plogical/virtualHostUtilities.py b/plogical/virtualHostUtilities.py index 8a78991a8..78a4d2847 100644 --- a/plogical/virtualHostUtilities.py +++ b/plogical/virtualHostUtilities.py @@ -124,8 +124,14 @@ class virtualHostUtilities: else: try: rDNS = mailUtilities.reverse_dns_lookup(serverIP) + # Check if rDNS lookup returned empty results (indicating lookup failure) + if not rDNS or len(rDNS) == 0: + message = f'Failed to perform reverse DNS lookup for server IP {serverIP}. The DNS lookup service may be unavailable or the IP address may not have rDNS configured. Please verify your rDNS settings with your hosting provider or check the "Skip rDNS/PTR Check" option if you do not need email services. [404]' + logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message) + logging.CyberCPLogFileWriter.writeToFile(message) + return 0 except Exception as e: - message = f'Failed to perform reverse DNS lookup: {str(e)} [404]' + message = f'Failed to perform reverse DNS lookup for server IP {serverIP}: {str(e)}. Please verify your rDNS settings with your hosting provider or check the "Skip rDNS/PTR Check" option if you do not need email services. [404]' logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message) logging.CyberCPLogFileWriter.writeToFile(message) return 0 @@ -329,9 +335,22 @@ class virtualHostUtilities: #first check if hostname is already configured as rDNS, if not return error + # Validate that we have rDNS results before checking + if not rDNS or len(rDNS) == 0: + message = f'Reverse DNS lookup failed for server IP {serverIP}. Unable to verify if domain "{Domain}" is configured as rDNS. Please check your rDNS configuration with your hosting provider or select "Skip rDNS/PTR Check" if you do not need email services. [404]' + print(message) + logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message) + logging.CyberCPLogFileWriter.writeToFile(message) + config['hostname'] = Domain + config['onboarding'] = 3 + config['skipRDNSCheck'] = skipRDNSCheck + admin.config = json.dumps(config) + admin.save() + return 0 if Domain not in rDNS: - message = 'Domain that you have provided is not configured as rDNS for your server IP. [404]' + rDNS_list_str = ', '.join(rDNS) if rDNS else 'none' + message = f'Domain "{Domain}" that you have provided is not configured as rDNS for your server IP {serverIP}. Current rDNS records: {rDNS_list_str}. Please configure rDNS (PTR record) for your IP address to point to "{Domain}" with your hosting provider, or select "Skip rDNS/PTR Check" if you do not need email services. [404]' print(message) logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message) logging.CyberCPLogFileWriter.writeToFile(message) diff --git a/static/firewall/firewall.js b/static/firewall/firewall.js index eddd74ea7..cdb37a6c1 100644 --- a/static/firewall/firewall.js +++ b/static/firewall/firewall.js @@ -1285,28 +1285,36 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) { if (response.data.owaspInstalled === 1) { $('#owaspInstalled').bootstrapToggle('on'); $scope.owaspDisable = false; + owaspInstalled = true; } else { $('#owaspInstalled').bootstrapToggle('off'); $scope.owaspDisable = true; + owaspInstalled = false; } if (response.data.comodoInstalled === 1) { $('#comodoInstalled').bootstrapToggle('on'); $scope.comodoDisable = false; + comodoInstalled = true; } else { $('#comodoInstalled').bootstrapToggle('off'); $scope.comodoDisable = true; + comodoInstalled = false; } } else { if (response.data.owaspInstalled === 1) { $scope.owaspDisable = false; + owaspInstalled = true; } else { $scope.owaspDisable = true; + owaspInstalled = false; } if (response.data.comodoInstalled === 1) { $scope.comodoDisable = false; + comodoInstalled = true; } else { $scope.comodoDisable = true; + comodoInstalled = false; } } diff --git a/websiteFunctions/website.py b/websiteFunctions/website.py index e366626c5..d028418d5 100644 --- a/websiteFunctions/website.py +++ b/websiteFunctions/website.py @@ -2563,11 +2563,12 @@ Require valid-user childDomains = [] for web in websites: - for child in web.childdomains_set.filter(alais=0): - if child.domain == f'mail.{web.domain}': - pass - else: - childDomains.append(child) + for child in web.childdomains_set.all(): + if child.alais == 0: + if child.domain == f'mail.{web.domain}': + pass + else: + childDomains.append(child) pagination = self.getPagination(len(childDomains), recordsToShow) json_data = self.findChildsListJson(childDomains[finalPageNumber:endPageNumber]) @@ -2577,6 +2578,8 @@ Require valid-user final_json = json.dumps(final_dic) return HttpResponse(final_json) except BaseException as msg: + import traceback + logging.CyberCPLogFileWriter.writeToFile(f"fetchChildDomainsMain error for userID {userID}: {str(msg)}\n{traceback.format_exc()}") dic = {'status': 1, 'listWebSiteStatus': 0, 'error_message': str(msg)} json_data = json.dumps(dic) return HttpResponse(json_data)