Compare commits

...

3 commits

Author SHA1 Message Date
6c60d88f62 Merge branch 'security/sanitize-test-scripts' 2025-09-07 21:26:52 +02:00
54c594e656 chore: merkwerk auto-update 2025-09-07 21:25:38 +02:00
08b49d3d75 security: sanitize internal infrastructure details from open source package
- Remove production_test_sequence.sh (DAW-specific production tests)
- Remove setup_env.sh (obsolete .env setup, replaced by furt.conf)
- Sanitize test scripts: replace dragons-at-work.de with example.com
- Sanitize API keys: replace dev keys with placeholder values
- Remove hardcoded DAW fallbacks from http_server.lua and smtp.lua
- Update .gitignore to exclude production-specific test files

Tests remain functional for developers with example domains.
All internal DAW infrastructure details removed from package.

Closes #101
2025-09-07 21:25:25 +02:00
12 changed files with 78 additions and 259 deletions

1
.gitignore vendored
View file

@ -68,3 +68,4 @@ config.production.lua
config/furt.conf config/furt.conf
scripts/production_test_sequence.sh

View file

@ -24,3 +24,4 @@ a670de0f,25a709e,feature/pid-file-service-management,2025-09-05T20:30:13Z,michae
a670de0f,59f372f,feature/pid-file-service-management,2025-09-07T14:58:01Z,michael,git,lua-api a670de0f,59f372f,feature/pid-file-service-management,2025-09-07T14:58:01Z,michael,git,lua-api
a670de0f,683d6e5,fix/validate-config-posix-regex,2025-09-07T16:00:48Z,michael,git,lua-api a670de0f,683d6e5,fix/validate-config-posix-regex,2025-09-07T16:00:48Z,michael,git,lua-api
a670de0f,24bd94d,feature/systemd-hardening,2025-09-07T16:40:47Z,michael,git,lua-api a670de0f,24bd94d,feature/systemd-hardening,2025-09-07T16:40:47Z,michael,git,lua-api
4ee95dbc,08b49d3,security/sanitize-test-scripts,2025-09-07T19:25:38Z,michael,git,lua-api

0
scripts/cleanup_debug.sh Normal file → Executable file
View file

4
scripts/manual_mail_test.sh Normal file → Executable file
View file

@ -4,11 +4,11 @@
echo "Testing SMTP with corrected JSON..." echo "Testing SMTP with corrected JSON..."
# Simple test without timestamp embedding # Simple test without timestamp embedding
curl -X POST http://127.0.0.1:8080/v1/mail/send \ curl -X POST http://127.0.0.1:7811/v1/mail/send \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"name": "Furt Test User", "name": "Furt Test User",
"email": "michael@dragons-at-work.de", "email": "admin@example.com",
"subject": "Furt SMTP Test Success!", "subject": "Furt SMTP Test Success!",
"message": "This is a test email from Furt Lua HTTP-Server. SMTP Integration working!" "message": "This is a test email from Furt Lua HTTP-Server. SMTP Integration working!"
}' }'

View file

