Skip to content

Commit f50006a

Browse files
Fixes
1 parent ae691c7 commit f50006a

File tree

5 files changed

+193
-30
lines changed

5 files changed

+193
-30
lines changed

app/directadmin_api.py

Lines changed: 118 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ def _make_request(self, endpoint, data=None, method='POST'):
6464
text = response.text.strip()
6565
print(f"Raw response: {text[:500]}...") # First 500 chars for debugging
6666

67+
# Check if we got HTML instead of API data
68+
if text.startswith('<!DOCTYPE html') or text.startswith('<html'):
69+
print(f"ERROR: Received HTML response instead of API data")
70+
print(f"This usually means the API endpoint doesn't exist or authentication failed")
71+
return None
72+
73+
# Check for empty response
74+
if not text:
75+
print(f"ERROR: Empty response from DirectAdmin API")
76+
return None
77+
6778
# Parse response into dictionary first
6879
result = {}
6980

@@ -142,52 +153,107 @@ def _make_request(self, endpoint, data=None, method='POST'):
142153
def test_connection(self):
143154
"""Test the connection to DirectAdmin"""
144155
try:
156+
print(f"\n=== Testing Connection to {self.server} ===")
157+
print(f"Username: {self.username}")
158+
print(f"Domain: {self.domain}")
159+
145160
# Try CMD_API_SHOW_DOMAINS first
146161
endpoint = '/CMD_API_SHOW_DOMAINS'
147162
response = self._make_request(endpoint, method='GET')
148163

149-
if response:
164+
if response is not None:
150165
if isinstance(response, dict):
151166
# Check if our domain is in the list
152167
if self.domain:
153168
# DirectAdmin might return domains in various formats
154169
domain_list = []
155170
for key, value in response.items():
156171
if 'domain' in key.lower() or key.startswith('list'):
157-
domain_list.append(value)
158-
elif '.' in key: # Might be domain name as key
172+
if isinstance(value, list):
173+
domain_list.extend(value)
174+
else:
175+
domain_list.append(value)
176+
elif '.' in key and not key.startswith('<'): # Might be domain name as key, but not HTML
159177
domain_list.append(key)
160178

179+
print(f"Found domains: {domain_list}")
161180
if self.domain in domain_list:
162181
return True, f"Successfully connected. Domain {self.domain} found."
163182
else:
164-
return True, f"Connected, but domain {self.domain} not found in account."
183+
return True, f"Connected, but domain {self.domain} not found in account. Available domains: {', '.join(domain_list[:3])}{'...' if len(domain_list) > 3 else ''}"
165184
else:
166185
return True, "Successfully connected to DirectAdmin."
167186
else:
168187
return True, "Successfully connected to DirectAdmin."
188+
else:
189+
print("CMD_API_SHOW_DOMAINS returned None (likely HTML response)")
169190

170191
# If that fails, try a simpler endpoint
192+
print("Trying CMD_API_SHOW_USER_CONFIG...")
171193
endpoint = '/CMD_API_SHOW_USER_CONFIG'
172194
response = self._make_request(endpoint, method='GET')
173195

174-
if response:
196+
if response is not None:
175197
return True, "Successfully connected to DirectAdmin."
198+
else:
199+
print("CMD_API_SHOW_USER_CONFIG also returned None")
176200

177-
return False, "Failed to connect. Please check your credentials."
201+
return False, "Failed to connect. Server returned HTML instead of API data - please check your DirectAdmin URL, credentials, and API access."
178202

179203
except Exception as e:
180204
import traceback
181-
print(f"Connection error: {str(e)}")
205+
error_msg = str(e)
206+
print(f"Connection test exception: {error_msg}")
182207
traceback.print_exc()
183-
return False, "Connection error: Unable to connect to DirectAdmin."
208+
209+
# Provide more specific error messages
210+
if 'timeout' in error_msg.lower():
211+
return False, "Connection timed out. Please check your DirectAdmin server URL and network connection."
212+
elif 'connection' in error_msg.lower():
213+
return False, "Unable to connect to DirectAdmin server. Please verify the server URL and credentials."
214+
elif 'ssl' in error_msg.lower() or 'certificate' in error_msg.lower():
215+
return False, "SSL certificate error. Try using HTTP instead of HTTPS."
216+
else:
217+
return False, f"Connection error: {error_msg}"
218+
219+
def validate_domain_access(self):
220+
"""Check if the current domain is accessible via the API"""
221+
try:
222+
print(f"\n=== Validating Domain Access for {self.domain} ===")
223+
224+
# Try to get domain list to verify access
225+
endpoint = '/CMD_API_SHOW_DOMAINS'
226+
response = self._make_request(endpoint, method='GET')
227+
228+
if response and isinstance(response, dict):
229+
domain_list = []
230+
for key, value in response.items():
231+
if 'domain' in key.lower() or key.startswith('list'):
232+
domain_list.append(value)
233+
elif '.' in key and not key.startswith('<'): # Might be domain name as key, but not HTML
234+
domain_list.append(key)
235+
236+
if self.domain in domain_list:
237+
print(f"✓ Domain {self.domain} found in account")
238+
return True, f"Domain {self.domain} is accessible"
239+
else:
240+
print(f"✗ Domain {self.domain} not found in account")
241+
print(f"Available domains: {domain_list}")
242+
return False, f"Domain {self.domain} not found in DirectAdmin account"
243+
244+
print("Could not verify domain access - no domain list returned")
245+
return False, "Unable to verify domain access"
246+
247+
except Exception as e:
248+
print(f"Error validating domain access: {e}")
249+
return False, f"Error validating domain: {str(e)}"
184250

185251
def get_email_accounts(self):
186252
"""Get all email accounts for the domain"""
187253
try:
188254
print(f"\n=== Getting Email Accounts for {self.domain} ===")
189255

190-
# Try multiple endpoints
256+
# Try API endpoints only
191257
endpoints = [
192258
('/CMD_API_POP', {'action': 'list', 'domain': self.domain}),
193259
('/CMD_API_POP', {'domain': self.domain}),
@@ -202,7 +268,11 @@ def get_email_accounts(self):
202268
break
203269

204270
if response is None:
205-
print("No response from any email endpoint")
271+
print("No valid response from any email accounts endpoint")
272+
print("This could mean:")
273+
print("- The domain doesn't exist in DirectAdmin")
274+
print("- API user doesn't have permission for this domain")
275+
print("- DirectAdmin API is not properly configured")
206276
return []
207277

208278
print(f"Raw response type: {type(response)}")
@@ -267,15 +337,29 @@ def get_email_accounts(self):
267337
elif line and not line.startswith('error'):
268338
accounts.append(f"{line}@{self.domain}")
269339

270-
# Ensure all accounts have domain part
340+
# Ensure all accounts have domain part and filter out invalid entries
271341
processed_accounts = []
272342
for account in accounts:
273343
if account: # Skip empty strings
344+
# Skip entries that look like HTML
345+
if account.startswith('<') or '"' in account or account.startswith(':root'):
346+
print(f"Skipping invalid account that looks like HTML: {account}")
347+
continue
348+
349+
# Validate email format
350+
import re
274351
if '@' not in account:
275-
# Add domain if missing
276-
processed_accounts.append(f"{account}@{self.domain}")
352+
# Validate username part before adding domain
353+
if re.match(r'^[a-zA-Z0-9._-]+$', account):
354+
processed_accounts.append(f"{account}@{self.domain}")
355+
else:
356+
print(f"Skipping invalid username: {account}")
277357
else:
278-
processed_accounts.append(account)
358+
# Validate full email
359+
if re.match(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', account):
360+
processed_accounts.append(account)
361+
else:
362+
print(f"Skipping invalid email: {account}")
279363

280364
# Remove duplicates and filter out API user
281365
processed_accounts = list(set(processed_accounts))
@@ -298,11 +382,10 @@ def get_forwarders(self):
298382
try:
299383
print(f"\n=== Getting Forwarders for {self.domain} ===")
300384

301-
# Try multiple endpoint variations
385+
# Try API endpoints only (avoid web interface endpoints)
302386
endpoints = [
303387
('/CMD_API_EMAIL_FORWARDERS', {'domain': self.domain, 'action': 'list'}),
304388
('/CMD_API_EMAIL_FORWARDERS', {'domain': self.domain}),
305-
('/CMD_EMAIL_FORWARDERS', {'domain': self.domain}),
306389
]
307390

308391
response = None
@@ -312,17 +395,21 @@ def get_forwarders(self):
312395
# Try GET first
313396
response = self._make_request(endpoint, params, method='GET')
314397
if response:
315-
print(f"Got response with GET")
398+
print(f"Got valid response with GET")
316399
break
317400

318401
# Try POST
319402
response = self._make_request(endpoint, params, method='POST')
320403
if response:
321-
print(f"Got response with POST")
404+
print(f"Got valid response with POST")
322405
break
323406

324407
if response is None:
325-
print("ERROR: No response from any forwarders endpoint!")
408+
print("ERROR: No valid response from any API endpoint!")
409+
print("This could mean:")
410+
print("- The domain doesn't exist in DirectAdmin")
411+
print("- API user doesn't have permission for this domain")
412+
print("- DirectAdmin API is not properly configured")
326413
return []
327414

328415
print(f"\n=== FORWARDERS RAW RESPONSE ===")
@@ -377,6 +464,18 @@ def get_forwarders(self):
377464
if key.startswith('error') or key == 'domain':
378465
continue
379466

467+
# Skip invalid keys that look like HTML
468+
if key.startswith('<') or '"' in key or key.startswith(':root'):
469+
print(f"Skipping invalid key that looks like HTML: {key}")
470+
continue
471+
472+
# Validate that the key looks like a valid email username
473+
# Allow alphanumeric, dots, hyphens, underscores
474+
import re
475+
if not re.match(r'^[a-zA-Z0-9._-]+$', key):
476+
print(f"Skipping invalid username: {key}")
477+
continue
478+
380479
# IMPORTANT: Accept ALL non-empty values as valid destinations
381480
if value:
382481
# Key is the username, value is the destination

app/main.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ def get_email_accounts():
161161
domain
162162
)
163163

164+
# Validate domain access first
165+
domain_valid, domain_message = api.validate_domain_access()
166+
if not domain_valid:
167+
return jsonify({
168+
'error': f'Domain access validation failed: {domain_message}',
169+
'accounts': []
170+
}), 403
171+
164172
# Get email accounts
165173
accounts = api.get_email_accounts()
166174

@@ -222,6 +230,14 @@ def get_forwarders():
222230
domain
223231
)
224232

