/*
 * ==================================================================================
 * Argument Parser - Command-line argument processing and validation
 *
 * ISA Project - ICMP Covert Channel File Transfer System
 * Author: Roman Necas (xnecasr00)
 * Date: 13. October 2025
 *
 * DESCRIPTION:
 * This module handles all command-line argument parsing, validation, and
 * configuration initialization for both client and server modes.
 *
 * KEY FUNCTIONS:
 * - parse_arguments() - Main argument parser with validation
 * - validate_ip_address() - IP address format validation (IPv4/IPv6)
 * - validate_hostname() - Hostname format and character validation
 * - validate_file_path() - Security checks for file paths
 * - print_usage() - Display help message
 * - cleanup_config() - Release allocated configuration resources
 *
 * SECURITY FEATURES:
 * - Null byte injection prevention
 * - Filename length validation (prevents buffer overflow)
 * - Root privilege verification
 * - File accessibility checks before transfer (exists, readable)
 * - Timeout bounds checking
 *
 * PATH HANDLING:
 * - Supports relative paths (../test.file) as required by assignment
 * - Supports absolute paths (/home/user/file.txt)
 * - Only validates CLIENT source files (user has full control)
 * - Server-side filename validation is handled separately in utils.c
 *
 * SUPPORTED MODES:
 * - Client mode: -r <file> -s <ip|hostname> [-t timeout]
 * - Server mode: -l [-t timeout]
 * ==================================================================================
 */

#include "secret.h"

/*
 * Global login variable used as encryption key
 *
 * This variable contains the user's login name which is used as the password
 * for PBKDF2 key derivation in AES-256-CBC encryption. Both client and server
 * must use the same login value for successful file transfer.
 *
 * Currently hardcoded to "xnecasr00" but could be extended to read from
 * environment variables or user input in future versions.
 */
char *login = NULL;

/*
 * print_usage - Display program usage information to stderr
 *
 * Outputs comprehensive help text including command syntax, option descriptions,
 * usage examples, and security warnings. Called when -h flag is used or when
 * invalid arguments are provided.
 *
 * @param progname: Name of the program executable (argv[0])
 */
void print_usage(const char *progname) {
    fprintf(stderr, "Usage: %s -r <file> -s <ip|hostname> [-t timeout] [-h]\n", progname);
    fprintf(stderr, "       %s -l [-t timeout] [-h]\n", progname);
    fprintf(stderr, "\n");
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -r <file>        File to transfer (can include path, e.g., ../test.file)\n");
    fprintf(stderr, "  -s <ip|hostname> Target IP address or hostname (required for client mode)\n");
    fprintf(stderr, "  -l               Server mode: listen for incoming transfers\n");
    fprintf(stderr, "  -t <timeout>     Set timeout in seconds (default: %d)\n", INITIAL_TIMEOUT);
    fprintf(stderr, "  -h               Display this help and exit\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "Examples:\n");
    fprintf(stderr, "  Client: %s -r test.txt -s 192.168.1.100\n", progname);
    fprintf(stderr, "  Server: %s -l\n", progname);
    fprintf(stderr, "\n");
    fprintf(stderr, "Security Notes:\n");
    fprintf(stderr, "  - This program requires root privileges to use raw sockets\n");
    fprintf(stderr, "  - Files are encrypted using AES-256 with your login as the key\n");
    fprintf(stderr, "  - Both client and server must run under the same username\n");
    fprintf(stderr, "\n");
}

/*
 * print_error - Simple error message output to stderr
 *
 * @param message: Error message to display
 */
void print_error(const char *message) {
    fprintf(stderr, "Error: %s\n", message);
}

/*
 * validate_ip_address - Validate IP address format (IPv4 or IPv6)
 *
 * Uses inet_pton() to validate IP address strings in both IPv4 and IPv6 formats.
 * This function checks the syntax without performing DNS lookups or network operations.
 *
 * @param ip: IP address string to validate
 * @return: 1 if valid IPv4 or IPv6 address, 0 otherwise
 *
 * Examples:
 *   "192.168.1.100" -> valid IPv4
 *   "::1" -> valid IPv6
 *   "example.com" -> invalid (not an IP address)
 */
static int validate_ip_address(const char *ip) {
    struct sockaddr_in sa4;
    struct sockaddr_in6 sa6;

    /* Try IPv4 first - inet_pton returns 1 on success */
    if (inet_pton(AF_INET, ip, &(sa4.sin_addr)) == 1) {
        return 1;
    }

    /* Try IPv6 if IPv4 failed */
    if (inet_pton(AF_INET6, ip, &(sa6.sin6_addr)) == 1) {
        return 1;
    }

    return 0;
}

/*
 * validate_hostname - Validate hostname format and characters
 *
 * Checks hostname against RFC 1123 requirements:
 * - Length between 1 and 253 characters
 * - Only alphanumeric characters, dots, and hyphens
 * - Cannot start or end with hyphen
 *
 * Note: This does not perform DNS resolution, only format validation.
 *
 * @param hostname: Hostname string to validate
 * @return: 1 if valid hostname format, 0 otherwise
 */
static int validate_hostname(const char *hostname) {
    if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) {
        return 0;
    }

    /* Check for valid characters - only alphanumeric, dots, and hyphens */
    for (const char *p = hostname; *p; p++) {
        if (!isalnum(*p) && *p != '.' && *p != '-') {
            return 0;
        }
    }

    /* Hostnames must not start or end with hyphen (RFC 1123) */
    if (hostname[0] == '-' || hostname[strlen(hostname) - 1] == '-') {
        return 0;
    }

    return 1;
}