@ -1,80 +0,0 @@
#!/bin/bash
# Production Test für api.dragons-at-work.de
echo "Testing Production API via Apache Proxy"
echo "======================================="
# Test 1: HTTPS Health Check
echo ""
echo "[1] Testing HTTPS Health Check..."
https_health=$(curl -s https://api.dragons-at-work.de/health)
echo "HTTPS Response: $https_health"
if echo "$https_health" | grep -q "healthy"; then
echo "[OK] HTTPS Proxy working"
else
echo "[ERROR] HTTPS Proxy failed"
exit 1
fi
# Test 2: SMTP Status via HTTPS
echo ""
echo "[2] Testing SMTP Configuration via HTTPS..."
if echo "$https_health" | grep -q '"smtp_configured":true'; then
echo "[OK] SMTP configured and accessible via HTTPS"
else
echo "[ERROR] SMTP not configured or not accessible"
fi
# Test 3: CORS Headers
echo ""
echo "[3] Testing CORS Headers..."
cors_test=$(curl -s -I https://api.dragons-at-work.de/health | grep -i "access-control")
if [ -n "$cors_test" ]; then
echo "[OK] CORS headers present: $cors_test"
else
echo "[WARN] CORS headers missing - add to Apache config"
fi
# Test 4: Production Mail Test
echo ""
echo "[4] Testing Production Mail via HTTPS..."
echo "WARNING: This sends real email via production API"
read -p "Continue with production mail test? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Sending production test email..."
prod_mail_response=$(curl -s -X POST https://api.dragons-at-work.de/v1/mail/send \
-H "Content-Type: application/json" \
-d '{
"name": "Production Test",
"email": "test@dragons-at-work.de",
"subject": "Production API Test - Apache Proxy Success!",
"message": "This email was sent via the production API at api.dragons-at-work.de through Apache proxy to Furt-Lua backend. HTTPS integration working!"
}')
echo "Production Response: $prod_mail_response"
if echo "$prod_mail_response" | grep -q '"success":true'; then
echo "[OK] PRODUCTION MAIL SENT VIA HTTPS!"
echo "Check admin@dragons-at-work.de for delivery confirmation"
else
echo "[ERROR] Production mail failed"
fi
else
echo "Skipping production mail test"
fi
# Test 5: Security Headers
echo ""
echo "[5] Testing Security Headers..."
security_headers=$(curl -s -I https://api.dragons-at-work.de/health)
echo "Security Headers:"
echo "$security_headers" | grep -i "x-content-type-options\|x-frame-options\|strict-transport"
echo ""
echo "Production Test Complete!"
echo "========================"
echo "Next: Hugo integration with https://api.dragons-at-work.de/v1/mail/send"

View file

@ -1,101 +0,0 @@
#!/bin/bash
# furt-lua/scripts/setup_env.sh
# Add SMTP environment variables to existing .env (non-destructive)
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${GREEN}=== Furt SMTP Environment Setup ===${NC}"
# Navigate to furt project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
ENV_FILE="$PROJECT_ROOT/.env"
echo -e "${YELLOW}Project root:${NC} $PROJECT_ROOT"
echo -e "${YELLOW}Environment file:${NC} $ENV_FILE"
# Check if .env exists
if [ ! -f "$ENV_FILE" ]; then
echo -e "${YELLOW}Creating new .env file...${NC}"
cat > "$ENV_FILE" << 'EOF'
# Dragons@Work Project Environment Variables
# Furt SMTP Configuration for mail.dragons-at-work.de
SMTP_HOST="mail.dragons-at-work.de"
SMTP_PORT="465"
SMTP_USERNAME="your_email@dragons-at-work.de"
SMTP_PASSWORD="your_smtp_password"
SMTP_FROM="noreply@dragons-at-work.de"
SMTP_TO="michael@dragons-at-work.de"
EOF
echo -e "${GREEN}[OK] Created new .env file${NC}"
echo -e "${YELLOW}[EDIT] Please edit:${NC} nano $ENV_FILE"
exit 0
fi
echo -e "${GREEN}[OK] Found existing .env file${NC}"
# Check if SMTP variables already exist
smtp_username_exists=$(grep -c "^SMTP_USERNAME=" "$ENV_FILE" 2>/dev/null || echo "0")
smtp_password_exists=$(grep -c "^SMTP_PASSWORD=" "$ENV_FILE" 2>/dev/null || echo "0")
if [ "$smtp_username_exists" -gt 0 ] && [ "$smtp_password_exists" -gt 0 ]; then
echo -e "${GREEN}[OK] SMTP variables already configured${NC}"
# Load and show current values
source "$ENV_FILE"
echo -e "${YELLOW}Current SMTP User:${NC} ${SMTP_USERNAME:-NOT_SET}"
echo -e "${YELLOW}Current SMTP Password:${NC} ${SMTP_PASSWORD:+[CONFIGURED]}${SMTP_PASSWORD:-NOT_SET}"
echo ""
echo -e "${YELLOW}To update SMTP settings:${NC} nano $ENV_FILE"
exit 0
fi
# Add missing SMTP variables
echo -e "${YELLOW}Adding SMTP configuration to existing .env...${NC}"
# Add section header if not present
if ! grep -q "SMTP_" "$ENV_FILE" 2>/dev/null; then
echo "" >> "$ENV_FILE"
echo "# Furt SMTP Configuration for mail.dragons-at-work.de" >> "$ENV_FILE"
fi
# Add username if missing
if [ "$smtp_username_exists" -eq 0 ]; then
echo "SMTP_HOST=\"mail.dragons-at-work.de\"" >> "$ENV_FILE"
echo "SMTP_PORT=\"465\"" >> "$ENV_FILE"
echo "SMTP_USERNAME=\"your_email@dragons-at-work.de\"" >> "$ENV_FILE"
echo -e "${GREEN}[OK] Added SMTP_HOST, SMTP_PORT, SMTP_USERNAME${NC}"
fi
# Add password if missing
if [ "$smtp_password_exists" -eq 0 ]; then
echo "SMTP_PASSWORD=\"your_smtp_password\"" >> "$ENV_FILE"
echo "SMTP_FROM=\"noreply@dragons-at-work.de\"" >> "$ENV_FILE"
echo "SMTP_TO=\"michael@dragons-at-work.de\"" >> "$ENV_FILE"
echo -e "${GREEN}[OK] Added SMTP_PASSWORD, SMTP_FROM, SMTP_TO${NC}"
fi
echo -e "${GREEN}[OK] SMTP configuration added to .env${NC}"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Edit SMTP credentials: nano $ENV_FILE"
echo "2. Set your actual email@dragons-at-work.de in SMTP_USERNAME"
echo "3. Set your actual SMTP password in SMTP_PASSWORD"
echo "4. Test with: ./scripts/start.sh"
echo ""
echo -e "${YELLOW}Current .env content:${NC}"
echo "==================="
cat "$ENV_FILE"
echo "==================="
echo ""
echo -e "${GREEN}Ready for SMTP testing!${NC}"

View file

@ -4,7 +4,7 @@
BASE_URL="http://127.0.0.1:8080" BASE_URL="http://127.0.0.1:8080"
# Use correct API keys that match current .env # Use correct API keys that match current .env
API_KEY="hugo-dev-key-change-in-production" API_KEY="YOUR_API_KEY_HERE"
echo "⚡ Furt API Stress Test" echo "⚡ Furt API Stress Test"
echo "======================" echo "======================"
@ -20,9 +20,9 @@ for i in {1..20}; do
response=$(curl -s -w "%{http_code}" \ response=$(curl -s -w "%{http_code}" \
-H "X-API-Key: $API_KEY" \ -H "X-API-Key: $API_KEY" \
"$BASE_URL/v1/auth/status") "$BASE_URL/v1/auth/status")
status=$(echo "$response" | tail -c 4) status=$(echo "$response" | tail -c 4)
if [ "$status" == "200" ]; then if [ "$status" == "200" ]; then
rate_limit_remaining=$(echo "$response" | head -n -1 | jq -r '.rate_limit_remaining // "N/A"' 2>/dev/null) rate_limit_remaining=$(echo "$response" | head -n -1 | jq -r '.rate_limit_remaining // "N/A"' 2>/dev/null)
echo "Request $i: ✅ 200 OK (Rate limit remaining: $rate_limit_remaining)" echo "Request $i: ✅ 200 OK (Rate limit remaining: $rate_limit_remaining)"
@ -33,7 +33,7 @@ for i in {1..20}; do
else else
echo "Request $i: ❌ $status Error" echo "Request $i: ❌ $status Error"
fi fi
# Small delay to prevent overwhelming # Small delay to prevent overwhelming
sleep 0.1 sleep 0.1
done done
@ -58,10 +58,10 @@ for i in {1..10}; do
-H "X-API-Key: $API_KEY" \ -H "X-API-Key: $API_KEY" \
"$BASE_URL/health") "$BASE_URL/health")
local_end=$(date +%s.%N) local_end=$(date +%s.%N)
status=$(echo "$response" | tail -c 4) status=$(echo "$response" | tail -c 4)
duration=$(echo "$local_end - $local_start" | bc -l) duration=$(echo "$local_end - $local_start" | bc -l)
echo "Concurrent $i: Status $status, Duration ${duration}s" > "$temp_dir/result_$i" echo "Concurrent $i: Status $status, Duration ${duration}s" > "$temp_dir/result_$i"
} & } &
done done
@ -85,18 +85,18 @@ mail_errors=0
for i in {1..5}; do for i in {1..5}; do
start_time=$(date +%s.%N) start_time=$(date +%s.%N)
response=$(curl -s -w "%{http_code}" \ response=$(curl -s -w "%{http_code}" \
-H "X-API-Key: $API_KEY" \ -H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"name\":\"Stress Test $i\",\"email\":\"test$i@example.com\",\"subject\":\"Performance Test\",\"message\":\"Load test message $i\"}" \ -d "{\"name\":\"Stress Test $i\",\"email\":\"test$i@example.com\",\"subject\":\"Performance Test\",\"message\":\"Load test message $i\"}" \
"$BASE_URL/v1/mail/send") "$BASE_URL/v1/mail/send")
end_time=$(date +%s.%N) end_time=$(date +%s.%N)
duration=$(echo "$end_time - $start_time" | bc -l) duration=$(echo "$end_time - $start_time" | bc -l)
status=$(echo "$response" | tail -c 4) status=$(echo "$response" | tail -c 4)
if [ "$status" == "200" ]; then if [ "$status" == "200" ]; then
echo "Mail $i: ✅ 200 OK (${duration}s)" echo "Mail $i: ✅ 200 OK (${duration}s)"
((mail_success++)) ((mail_success++))
@ -104,7 +104,7 @@ for i in {1..5}; do
echo "Mail $i: ❌ Status $status (${duration}s)" echo "Mail $i: ❌ Status $status (${duration}s)"
((mail_errors++)) ((mail_errors++))
fi fi
# Delay between mail sends to be nice to SMTP server # Delay between mail sends to be nice to SMTP server
sleep 1 sleep 1
done done
@ -120,7 +120,7 @@ mixed_success=0
for i in {1..15}; do for i in {1..15}; do
((mixed_total++)) ((mixed_total++))
if [ $((i % 3)) -eq 0 ]; then if [ $((i % 3)) -eq 0 ]; then
# Every 3rd request: auth status # Every 3rd request: auth status
endpoint="/v1/auth/status" endpoint="/v1/auth/status"
@ -128,20 +128,20 @@ for i in {1..15}; do
# Other requests: health check # Other requests: health check
endpoint="/health" endpoint="/health"
fi fi
response=$(curl -s -w "%{http_code}" \ response=$(curl -s -w "%{http_code}" \
-H "X-API-Key: $API_KEY" \ -H "X-API-Key: $API_KEY" \
"$BASE_URL$endpoint") "$BASE_URL$endpoint")
status=$(echo "$response" | tail -c 4) status=$(echo "$response" | tail -c 4)
if [ "$status" == "200" ]; then if [ "$status" == "200" ]; then
echo "Mixed $i ($endpoint): ✅ 200 OK" echo "Mixed $i ($endpoint): ✅ 200 OK"
((mixed_success++)) ((mixed_success++))
else else
echo "Mixed $i ($endpoint): ❌ $status" echo "Mixed $i ($endpoint): ❌ $status"
fi fi
sleep 0.2 sleep 0.2
done done

