diff --git a/.env.example b/.env.example index fb5827c..ed9fe84 100644 --- a/.env.example +++ b/.env.example @@ -18,9 +18,12 @@ SAGJAN_PORT=8082 # SMTP-Konfiguration (für formular2mail) SMTP_HOST=localhost SMTP_PORT=25 -SMTP_FROM=no-reply@dragons-at-work.de -SMTP_TO=admin@dragons-at-work.de +SMTP_USERNAME=noreply@example.com +SMTP_PASSWORD=secret-password +SMTP_FROM=noreply@example.com +SMTP_TO=admin@example.com # API-Schlüssel (generiere sichere Schlüssel für Produktion!) HUGO_API_KEY=change-me-in-production ADMIN_API_KEY=change-me-in-production + diff --git a/furt-lua/config/server.lua b/furt-lua/config/server.lua index acfd8c8..a2916ff 100644 --- a/furt-lua/config/server.lua +++ b/furt-lua/config/server.lua @@ -24,13 +24,13 @@ return { -- Mail configuration (for SMTP integration) mail = { - smtp_server = "mail.dragons-at-work.de", - smtp_port = 465, + smtp_server = os.getenv("SMTP_HOST") or "mail.dragons-at-work.de", + smtp_port = tonumber(os.getenv("SMTP_PORT")) or 465, use_ssl = true, - username = os.getenv("FURT_MAIL_USERNAME"), - password = os.getenv("FURT_MAIL_PASSWORD"), - from_address = "noreply@dragons-at-work.de", - to_address = "michael@dragons-at-work.de" + username = os.getenv("SMTP_USERNAME"), + password = os.getenv("SMTP_PASSWORD"), + from_address = os.getenv("SMTP_FROM") or "noreply@dragons-at-work.de", + to_address = os.getenv("SMTP_TO") or "michael@dragons-at-work.de" } } diff --git a/furt-lua/scripts/setup_env.sh b/furt-lua/scripts/setup_env.sh new file mode 100755 index 0000000..858436e --- /dev/null +++ b/furt-lua/scripts/setup_env.sh @@ -0,0 +1,101 @@ +#!/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}" + diff --git a/furt-lua/scripts/start.sh b/furt-lua/scripts/start.sh index 07ee37d..a8af870 100755 --- a/furt-lua/scripts/start.sh +++ b/furt-lua/scripts/start.sh @@ -57,13 +57,21 @@ lua -e "require('ssl')" 2>/dev/null && { echo -e "${YELLOW}○${NC} lua-ssl not found (install with: luarocks install --local luaossl)" } -# Set environment variables for mail (if not set) -if [ -z "$FURT_MAIL_USERNAME" ]; then - echo -e "${YELLOW}Warning: FURT_MAIL_USERNAME not set${NC}" +# Load environment variables from project root +echo -e "${YELLOW}Loading environment variables...${NC}" +if [ -f "../.env" ]; then + echo -e "${GREEN}[OK]${NC} Loading from ../.env" + export $(grep -v '^#' ../.env | grep -v '^$' | xargs) +else + echo -e "${YELLOW}[WARN]${NC} No .env file found in project root" fi -if [ -z "$FURT_MAIL_PASSWORD" ]; then - echo -e "${YELLOW}Warning: FURT_MAIL_PASSWORD not set${NC}" +# Check SMTP configuration (korrekte Variable-Namen) +if [ -n "$SMTP_USERNAME" ] && [ -n "$SMTP_PASSWORD" ]; then + echo -e "${GREEN}[OK]${NC} SMTP configured: $SMTP_USERNAME" +else + echo -e "${YELLOW}[WARN]${NC} SMTP credentials missing in .env" + echo "Add SMTP_USERNAME and SMTP_PASSWORD to .env" fi # Change to project directory diff --git a/furt-lua/scripts/test_smtp.sh b/furt-lua/scripts/test_smtp.sh new file mode 100644 index 0000000..c014a52 --- /dev/null +++ b/furt-lua/scripts/test_smtp.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# furt-lua/scripts/test_smtp.sh +# Test SMTP mail functionality + +SERVER_URL="http://127.0.0.1:8080" + +echo "Testing Furt SMTP Mail Functionality" +echo "========================================" + +# Test 1: Server Health Check +echo "" +echo "[1] Testing Health Check..." +health_response=$(curl -s "$SERVER_URL/health") +echo "Response: $health_response" + +# Check if server is responding +if echo "$health_response" | grep -q "healthy"; then + echo "[OK] Server is healthy" +else + echo "[ERROR] Server not responding or unhealthy" + exit 1 +fi + +# Test 2: Invalid Mail Request (missing fields) +echo "" +echo "[2] Testing validation (missing fields)..." +invalid_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \ + -H "Content-Type: application/json" \ + -d '{"name":"Test"}') +echo "Response: $invalid_response" + +# Check for validation error +if echo "$invalid_response" | grep -q "Missing required fields"; then + echo "[OK] Validation working correctly" +else + echo "[ERROR] Validation failed" +fi + +# Test 3: Invalid Email Format +echo "" +echo "[3] Testing email validation..." +email_validation_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \ + -H "Content-Type: application/json" \ + -d '{"name":"Test","email":"invalid-email","message":"Test"}') +echo "Response: $email_validation_response" + +# Check for email validation error +if echo "$email_validation_response" | grep -q "error"; then + echo "[OK] Email validation working" +else + echo "[ERROR] Email validation failed" +fi + +# Test 4: Valid Mail Request (REAL SMTP TEST) +echo "" +echo "[4] Testing REAL mail sending..." +echo "WARNING: This will send a real email to michael@dragons-at-work.de" +read -p "Continue with real mail test? (y/N): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Sending real test email..." + + mail_response=$(curl -s -X POST "$SERVER_URL/v1/mail/send" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Furt Test User", + "email": "test@dragons-at-work.de", + "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" + }') + + echo "Response: $mail_response" + + # Check for success + if echo "$mail_response" | grep -q '"success":true'; then + echo "[OK] MAIL SENT SUCCESSFULLY!" + echo "Check michael@dragons-at-work.de inbox" + + # Extract request ID + request_id=$(echo "$mail_response" | grep -o '"request_id":"[^"]*"' | cut -d'"' -f4) + echo "Request ID: $request_id" + else + echo "[ERROR] Mail sending failed" + echo "Check server logs and SMTP credentials" + + # Show error details + if echo "$mail_response" | grep -q "error"; then + error_msg=$(echo "$mail_response" | grep -o '"error":"[^"]*"' | cut -d'"' -f4) + echo "Error: $error_msg" + fi + fi +else + echo "Skipping real mail test" +fi + +# Test 5: Performance Test +echo "" +echo "[5] Testing response time..." +start_time=$(date +%s%N) +perf_response=$(curl -s "$SERVER_URL/health") +end_time=$(date +%s%N) + +duration_ms=$(( (end_time - start_time) / 1000000 )) +echo "Response time: ${duration_ms}ms" + +if [ $duration_ms -lt 100 ]; then + echo "[OK] Response time excellent (< 100ms)" +elif [ $duration_ms -lt 500 ]; then + echo "[OK] Response time good (< 500ms)" +else + echo "[WARN] Response time slow (> 500ms)" +fi + +echo "" +echo "SMTP Test Complete!" +echo "====================" +echo "[OK] Health check working" +echo "[OK] Input validation working" +echo "[OK] Email format validation working" +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Real mail test executed" +fi +echo "Performance: ${duration_ms}ms" + +echo "" +echo "Week 2 Challenge Status:" +echo " SMTP Integration: COMPLETE" +echo " Environment Variables: CHECK .env" +echo " Native Lua Implementation: DONE" +echo " Production Ready: READY FOR TESTING" + diff --git a/furt-lua/src/main.lua b/furt-lua/src/main.lua index 45160e5..d46e20d 100644 --- a/furt-lua/src/main.lua +++ b/furt-lua/src/main.lua @@ -181,7 +181,8 @@ server:add_route("GET", "/health", function(request) status = "healthy", service = "furt-lua", version = "1.0.0", - timestamp = os.time() + timestamp = os.time(), + smtp_configured = config.mail and config.mail.username ~= nil } return server:create_response(200, response_data) end) @@ -223,16 +224,24 @@ server:add_route("POST", "/v1/mail/send", function(request) }) end - -- TODO: Implement actual mail sending via SMTP - print("Mail request received: " .. data.name .. " <" .. data.email .. ">") - - local response_data = { - success = true, - message = "Mail queued for sending", - request_id = os.time() .. "-" .. math.random(1000, 9999) - } - - return server:create_response(200, response_data) + -- Send email via SMTP + local SMTP = require("src.smtp") + local smtp_client = SMTP:new(config.mail) + + local request_id = os.time() .. "-" .. math.random(1000, 9999) + local subject = data.subject or "Contact Form Message" + local email_content = string.format("From: %s <%s>\nSubject: %s\n\n%s", + data.name, data.email, subject, data.message) + + local success, result = smtp_client:send_email( + config.mail.to_address, subject, email_content, data.name) + + if success then + return server:create_response(200, {success = true, message = "Mail sent", request_id = request_id}) + else + return server:create_response(500, {success = false, error = result, request_id = request_id}) + end + end) -- Start server diff --git a/furt-lua/src/smtp.lua b/furt-lua/src/smtp.lua new file mode 100644 index 0000000..75e5e86 --- /dev/null +++ b/furt-lua/src/smtp.lua @@ -0,0 +1,233 @@ +-- furt-lua/src/smtp.lua +-- Native SMTP implementation using lua-socket + lua-ssl +-- Dragons@Work Digital Sovereignty Project + +local socket = require("socket") +local ssl = require("ssl") + +local SMTP = {} + +function SMTP:new(config) + local instance = { + server = config.smtp_server or "mail.dragons-at-work.de", + port = config.smtp_port or 465, + username = config.username, + password = config.password, + from_address = config.from_address or "noreply@dragons-at-work.de", + use_ssl = config.use_ssl or true, + debug = false + } + setmetatable(instance, self) + self.__index = self + return instance +end + +-- Base64 encoding for SMTP AUTH +function SMTP:base64_encode(str) + local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + return ((str:gsub('.', function(x) + local r, b = '', x:byte() + for i = 8, 1, -1 do + r = r .. (b % 2^i - b % 2^(i-1) > 0 and '1' or '0') + end + return r; + end) .. '0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c = 0 + for i = 1, 6 do + c = c + (x:sub(i,i) == '1' and 2^(6-i) or 0) + end + return b:sub(c+1,c+1) + end) .. ({ '', '==', '=' })[#str % 3 + 1]) +end + +-- Send SMTP command and read response +function SMTP:send_command(sock, command, expected_code) + if self.debug then + print("SMTP CMD: " .. (command or ""):gsub("\r\n", "\\r\\n")) + end + + -- Only send if command is not nil (for server greeting, command is nil) + if command then + local success, err = sock:send(command .. "\r\n") + if not success then + return false, "Failed to send command: " .. (err or "unknown error") + end + end + + local response, err = sock:receive() + if not response then + return false, "Failed to receive response: " .. (err or "unknown error") + end + + if self.debug then + print("SMTP RSP: " .. response) + end + + -- Handle multi-line responses (like EHLO) + local full_response = response + while response:match("^%d%d%d%-") do + response, err = sock:receive() + if not response then + return false, "Failed to receive multi-line response: " .. (err or "unknown error") + end + if self.debug then + print("SMTP RSP: " .. response) + end + full_response = full_response .. "\n" .. response + end + + local code = response:match("^(%d+)") + if expected_code and code ~= tostring(expected_code) then + return false, "Unexpected response: " .. full_response + end + + return true, full_response +end + +-- Connect to SMTP server +function SMTP:connect() + -- Create socket + local sock, err = socket.tcp() + if not sock then + return false, "Failed to create socket: " .. (err or "unknown error") + end + + -- Set timeout + sock:settimeout(30) + + -- Connect to server + local success, err = sock:connect(self.server, self.port) + if not success then + return false, "Failed to connect to " .. self.server .. ":" .. self.port .. " - " .. (err or "unknown error") + end + + -- Wrap with SSL for port 465 + if self.use_ssl and self.port == 465 then + local ssl_sock, err = ssl.wrap(sock, { + mode = "client", + protocol = "tlsv1_2", + verify = "none" -- For self-signed certs, adjust as needed + }) + + if not ssl_sock then + sock:close() + return false, "Failed to establish SSL connection: " .. (err or "unknown error") + end + + local success, err = ssl_sock:dohandshake() + if not success then + sock:close() + return false, "SSL handshake failed: " .. (err or "unknown error") + end + + sock = ssl_sock + end + + -- Read server greeting + local success, response = self:send_command(sock, nil, 220) + if not success then + sock:close() + return false, "SMTP server greeting failed: " .. response + end + + return sock, nil +end + +-- Send email +function SMTP:send_email(to_address, subject, message, from_name) + if not self.username or not self.password then + return false, "SMTP username or password not configured" + end + + -- Connect to server + local sock, err = self:connect() + if not sock then + return false, err + end + + local function cleanup_and_fail(error_msg) + sock:close() + return false, error_msg + end + + -- EHLO command + local success, response = self:send_command(sock, "EHLO furt-lua", 250) + if not success then + return cleanup_and_fail("EHLO failed: " .. response) + end + + -- AUTH LOGIN + local success, response = self:send_command(sock, "AUTH LOGIN", 334) + if not success then + return cleanup_and_fail("AUTH LOGIN failed: " .. response) + end + + -- Send username (base64 encoded) + local username_b64 = self:base64_encode(self.username) + local success, response = self:send_command(sock, username_b64, 334) + if not success then + return cleanup_and_fail("Username authentication failed: " .. response) + end + + -- Send password (base64 encoded) + local password_b64 = self:base64_encode(self.password) + local success, response = self:send_command(sock, password_b64, 235) + if not success then + return cleanup_and_fail("Password authentication failed: " .. response) + end + + -- MAIL FROM + local mail_from = "MAIL FROM:<" .. self.from_address .. ">" + local success, response = self:send_command(sock, mail_from, 250) + if not success then + return cleanup_and_fail("MAIL FROM failed: " .. response) + end + + -- RCPT TO + local rcpt_to = "RCPT TO:<" .. to_address .. ">" + local success, response = self:send_command(sock, rcpt_to, 250) + if not success then + return cleanup_and_fail("RCPT TO failed: " .. response) + end + + -- DATA command + local success, response = self:send_command(sock, "DATA", 354) + if not success then + return cleanup_and_fail("DATA command failed: " .. response) + end + + -- Build email message + local display_name = from_name or "Furt Contact Form" + local email_content = string.format( + "From: %s <%s>\r\n" .. + "To: <%s>\r\n" .. + "Subject: %s\r\n" .. + "Date: %s\r\n" .. + "Content-Type: text/plain; charset=UTF-8\r\n" .. + "\r\n" .. + "%s\r\n" .. + ".", + display_name, + self.from_address, + to_address, + subject, + os.date("%a, %d %b %Y %H:%M:%S %z"), + message + ) + + -- Send email content + local success, response = self:send_command(sock, email_content, 250) + if not success then + return cleanup_and_fail("Email sending failed: " .. response) + end + + -- QUIT + self:send_command(sock, "QUIT", 221) + sock:close() + + return true, "Email sent successfully" +end + +return SMTP +