233+
# Validate domain access first
234+
domain_valid, domain_message = api.validate_domain_access()
235+
if not domain_valid:
236+
return jsonify({
237+
'error': f'Domain access validation failed: {domain_message}',
238+
'forwarders': []
239+
}), 403
240+
225241
# Get forwarders
226242
forwarders = api.get_forwarders()
227243

app/settings.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,21 @@ def test_connection():
124124
})
125125

126126
except Exception as e:
127-
print(f"Test connection error: {str(e)}")
127+
error_msg = str(e)
128+
print(f"Test connection error: {error_msg}")
128129
print(traceback.format_exc())
129-
return jsonify({'error': 'An internal error has occurred.', 'success': False}), 200
130+
131+
# Provide more specific error messages
132+
if 'timeout' in error_msg.lower():
133+
error_msg = 'Connection timed out. Please check your DirectAdmin server URL and network connection.'
134+
elif 'connection' in error_msg.lower():
135+
error_msg = 'Unable to connect to DirectAdmin server. Please verify the server URL is correct.'
136+
elif 'ssl' in error_msg.lower() or 'certificate' in error_msg.lower():
137+
error_msg = 'SSL certificate error. Try using HTTP instead of HTTPS, or check your certificate configuration.'
138+
else:
139+
error_msg = f'Connection test failed: {error_msg}'
140+
141+
return jsonify({'error': error_msg, 'success': False}), 200
130142

131143
@settings_bp.route('/api/domains', methods=['GET'])
132144
@login_required

static/dashboard.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,19 @@ async function loadEmailAccounts() {
123123
updateDestinationDropdown();
124124
} else {
125125
console.error('Failed to load email accounts:', data.error);
126-
showMessage(`Failed to load email accounts for ${selectedDomain}`, 'error');
126+
if (response.status === 403) {
127+
showMessage(`Domain access denied: ${selectedDomain} may not be configured in your DirectAdmin account`, 'error');
128+
} else {
129+
showMessage(`Failed to load email accounts for ${selectedDomain}: ${data.error || 'Unknown error'}`, 'error');
130+
}
131+
132+
// Clear dropdown on error
133+
updateDestinationDropdown();
127134
}
128135
} catch (error) {
129136
console.error('Error loading email accounts:', error);
137+
showMessage(`Error loading email accounts for ${selectedDomain}`, 'error');
138+
updateDestinationDropdown();
130139
}
131140
}
132141

@@ -217,7 +226,12 @@ async function loadForwarders() {
217226

218227
} catch (error) {
219228
console.error('Error loading forwarders:', error);
220-
tbody.innerHTML = '<tr><td colspan="3" class="error-message">Failed to load forwarders for ' + selectedDomain + '. Please check your DirectAdmin settings.</td></tr>';
229+
230+
if (error.response && error.response.status === 403) {
231+
tbody.innerHTML = '<tr><td colspan="3" class="error-message">Domain access denied: ' + selectedDomain + ' may not be configured in your DirectAdmin account.</td></tr>';
232+
} else {
233+
tbody.innerHTML = '<tr><td colspan="3" class="error-message">Failed to load forwarders for ' + selectedDomain + '. Please check your DirectAdmin settings.</td></tr>';
234+
}
221235
}
222236
}
223237

0 commit comments

Comments
 (0)