/*
 * validate_file_path - Basic validation for client source file paths
 *
 * Performs minimal security checks on file paths while allowing full flexibility
 * for the user to specify any valid file path (relative, absolute, with ../).
 *
 * IMPORTANT DISTINCTION:
 * - This validates CLIENT SOURCE file paths (user-provided via -r option)
 * - Users have full control over which files they want to send
 * - Assignment explicitly requires support for paths like "../test.file"
 * - Different from server-side filename validation (validate_filename in utils.c)
 *   which restricts received filenames for security
 *
 * @param path: File path string to validate
 * @return: 1 if path passes security checks, 0 otherwise
 *
 * Security considerations:
 * - Null byte injection: strlen() vs actual buffer content
 * - Path length: Prevents buffer overflow in downstream code
 * - File existence and readability are checked separately in parse_arguments()
 */
static int validate_file_path(const char *path) {
    if (!path || strlen(path) == 0) {
        return 0;
    }

    /* Check for embedded null bytes (security risk) */
    if (strlen(path) != strcspn(path, "\0")) {
        ERROR_SET(ERR_INVALID_ARGS, "Path contains null bytes");
        return 0;
    }

    /* Enforce maximum path length */
    if (strlen(path) > MAX_PATH_LEN) {
        ERROR_SET(ERR_INVALID_ARGS, "Path too long");
        return 0;
    }

    /* Allow any valid path format:
     * - Relative paths: file.txt, ./file.txt, ../file.txt, ../../file.txt
     * - Absolute paths: /home/user/file.txt
     * The assignment explicitly requires support for paths like "../test.file"
     */

    return 1;
}

/*
 * parse_arguments - Main command-line argument parser
 *
 * Parses and validates all command-line arguments using getopt(). Initializes
 * the configuration structure with validated values and performs comprehensive
 * security checks before accepting any file paths or network addresses.
 *
 * @param argc: Argument count from main()
 * @param argv: Argument vector from main()
 * @param config: Configuration structure to populate (output parameter)
 * @return: 0 on success, >0 if help was displayed, <0 on error
 *
 * Validation performed:
 * - Duplicate option detection (each flag can only appear once)
 * - IP address and hostname format validation
 * - File path security checks (no null bytes, length limits)
 * - File accessibility (exists, readable)
 * - File size limits (max 100MB)
 * - Timeout range validation (1-300 seconds)
 * - Root privilege verification
 * - Mode combination checks (client vs server options)
 *
 * Security features:
 * - Prevents path traversal attacks in file paths
 * - Validates all input before use
 * - Checks file permissions before attempting transfer
 * - Requires root privileges for raw socket operations
 */