View file

@ -3,8 +3,8 @@
# Test API-Key-Authentifizierung (ohne jq parse errors) # Test API-Key-Authentifizierung (ohne jq parse errors)
BASE_URL="http://127.0.0.1:8080" BASE_URL="http://127.0.0.1:8080"
HUGO_API_KEY="hugo-dev-key-change-in-production" HUGO_API_KEY="YOUR_API_KEY_HERE"
ADMIN_API_KEY="admin-dev-key-change-in-production" ADMIN_API_KEY="YOUR_ADMIN_KEY_HERE"
INVALID_API_KEY="invalid-key-should-fail" INVALID_API_KEY="invalid-key-should-fail"
echo "🔐 Testing Furt API-Key Authentication" echo "🔐 Testing Furt API-Key Authentication"
@ -16,24 +16,24 @@ make_request() {
local url="$2" local url="$2"
local headers="$3" local headers="$3"
local data="$4" local data="$4"
echo "Request: $method $url" echo "Request: $method $url"
if [ -n "$headers" ]; then if [ -n "$headers" ]; then
echo "Headers: $headers" echo "Headers: $headers"
fi fi
local response=$(curl -s $method \ local response=$(curl -s $method \
${headers:+-H "$headers"} \ ${headers:+-H "$headers"} \
${data:+-d "$data"} \ ${data:+-d "$data"} \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
"$url") "$url")
local status=$(curl -s -o /dev/null -w "%{http_code}" $method \ local status=$(curl -s -o /dev/null -w "%{http_code}" $method \
${headers:+-H "$headers"} \ ${headers:+-H "$headers"} \
${data:+-d "$data"} \ ${data:+-d "$data"} \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
"$url") "$url")
echo "Status: $status" echo "Status: $status"
echo "Response: $response" | jq '.' 2>/dev/null || echo "$response" echo "Response: $response" | jq '.' 2>/dev/null || echo "$response"
echo "" echo ""

2
scripts/test_modular.sh Normal file → Executable file
View file

@ -3,7 +3,7 @@
# Test der modularen Furt-Architektur # Test der modularen Furt-Architektur
BASE_URL="http://127.0.0.1:8080" BASE_URL="http://127.0.0.1:8080"
HUGO_API_KEY="hugo-dev-key-change-in-production" HUGO_API_KEY="YOUR_API_KEY_HERE"
echo "🧩 Testing Modular Furt Architecture" echo "🧩 Testing Modular Furt Architecture"
echo "====================================" echo "===================================="

20
scripts/test_smtp.sh Normal file → Executable file
View file

@ -36,7 +36,7 @@ else
echo "[ERROR] Validation failed" echo "[ERROR] Validation failed"
fi fi
# Test 3: Invalid Email Format # Test 3: Invalid Email Format
echo "" echo ""
echo "[3] Testing email validation..." echo "[3] Testing email validation..."
email_validation_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \ email_validation_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \
@ -54,36 +54,36 @@ fi
# Test 4: Valid Mail Request (REAL SMTP TEST) # Test 4: Valid Mail Request (REAL SMTP TEST)
echo "" echo ""
echo "[4] Testing REAL mail sending..." echo "[4] Testing REAL mail sending..."
echo "WARNING: This will send a real email to michael@dragons-at-work.de" echo "WARNING: This will send a real email to admin@example.com"
read -p "Continue with real mail test? (y/N): " -n 1 -r read -p "Continue with real mail test? (y/N): " -n 1 -r
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Sending real test email..." echo "Sending real test email..."
mail_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \ mail_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
"name": "Furt Test User", "name": "Furt Test User",
"email": "test@dragons-at-work.de", "email": "test@example.com",
"subject": "Furt SMTP Test - Week 2 Success!", "subject": "Furt SMTP Test - Week 2 Success!",
"message": "This is a test email from the Furt Lua HTTP-Server.\n\nSMTP Integration is working!\n\nTimestamp: '$(date)'\nServer: furt-lua v1.0" "message": "This is a test email from the Furt Lua HTTP-Server.\n\nSMTP Integration is working!\n\nTimestamp: '$(date)'\nServer: furt-lua v1.0"
}') }')
echo "Response: $mail_response" echo "Response: $mail_response"
# Check for success # Check for success
if echo "$mail_response" | grep -q '"success":true'; then if echo "$mail_response" | grep -q '"success":true'; then
echo "[OK] MAIL SENT SUCCESSFULLY!" echo "[OK] MAIL SENT SUCCESSFULLY!"
echo "Check michael@dragons-at-work.de inbox" echo "Check admin@example.com inbox"
# Extract request ID # Extract request ID
request_id=$(echo "$mail_response" | grep -o '"request_id":"[^"]*"' | cut -d'"' -f4) request_id=$(echo "$mail_response" | grep -o '"request_id":"[^"]*"' | cut -d'"' -f4)
echo "Request ID: $request_id" echo "Request ID: $request_id"
else else
echo "[ERROR] Mail sending failed" echo "[ERROR] Mail sending failed"
echo "Check server logs and SMTP credentials" echo "Check server logs and SMTP credentials"
# Show error details # Show error details
if echo "$mail_response" | grep -q "error"; then if echo "$mail_response" | grep -q "error"; then
error_msg=$(echo "$mail_response" | grep -o '"error":"[^"]*"' | cut -d'"' -f4) error_msg=$(echo "$mail_response" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
@ -126,7 +126,7 @@ echo "Performance: ${duration_ms}ms"
echo "" echo ""
echo "Week 2 Challenge Status:" echo "Week 2 Challenge Status:"
echo " SMTP Integration: COMPLETE" echo " SMTP Integration: COMPLETE"
echo " Environment Variables: CHECK .env" echo " Environment Variables: CHECK .env"
echo " Native Lua Implementation: DONE" echo " Native Lua Implementation: DONE"
echo " Production Ready: READY FOR TESTING" echo " Production Ready: READY FOR TESTING"

View file

@ -91,9 +91,7 @@ end
function FurtServer:add_cors_headers(request) function FurtServer:add_cors_headers(request)
local allowed_origins = config.cors and config.cors.allowed_origins or { local allowed_origins = config.cors and config.cors.allowed_origins or {
"http://localhost:1313", "http://localhost:1313",
"http://127.0.0.1:1313", "http://127.0.0.1:1313"
"https://dragons-at-work.de",
"https://www.dragons-at-work.de"
} }
-- Check if request has Origin header -- Check if request has Origin header

View file

@ -19,7 +19,7 @@ function SSLCompat:detect_ssl_library()
return "luaossl", ssl_lib return "luaossl", ssl_lib
end end
end end
-- Try luasec -- Try luasec
local success, ssl_lib = pcall(require, "ssl") local success, ssl_lib = pcall(require, "ssl")
if success and ssl_lib then if success and ssl_lib then
@ -28,23 +28,23 @@ function SSLCompat:detect_ssl_library()
return "luasec", ssl_lib return "luasec", ssl_lib
end end
end end
return nil, "No compatible SSL library found (tried luaossl, luasec)" return nil, "No compatible SSL library found (tried luaossl, luasec)"
end end
function SSLCompat:wrap_socket(sock, options) function SSLCompat:wrap_socket(sock, options)
local ssl_type, ssl_lib = self:detect_ssl_library() local ssl_type, ssl_lib = self:detect_ssl_library()
if not ssl_type then if not ssl_type then
return nil, ssl_lib -- ssl_lib contains error message return nil, ssl_lib -- ssl_lib contains error message
end end
if ssl_type == "luaossl" then if ssl_type == "luaossl" then
return self:wrap_luaossl(sock, options, ssl_lib) return self:wrap_luaossl(sock, options, ssl_lib)
elseif ssl_type == "luasec" then elseif ssl_type == "luasec" then
return self:wrap_luasec(sock, options, ssl_lib) return self:wrap_luasec(sock, options, ssl_lib)
end end
return nil, "Unknown SSL library type: " .. ssl_type return nil, "Unknown SSL library type: " .. ssl_type
end end
@ -55,18 +55,18 @@ function SSLCompat:wrap_luaossl(sock, options, ssl_lib)
protocol = "tlsv1_2", protocol = "tlsv1_2",
verify = "none" -- For self-signed certs verify = "none" -- For self-signed certs
}) })
if not ssl_sock then if not ssl_sock then
return nil, "luaossl wrap failed: " .. (err or "unknown error") return nil, "luaossl wrap failed: " .. (err or "unknown error")
end end
-- luaossl typically does handshake automatically, but explicit is safer -- luaossl typically does handshake automatically, but explicit is safer
local success, err = pcall(function() return ssl_sock:dohandshake() end) local success, err = pcall(function() return ssl_sock:dohandshake() end)
if not success then if not success then
-- Some luaossl versions don't need explicit handshake -- Some luaossl versions don't need explicit handshake
-- Continue if dohandshake doesn't exist -- Continue if dohandshake doesn't exist
end end
return ssl_sock, nil return ssl_sock, nil
end end
@ -78,28 +78,28 @@ function SSLCompat:wrap_luasec(sock, options, ssl_lib)
verify = "none", verify = "none",
options = "all" options = "all"
}) })
if not ssl_sock then if not ssl_sock then
return nil, "luasec wrap failed: " .. (err or "unknown error") return nil, "luasec wrap failed: " .. (err or "unknown error")
end end
-- luasec requires explicit handshake -- luasec requires explicit handshake
local success, err = ssl_sock:dohandshake() local success, err = ssl_sock:dohandshake()
if not success then if not success then
return nil, "luasec handshake failed: " .. (err or "unknown error") return nil, "luasec handshake failed: " .. (err or "unknown error")
end end
return ssl_sock, nil return ssl_sock, nil
end end
-- Create SMTP instance -- Create SMTP instance
function SMTP:new(config) function SMTP:new(config)
local instance = { local instance = {
server = config.smtp_server or "mail.dragons-at-work.de", server = config.smtp_server,
port = config.smtp_port or 465, port = config.smtp_port,
username = config.username, username = config.username,
password = config.password, password = config.password,
from_address = config.from_address or "noreply@dragons-at-work.de", from_address = config.from_address,
use_ssl = config.use_ssl or true, use_ssl = config.use_ssl or true,
debug = config.debug or false, debug = config.debug or false,
ssl_compat = SSLCompat ssl_compat = SSLCompat
@ -133,7 +133,7 @@ function SMTP:send_command(sock, command, expected_code)
if self.debug then if self.debug then
print("SMTP CMD: " .. (command or ""):gsub("\r\n", "\\r\\n")) print("SMTP CMD: " .. (command or ""):gsub("\r\n", "\\r\\n"))
end end
-- Only send if command is not nil (for server greeting, command is nil) -- Only send if command is not nil (for server greeting, command is nil)
if command then if command then
local success, err = sock:send(command .. "\r\n") local success, err = sock:send(command .. "\r\n")
@ -141,16 +141,16 @@ function SMTP:send_command(sock, command, expected_code)
return false, "Failed to send command: " .. (err or "unknown error") return false, "Failed to send command: " .. (err or "unknown error")
end end
end end
local response, err = sock:receive() local response, err = sock:receive()
if not response then if not response then
return false, "Failed to receive response: " .. (err or "unknown error") return false, "Failed to receive response: " .. (err or "unknown error")
end end
if self.debug then if self.debug then
print("SMTP RSP: " .. response) print("SMTP RSP: " .. response)
end end
-- Handle multi-line responses (like EHLO) -- Handle multi-line responses (like EHLO)
local full_response = response local full_response = response
while response:match("^%d%d%d%-") do while response:match("^%d%d%d%-") do
@ -163,12 +163,12 @@ function SMTP:send_command(sock, command, expected_code)
end end
full_response = full_response .. "\n" .. response full_response = full_response .. "\n" .. response
end end
local code = response:match("^(%d+)") local code = response:match("^(%d+)")
if expected_code and code ~= tostring(expected_code) then if expected_code and code ~= tostring(expected_code) then
return false, "Unexpected response: " .. full_response return false, "Unexpected response: " .. full_response
end end
return true, full_response return true, full_response
end end
@ -179,38 +179,38 @@ function SMTP:connect()
if not sock then if not sock then
return false, "Failed to create socket: " .. (err or "unknown error") return false, "Failed to create socket: " .. (err or "unknown error")
end end
-- Set timeout -- Set timeout
sock:settimeout(30) sock:settimeout(30)
-- Connect to server -- Connect to server
local success, err = sock:connect(self.server, self.port) local success, err = sock:connect(self.server, self.port)
if not success then if not success then
return false, "Failed to connect to " .. self.server .. ":" .. self.port .. " - " .. (err or "unknown error") return false, "Failed to connect to " .. self.server .. ":" .. self.port .. " - " .. (err or "unknown error")
end end
-- Wrap with SSL for port 465 using compatibility layer -- Wrap with SSL for port 465 using compatibility layer
if self.use_ssl and self.port == 465 then if self.use_ssl and self.port == 465 then
local ssl_sock, err = self.ssl_compat:wrap_socket(sock, { local ssl_sock, err = self.ssl_compat:wrap_socket(sock, {
mode = "client", mode = "client",
protocol = "tlsv1_2" protocol = "tlsv1_2"
}) })
if not ssl_sock then if not ssl_sock then
sock:close() sock:close()
return false, "Failed to establish SSL connection: " .. (err or "unknown error") return false, "Failed to establish SSL connection: " .. (err or "unknown error")
end end
sock = ssl_sock sock = ssl_sock
end end
-- Read server greeting -- Read server greeting
local success, response = self:send_command(sock, nil, 220) local success, response = self:send_command(sock, nil, 220)
if not success then if not success then
sock:close() sock:close()
return false, "SMTP server greeting failed: " .. response return false, "SMTP server greeting failed: " .. response
end end
return sock, nil return sock, nil
end end
@ -219,64 +219,64 @@ function SMTP:send_email(to_address, subject, message, from_name)
if not self.username or not self.password then if not self.username or not self.password then
return false, "SMTP username or password not configured" return false, "SMTP username or password not configured"
end end
-- Connect to server -- Connect to server
local sock, err = self:connect() local sock, err = self:connect()
if not sock then if not sock then
return false, err return false, err
end end
local function cleanup_and_fail(error_msg) local function cleanup_and_fail(error_msg)
sock:close() sock:close()
return false, error_msg return false, error_msg
end end
-- EHLO command -- EHLO command
local success, response = self:send_command(sock, "EHLO furt-lua", 250) local success, response = self:send_command(sock, "EHLO furt-lua", 250)
if not success then if not success then
return cleanup_and_fail("EHLO failed: " .. response) return cleanup_and_fail("EHLO failed: " .. response)
end end
-- AUTH LOGIN -- AUTH LOGIN
local success, response = self:send_command(sock, "AUTH LOGIN", 334) local success, response = self:send_command(sock, "AUTH LOGIN", 334)
if not success then if not success then
return cleanup_and_fail("AUTH LOGIN failed: " .. response) return cleanup_and_fail("AUTH LOGIN failed: " .. response)
end end
-- Send username (base64 encoded) -- Send username (base64 encoded)
local username_b64 = self:base64_encode(self.username) local username_b64 = self:base64_encode(self.username)
local success, response = self:send_command(sock, username_b64, 334) local success, response = self:send_command(sock, username_b64, 334)
if not success then if not success then
return cleanup_and_fail("Username authentication failed: " .. response) return cleanup_and_fail("Username authentication failed: " .. response)
end end
-- Send password (base64 encoded) -- Send password (base64 encoded)
local password_b64 = self:base64_encode(self.password) local password_b64 = self:base64_encode(self.password)
local success, response = self:send_command(sock, password_b64, 235) local success, response = self:send_command(sock, password_b64, 235)
if not success then if not success then
return cleanup_and_fail("Password authentication failed: " .. response) return cleanup_and_fail("Password authentication failed: " .. response)
end end
-- MAIL FROM -- MAIL FROM
local mail_from = "MAIL FROM:<" .. self.from_address .. ">" local mail_from = "MAIL FROM:<" .. self.from_address .. ">"
local success, response = self:send_command(sock, mail_from, 250) local success, response = self:send_command(sock, mail_from, 250)
if not success then if not success then
return cleanup_and_fail("MAIL FROM failed: " .. response) return cleanup_and_fail("MAIL FROM failed: " .. response)
end end
-- RCPT TO -- RCPT TO
local rcpt_to = "RCPT TO:<" .. to_address .. ">" local rcpt_to = "RCPT TO:<" .. to_address .. ">"
local success, response = self:send_command(sock, rcpt_to, 250) local success, response = self:send_command(sock, rcpt_to, 250)
if not success then if not success then
return cleanup_and_fail("RCPT TO failed: " .. response) return cleanup_and_fail("RCPT TO failed: " .. response)
end end
-- DATA command -- DATA command
local success, response = self:send_command(sock, "DATA", 354) local success, response = self:send_command(sock, "DATA", 354)
if not success then if not success then
return cleanup_and_fail("DATA command failed: " .. response) return cleanup_and_fail("DATA command failed: " .. response)
end end
-- Build email message -- Build email message
local display_name = from_name or "Furt Contact Form" local display_name = from_name or "Furt Contact Form"
local email_content = string.format( local email_content = string.format(
@ -295,17 +295,17 @@ function SMTP:send_email(to_address, subject, message, from_name)
os.date("%a, %d %b %Y %H:%M:%S %z"), os.date("%a, %d %b %Y %H:%M:%S %z"),
message message
) )
-- Send email content -- Send email content
local success, response = self:send_command(sock, email_content, 250) local success, response = self:send_command(sock, email_content, 250)
if not success then if not success then
return cleanup_and_fail("Email sending failed: " .. response) return cleanup_and_fail("Email sending failed: " .. response)
end end
-- QUIT -- QUIT
self:send_command(sock, "QUIT", 221) self:send_command(sock, "QUIT", 221)
sock:close() sock:close()
return true, "Email sent successfully" return true, "Email sent successfully"
end end