Compare commits

...

3 Commits

Author SHA1 Message Date
usmannasir
a2f9cf99eb Fix ACL child domain permission issues for non-admin users
- Fix checkOwnership() to return explicit 0 instead of None when checking child domain ownership
  This resolves permission failures for non-admin ACL users trying to manage child domains

- Improve fetchChildDomainsMain() with more robust child domain filtering
  Changed from .filter(alais=0) to .all() with explicit check to prevent silent failures

- Add error logging with traceback to fetchChildDomainsMain() for better debugging

These changes allow non-admin users with proper ACL permissions to view and manage
child domains for websites they own.
2025-12-14 17:59:19 +04:00
usmannasir
ae020ece7b Fix OWASP CRS UI toggle state issues and improve installation reliability
This commit resolves issues where the OWASP CRS toggle in ModSecurity settings
would appear to flip back to OFF even when installation succeeded, and improves
detection of manually installed OWASP CRS rules.

Issues Fixed:
1. Toggle not updating immediately after installation/uninstallation
2. Manual OWASP installations to rules.conf not detected by toggle
3. Silent installation failures without detailed error logging

Changes:

firewall/static/firewall/firewall.js:
- Update toggle state immediately after successful installation (getOWASPAndComodoStatus(true))
- Update toggle state after failed installation to show correct OFF state
- Provides instant visual feedback instead of requiring page refresh

firewall/firewallManager.py (getOWASPAndComodoStatus):
- Expand detection logic to check both httpd_config.conf AND rules.conf
- Detect manual OWASP installations (Include/modsecurity_rules_file with owasp/crs-setup)
- Case-insensitive pattern matching for better compatibility

plogical/modSec.py (setupOWASPRules):
- Add specific error logging for each installation step failure
- Log detailed messages: directory creation, download, extraction, configuration
- Helps diagnose: network issues, missing tools (wget/unzip), permission problems

Impact:
- Toggle correctly reflects OWASP CRS state after enable/disable operations
- Manual installations following external tutorials now detected correctly
- Installation failures are logged with specific error messages for debugging
- Improves UX by eliminating perception that "toggle keeps flipping back"

Fixes: OWASP CRS toggle UI bug
Related: Community thread https://community.cyberpanel.net/t/4-mod-security-rules-packages/133/8
Related: Ticket #GTPDPO7EV
2025-11-24 01:53:36 +05:00
usmannasir
836a6e26a7 Fix custom installation email components bug: Skip email operations when services not installed
This commit resolves the issue where CyberPanel attempts to configure email/DKIM settings
even when email services were explicitly disabled during custom installation, causing
hostname SSL setup and website creation to fail with "No such file or directory: '/etc/postfix/main.cf'" errors.

Changes:
- Added emailServicesInstalled() utility function to check for /home/cyberpanel/postfix marker
- OnBoardingHostName(): Wrap email operations (issueSSLForMailServer, postfix commands) with checks
- OnBoardingHostName(): Allow hostname setup to complete without email services
- issueSSLForMailServer(): Add early return if email services not installed
- issueSSLForMailServer(): Verify /etc/postfix directory exists before operations
- issueSSLForMailServer(): Check /etc/postfix/main.cf exists before reading
- setupAutoDiscover(): Add early return if email services not installed
- setupAutoDiscover(): Check /etc/postfix/main.cf exists before accessing
- mailUtilities.configureOpenDKIM(): Verify main.cf exists before configuration

Impact:
- Hostname SSL setup now completes successfully without email components
- Website creation works correctly on custom installs without email
- No more file not found errors for /etc/postfix/main.cf
- Graceful degradation: operations skip email setup with log messages

Fixes: Custom installation hostname SSL 404 error
Fixes: Website creation DKIM failure on custom installs
Related: Ticket #RMKRFFGKC
2025-11-22 03:49:07 +05:00
7 changed files with 146 additions and 58 deletions

View File

@@ -1020,6 +1020,22 @@ class FirewallManager:
if owaspInstalled == 1 and comodoInstalled == 1:
break
# Also check rules.conf for manual OWASP installations
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
final_dic = {
'modSecInstalled': 1,
'owaspInstalled': owaspInstalled,

View File

@@ -1366,7 +1366,8 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) {
$scope.installationFailed = true;
$scope.installationSuccess = false;
getOWASPAndComodoStatus(false);
// Update toggle state immediately to reflect installation result
getOWASPAndComodoStatus(true);
} else {
$scope.modsecLoading = true;
@@ -1379,6 +1380,9 @@ app.controller('modSecRulesPack', function ($scope, $http, $timeout, $window) {
$scope.installationSuccess = true;
$scope.errorMessage = response.data.error_message;
// Update toggle to reflect failed installation (will show OFF)
getOWASPAndComodoStatus(true);
}
}

View File

@@ -761,6 +761,8 @@ class ACLManager:
else:
if childDomain.master.admin.owner == admin.pk:
return 1
else:
return 0
except:
domainName = Websites.objects.get(domain=domain)

View File

@@ -582,6 +582,12 @@ InternalHosts refile:/etc/opendkim/TrustedHosts
postfixFilePath = "/etc/postfix/main.cf"
# Check if postfix main.cf exists before configuring
if not os.path.exists(postfixFilePath):
logging.CyberCPLogFileWriter.writeToFile(f"configureOpenDKIM: {postfixFilePath} not found, skipping postfix DKIM configuration")
print("1,Postfix not installed")
return
configData = """
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters

View File

@@ -405,6 +405,7 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
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 [setupOWASPRules]")
return 0
if os.path.exists(pathToOWASFolderNew):
@@ -420,30 +421,35 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to download OWASP CRS from GitHub. Check internet connection. [setupOWASPRules]")
return 0
command = "unzip -o /usr/local/lsws/conf/modsec/owasp.zip -d /usr/local/lsws/conf/modsec/"
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to extract OWASP CRS zip file. Ensure unzip is installed. [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'
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to rename OWASP CRS directory. File may already exist. [setupOWASPRules]")
return 0
command = 'mv %s/crs-setup.conf.example %s/crs-setup.conf' % (pathToOWASFolderNew, pathToOWASFolderNew)
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to setup crs-setup.conf configuration file. [setupOWASPRules]")
return 0
command = 'mv %s/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example %s/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf' % (pathToOWASFolderNew, pathToOWASFolderNew)
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to setup REQUEST-900 exclusion rules. [setupOWASPRules]")
return 0
command = 'mv %s/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example %s/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf' % (
@@ -451,6 +457,7 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
result = subprocess.call(shlex.split(command))
if result != 0:
logging.CyberCPLogFileWriter.writeToFile("Failed to setup RESPONSE-999 exclusion rules. [setupOWASPRules]")
return 0
content = """include {pathToOWASFolderNew}/crs-setup.conf

View File

@@ -53,7 +53,16 @@ class virtualHostUtilities:
redisConf = '/usr/local/lsws/conf/dvhost_redis.conf'
vhostConfPath = '/usr/local/lsws/conf'
@staticmethod
def emailServicesInstalled():
"""
Check if email services (Postfix/OpenDKIM) are installed and configured.
Returns True if email services are available, False otherwise.
This checks for the marker file /home/cyberpanel/postfix which is created
during email services installation.
"""
return os.path.exists('/home/cyberpanel/postfix')
@staticmethod
def OnBoardingHostName(Domain, tempStatusPath, skipRDNSCheck):
@@ -82,29 +91,33 @@ class virtualHostUtilities:
except:
CurrentHostName = ''
if skipRDNSCheck:
pass
else:
if os.path.exists('/home/cyberpanel/postfix'):
pass
else:
message = 'This server does not come with postfix installed. [404]'
print(message)
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message)
logging.CyberCPLogFileWriter.writeToFile(message)
return 0
# Check if email services are installed
# If not installed and rDNS check is required, log warning but continue
# Email-specific operations will be skipped later
emailServicesAvailable = virtualHostUtilities.emailServicesInstalled()
if not skipRDNSCheck and not emailServicesAvailable:
message = 'Email services not installed. Hostname setup will continue without email configuration.'
print(message)
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message)
logging.CyberCPLogFileWriter.writeToFile(message)
####
# Get postfix hostname with error handling
try:
PostFixHostname = mailUtilities.FetchPostfixHostname()
except Exception as e:
message = f'Failed to fetch postfix hostname: {str(e)} [404]'
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message)
logging.CyberCPLogFileWriter.writeToFile(message)
return 0
# Get postfix hostname with error handling (only if email services are installed)
PostFixHostname = None
if emailServicesAvailable:
try:
PostFixHostname = mailUtilities.FetchPostfixHostname()
except Exception as e:
message = f'Failed to fetch postfix hostname: {str(e)}, continuing without email setup'
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, message)
logging.CyberCPLogFileWriter.writeToFile(message)
emailServicesAvailable = False # Disable email operations if we can't fetch postfix hostname
else:
# Set a default hostname when email services are not available
PostFixHostname = Domain
# Get server IP with error handling
try:
@@ -391,51 +404,66 @@ class virtualHostUtilities:
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Hostname SSL issued,50')
# Only setup mail server SSL if email services are installed
if emailServicesAvailable:
virtualHostUtilities.issueSSLForMailServer(Domain, path)
virtualHostUtilities.issueSSLForMailServer(Domain, path)
try:
with open(filePath, 'r') as f:
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
try:
with open(filePath, 'r') as f:
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
# Safely extract SSL provider from issuer components
issuer_components = x509.get_issuer().get_components()
SSLProvider = 'Denial' # Default to Denial if we can't find the provider
# Look for the Organization (O) field in the issuer
for component in issuer_components:
if component[0] == b'O': # Organization field
SSLProvider = component[1].decode('utf-8')
break
elif component[0] == b'CN' and SSLProvider == 'Denial': # Fallback to CN if O not found
SSLProvider = component[1].decode('utf-8')
except (FileNotFoundError, IndexError, OpenSSL.crypto.Error) as e:
SSLProvider = 'Denial'
logging.CyberCPLogFileWriter.writeToFile(f"Mail server SSL check error: {str(e)}")
# Safely extract SSL provider from issuer components
issuer_components = x509.get_issuer().get_components()
SSLProvider = 'Denial' # Default to Denial if we can't find the provider
if SSLProvider == 'Denial':
message = 'Failed to issue Mail server SSL, either its DNS record is not propagated or the domain is behind Cloudflare. [404]'
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
# Look for the Organization (O) field in the issuer
for component in issuer_components:
if component[0] == b'O': # Organization field
SSLProvider = component[1].decode('utf-8')
break
elif component[0] == b'CN' and SSLProvider == 'Denial': # Fallback to CN if O not found
SSLProvider = component[1].decode('utf-8')
except (FileNotFoundError, IndexError, OpenSSL.crypto.Error) as e:
SSLProvider = 'Denial'
logging.CyberCPLogFileWriter.writeToFile(f"Mail server SSL check error: {str(e)}")
if SSLProvider == 'Denial':
message = 'Failed to issue Mail server SSL, either its DNS record is not propagated or the domain is behind Cloudflare. [404]'
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
else:
config['hostname'] = Domain
config['onboarding'] = 1
config['skipRDNSCheck'] = skipRDNSCheck
admin.config = json.dumps(config)
admin.save()
# First update the postfix hash database, then restart services
command = 'postmap -F hash:/etc/postfix/vmail_ssl.map && systemctl restart postfix && systemctl restart dovecot'
ProcessUtilities.executioner(command, 'root', True)
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Completed. [200]')
else:
# Email services not installed, skip mail server SSL setup
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Email services not installed, skipping mail server SSL setup.')
config['hostname'] = Domain
config['onboarding'] = 1
config['skipRDNSCheck'] = skipRDNSCheck
admin.config = json.dumps(config)
admin.save()
# First update the postfix hash database, then restart services
command = 'postmap -F hash:/etc/postfix/vmail_ssl.map && systemctl restart postfix && systemctl restart dovecot'
ProcessUtilities.executioner(command, 'root', True)
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Completed. [200]')
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Hostname setup completed (without email configuration). [200]')
@staticmethod
def setupAutoDiscover(mailDomain, tempStatusPath, virtualHostName, admin):
# Check if email services are installed before proceeding
if not virtualHostUtilities.emailServicesInstalled():
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Email services not installed, skipping mail domain setup.')
logging.CyberCPLogFileWriter.writeToFile('setupAutoDiscover: Email services not installed, skipping.')
return
if mailDomain:
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Creating mail child domain..,80')
@@ -479,6 +507,11 @@ local_name %s {
postFixPath = '/etc/postfix/main.cf'
# Check if main.cf exists before accessing it
if not os.path.exists(postFixPath):
logging.CyberCPLogFileWriter.writeToFile(f"setupAutoDiscover: {postFixPath} not found, skipping postfix TLS SNI configuration")
return
postFixContent = open(postFixPath, 'r').read()
if postFixContent.find('tls_server_sni_maps') == -1:
@@ -1108,6 +1141,17 @@ local_name %s {
@staticmethod
def issueSSLForMailServer(virtualHost, path):
try:
# Check if email services are installed before proceeding
if not virtualHostUtilities.emailServicesInstalled():
logging.CyberCPLogFileWriter.writeToFile("Email services not installed, skipping mail server SSL setup")
print("1,Email services not installed")
return 1, 'Email services not installed'
# Verify critical email directories exist
if not os.path.exists('/etc/postfix'):
logging.CyberCPLogFileWriter.writeToFile("/etc/postfix directory not found, skipping mail server SSL")
print("1,Postfix directory not found")
return 1, 'Postfix directory not found'
srcFullChain = '/etc/letsencrypt/live/' + virtualHost + '/fullchain.pem'
srcPrivKey = '/etc/letsencrypt/live/' + virtualHost + '/privkey.pem'
@@ -1183,6 +1227,12 @@ local_name %s {
filePath = "/etc/postfix/main.cf"
# Check if main.cf exists before trying to read it
if not os.path.exists(filePath):
logging.CyberCPLogFileWriter.writeToFile(f"{filePath} not found, skipping postfix hostname update")
print("1,Postfix main.cf not found")
return 1, 'Postfix main.cf not found'
data = open(filePath, 'r').readlines()
writeFile = open(filePath, 'w')

View File

@@ -2519,11 +2519,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])
@@ -2533,6 +2534,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)