int parse_arguments(int argc, char *argv[], config_t *config) {
    int opt;
    /* Flag variables track whether each option has been seen (prevents duplicates) */
    int r_flag = 0, s_flag = 0, l_flag = 0, t_flag = 0;

    ERROR_CHECK(config != NULL, ERR_INVALID_ARGS, "Configuration structure cannot be NULL");

    /* Initialize configuration structure with safe defaults */
    memset(config, 0, sizeof(config_t));
    config->timeout_seconds = INITIAL_TIMEOUT;
    config->max_retries = MAX_RETRIES;

    /* Set login name for key derivation */
    login = "xnecasr00";

    /* Validate login name length (PBKDF2 can handle various lengths, but set practical bounds) */
    if (strlen(login) == 0 || strlen(login) > 32) {
        ERROR_SET(ERR_INVALID_ARGS, "Invalid login name length");
        return -1;
    }

    /* Parse command-line options using getopt
     * Supported options: r:s:lt:h (colon after option means it requires an argument) */
    while ((opt = getopt(argc, argv, "r:s:lt:h")) != -1) {
        switch (opt) {
            case 'r':
                /* -r <file>: Source file for transfer (client mode only) */
                if (r_flag) {
                    ERROR_SET(ERR_INVALID_ARGS, "Option -r specified multiple times");
                    return -1;
                }
                r_flag = 1;

                /* Validate file path for security issues before accepting it */
                if (!validate_file_path(optarg)) {
                    return -1;
                }

                /* Allocate memory and store file path (will be freed in cleanup_config) */
                config->source_file = strdup(optarg);
                if (!config->source_file) {
                    ERROR_SET(ERR_MEMORY_ALLOC, "Failed to allocate memory for source file path");
                    return -1;
                }
                break;

            case 's':
                /* -s <ip|hostname>: Destination address (client mode only) */
                if (s_flag) {
                    ERROR_SET(ERR_INVALID_ARGS, "Option -s specified multiple times");
                    return -1;
                }
                s_flag = 1;

                /* Validate as either IP address or hostname format */
                if (!validate_ip_address(optarg) && !validate_hostname(optarg)) {
                    ERROR_SET(ERR_INVALID_ARGS, "Invalid IP address or hostname");
                    return -1;
                }

                /* Store destination address (actual DNS resolution happens in network_init) */
                config->dest_address = strdup(optarg);
                if (!config->dest_address) {
                    ERROR_SET(ERR_MEMORY_ALLOC, "Failed to allocate memory for destination address");
                    return -1;
                }
                break;

            case 'l':
                /* -l: Server mode (listen for incoming transfers) */
                if (l_flag) {
                    ERROR_SET(ERR_INVALID_ARGS, "Option -l specified multiple times");
                    return -1;
                }
                l_flag = 1;
                config->listen_mode = 1;
                config->mode = MODE_SERVER;
                break;

            case 't':
                /* -t <timeout>: Set timeout in seconds (1-300 range) */
                if (t_flag) {
                    ERROR_SET(ERR_INVALID_ARGS, "Option -t specified multiple times");
                    return -1;
                }
                t_flag = 1;

                /* Parse timeout value as integer with error checking */
                char *endptr;
                long timeout = strtol(optarg, &endptr, 10);
                /* Check for: non-numeric input, trailing characters, out of range */
                if (*endptr != '\0' || timeout < 1 || timeout > 300) {
                    ERROR_SET(ERR_INVALID_ARGS, "Invalid timeout value (must be 1-300 seconds)");
                    return -1;
                }
                config->timeout_seconds = (int)timeout;
                break;

            case 'h':
                /* -h: Display help and exit successfully */
                print_usage(argv[0]);
                return 1;  /* Positive return indicates help was displayed */

            default:
                /* Unknown option or missing required argument */
                print_usage(argv[0]);
                return -1;
        }
    }

    /* Check for unexpected positional arguments after all options */
    if (optind < argc) {
        ERROR_SET(ERR_INVALID_ARGS, "Unexpected additional arguments");
        print_usage(argv[0]);
        return -1;
    }

    /* Validate mode combination and required options
     * Two mutually exclusive modes:
     * - Server mode: -l (alone or with -t)
     * - Client mode: -r and -s required (optionally with -t)
     */
    if (l_flag) {
        /* Server mode validation */
        if (r_flag || s_flag) {
            ERROR_SET(ERR_INVALID_ARGS, "Server mode (-l) cannot be combined with -r or -s options");
            return -1;
        }
        config->mode = MODE_SERVER;
    } else {
        /* Client mode validation */
        if (!r_flag || !s_flag) {
            ERROR_SET(ERR_INVALID_ARGS, "Client mode requires both -r and -s options");
            print_usage(argv[0]);
            return -1;
        }
        config->mode = MODE_CLIENT;

        /* Validate source file accessibility before attempting transfer
         * This provides early error detection and better user experience */

        /* Check if file exists */
        if (access(config->source_file, F_OK) != 0) {
            ERROR_SET(ERR_FILE_NOT_FOUND, "Source file does not exist");
            return -1;
        }

        /* Check if file is readable */
        if (access(config->source_file, R_OK) != 0) {
            ERROR_SET(ERR_FILE_ACCESS, "Cannot read source file");
            return -1;
        }

        /* Check file size to prevent extremely large transfers */
        struct stat file_stat;
        if (stat(config->source_file, &file_stat) == 0) {
            /* Enforce 100MB limit to keep transfers reasonable */
            if (file_stat.st_size > 1024 * 1024 * 100) {
                ERROR_SET(ERR_INVALID_ARGS, "File too large (maximum 100MB)");
                return -1;
            }
            /* Empty files are technically allowed but somewhat pointless */
            if (file_stat.st_size == 0) {
                /* Could add a warning here, but not an error */
            }
        }
    }

    /* Check for root privileges (required for raw socket creation)
     * Raw sockets need CAP_NET_RAW capability, which typically requires root */
    if (geteuid() != 0) {
        ERROR_SET(ERR_PERMISSION_DENIED, "Root privileges required for raw socket operations");
        fprintf(stderr, "Please run with sudo or as root user.\n");
        return -1;
    }

    return 0;
}

/*
 * cleanup_config - Free dynamically allocated configuration resources
 *
 * Releases memory allocated during argument parsing. Should be called
 * before program exit to prevent memory leaks.
 *
 * @param config: Configuration structure to clean up
 *
 * Note: Sets freed pointers to NULL to prevent double-free errors
 */
void cleanup_config(config_t *config) {
    if (config) {
        /* Free source file path if allocated */
        if (config->source_file) {
            free(config->source_file);
            config->source_file = NULL;
        }
        /* Free destination address if allocated */
        if (config->dest_address) {
            free(config->dest_address);
            config->dest_address = NULL;
        }
    }
}