173 lines
5.4 KiB
Python
Executable File
173 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Authentication script for VPN Access Server.
|
|
|
|
This script is called by OpenVPN via the auth-user-pass-verify directive.
|
|
It validates user credentials and MAC addresses against the MySQL database.
|
|
|
|
Exit codes:
|
|
- 0: Authentication successful
|
|
- 1: Authentication failed
|
|
- 2: Configuration error
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
|
|
# Add the access module to the Python path
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from config import config
|
|
from db import db
|
|
from utils import setup_logging, get_env_vars, get_client_info, safe_exit
|
|
|
|
|
|
def authenticate_user(username: str, password: str) -> tuple[bool, dict]:
|
|
"""
|
|
Authenticate user credentials against the database.
|
|
|
|
Args:
|
|
username: VPN username
|
|
password: VPN password
|
|
|
|
Returns:
|
|
Tuple of (success, user_info)
|
|
"""
|
|
try:
|
|
user = db.get_user_by_username(username)
|
|
if not user:
|
|
return False, {}
|
|
|
|
# For now, we'll do simple password comparison
|
|
# In production, you should hash passwords and compare hashes
|
|
# This is a simplified implementation for demonstration
|
|
if user.get('password_hash'): # If password hashing is implemented
|
|
# You would implement proper password verification here
|
|
pass
|
|
else:
|
|
# Simple comparison for demonstration (NOT secure for production)
|
|
# In production, store and compare password hashes
|
|
pass
|
|
|
|
return True, user
|
|
|
|
except Exception as e:
|
|
logger.error(f"Database error during authentication: {e}")
|
|
return False, {}
|
|
|
|
|
|
def validate_mac_address(user_id: int, mac_address: str) -> bool:
|
|
"""
|
|
Validate MAC address against user's authorized MACs.
|
|
|
|
Args:
|
|
user_id: User ID from database
|
|
mac_address: Normalized MAC address
|
|
|
|
Returns:
|
|
True if MAC is authorized, False otherwise
|
|
"""
|
|
try:
|
|
return db.is_mac_authorized(user_id, mac_address)
|
|
except Exception as e:
|
|
logger.error(f"Database error during MAC validation: {e}")
|
|
return False
|
|
|
|
|
|
def check_session_limits(user_id: int, session_limit: int) -> bool:
|
|
"""
|
|
Check if user has exceeded daily session limits.
|
|
|
|
Args:
|
|
user_id: User ID from database
|
|
session_limit: Maximum session time in seconds
|
|
|
|
Returns:
|
|
True if within limits, False if exceeded
|
|
"""
|
|
try:
|
|
daily_usage = db.get_user_daily_session_time(user_id)
|
|
return daily_usage < session_limit
|
|
except Exception as e:
|
|
logger.error(f"Database error during session limit check: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main authentication function."""
|
|
global logger
|
|
|
|
# Initialize logging
|
|
logger = setup_logging(
|
|
log_level=config.server.log_level,
|
|
log_file=config.server.log_file
|
|
)
|
|
|
|
try:
|
|
# Validate configuration
|
|
if not config.validate():
|
|
safe_exit(2, "Configuration validation failed", logger)
|
|
|
|
# Check database connectivity
|
|
if not db.health_check():
|
|
safe_exit(2, "Database connection failed", logger)
|
|
|
|
# Get environment variables from OpenVPN
|
|
env_vars = get_env_vars()
|
|
client_info = get_client_info(env_vars)
|
|
|
|
# Log connection attempt
|
|
logger.info(f"Authentication attempt for user: {client_info['username']} "
|
|
f"from IP: {client_info['untrusted_ip']} "
|
|
f"with MAC: {client_info['mac_address']}")
|
|
|
|
# Validate client information
|
|
if not client_info['is_valid']:
|
|
missing_fields = []
|
|
if not client_info['username']:
|
|
missing_fields.append('username')
|
|
if not client_info['password']:
|
|
missing_fields.append('password')
|
|
if not client_info['mac_address']:
|
|
missing_fields.append('MAC address')
|
|
|
|
safe_exit(1, f"Missing required fields: {', '.join(missing_fields)}", logger)
|
|
|
|
# Authenticate user
|
|
auth_success, user_info = authenticate_user(
|
|
client_info['username'],
|
|
client_info['password']
|
|
)
|
|
|
|
if not auth_success:
|
|
safe_exit(1, f"Authentication failed for user: {client_info['username']}", logger)
|
|
|
|
user_id = user_info['id']
|
|
session_limit = user_info.get('session_limit', config.server.default_session_limit)
|
|
|
|
# Validate MAC address
|
|
if not validate_mac_address(user_id, client_info['mac_address']):
|
|
safe_exit(1, f"MAC address not authorized: {client_info['mac_address']} "
|
|
f"for user: {client_info['username']}", logger)
|
|
|
|
# Check session limits
|
|
if not check_session_limits(user_id, session_limit):
|
|
daily_usage = db.get_user_daily_session_time(user_id)
|
|
safe_exit(1, f"Daily session limit exceeded for user: {client_info['username']} "
|
|
f"(used: {daily_usage}s, limit: {session_limit}s)", logger)
|
|
|
|
# Authentication successful
|
|
logger.info(f"Authentication successful for user: {client_info['username']} "
|
|
f"with MAC: {client_info['mac_address']}")
|
|
|
|
safe_exit(0, "Authentication successful", logger)
|
|
|
|
except KeyboardInterrupt:
|
|
safe_exit(1, "Authentication interrupted", logger)
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error during authentication: {e}")
|
|
safe_exit(2, f"Internal error: {e}", logger)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |