Files
zftpd/include/ftp_path.h
T
Seregon ed3df54225 Add logging, tests, and session improvements
Add a lightweight logging subsystem and MIT headers, enable unit tests, and harden session/path handling.

Key changes:
- Introduce ftp_log.h and src/ftp_log.c for session/command logging and line logging.
- Add MIT license blocks and update file dates across many headers/sources.
- Makefile: include src/ftp_log.c, add LIB_OBJECTS (build objects without main) and multi-test support, PS5 link libSceNotification, and skip tests on unsupported targets.
- Add tests/tests: tests/test_security.c and tests/test_path_security.c and Makefile rules to build/run them.
- Extend ftp_session_t with root_path, control RX buffer, auth flags, and rate-limiter fields; initialize realpath(root) and set socket timeouts during init.
- Add compile-time config knobs in include/ftp_config.h (control/data timeouts, transfer rate limits and burst size).
- Improve RETR/STOR/APPE flows: aggregate bytes sent/received, use new logging hooks, prefer sendfile only when no rate limit, and unify data send path via ftp_session_send_data.
- Strengthen PORT/PASV handling: restrict PORT to same client IP, reuse/close previous pasv socket, bind PASV to INADDR_ANY, and robustly determine local IP for PASV response.
- Enhance path normalization and resolution (ftp_path.c): safer component parsing, realpath checks, and validation against session root to prevent traversal/symlink escapes.
- Add pal API additions in headers (pal_socket_set_timeouts, pal_send_all) and use pal_send_all for control replies.
- Add helper utilities in ftp_session.c (wait_fd_ready, monotonic_ns) and other misc cleanups.

Overall this commit focuses on observability, testability, and security/robustness of session, path and data handling.
2026-02-14 20:32:39 +01:00

218 lines
6.1 KiB
C

/*
MIT License
Copyright (c) 2026 Seregon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
* @file ftp_path.h
* @brief Secure path validation and normalization
*
* @author SeregonWar
* @version 1.0.0
* @date 2026-02-13
*
* SECURITY: Prevents directory traversal attacks (../, symlinks, etc.)
* ALGORITHM: Stack-based path component processing
*
*/
#ifndef FTP_PATH_H
#define FTP_PATH_H
#include "ftp_types.h"
/*===========================================================================*
* PATH VALIDATION
*===========================================================================*/
/**
* @brief Normalize path (remove .., ., //)
*
* SECURITY: Essential for preventing directory traversal attacks
*
* ALGORITHM:
* 1. Split path into components (delimiter: '/')
* 2. Process each component:
* - "." -> skip (current directory)
* - ".." -> pop stack (parent directory)
* - other -> push to stack
* 3. Reconstruct path from stack
*
* EXAMPLES:
* "/home/user/../admin" -> "/home/admin"
* "/home/./user" -> "/home/user"
* "/home//user" -> "/home/user"
* "/../etc/passwd" -> "/etc/passwd"
*
* @param path Input path (null-terminated)
* @param output Output buffer for normalized path
* @param size Size of output buffer
*
* @return FTP_OK on success, negative error code on failure
* @retval FTP_OK Path normalized successfully
* @retval FTP_ERR_INVALID_PARAM Invalid parameters
* @retval FTP_ERR_PATH_TOO_LONG Path exceeds buffer size
*
* @pre path != NULL
* @pre output != NULL
* @pre size >= FTP_PATH_MAX
*
* @post output contains normalized path (absolute)
* @post output is null-terminated
*
* @note WCET: O(n) where n = strlen(path)
* @note Thread-safety: Safe (no shared state)
*
* @warning Does not resolve symlinks (use realpath for that)
*/
ftp_error_t ftp_path_normalize(const char *path,
char *output,
size_t size);
/**
* @brief Resolve path relative to session CWD
*
* Converts relative paths to absolute paths:
* - Absolute path ("/foo") -> keep as-is
* - Relative path ("foo") -> prepend CWD
*
* @param session Client session (for CWD context)
* @param path User-supplied path
* @param output Output buffer for resolved path
* @param size Size of output buffer
*
* @return FTP_OK on success, negative error code on failure
*
* @pre session != NULL
* @pre path != NULL
* @pre output != NULL
* @pre size >= FTP_PATH_MAX
*
* @post output contains absolute, normalized path
*/
ftp_error_t ftp_path_resolve(const ftp_session_t *session,
const char *path,
char *output,
size_t size);
/**
* @brief Check if path is within server root
*
* SECURITY: Ensures clients cannot escape chroot jail
*
* @param path Absolute path to check
* @param root Server root directory
*
* @return 1 if path is within root, 0 if not, negative on error
*
* @pre path != NULL
* @pre root != NULL
* @pre path is absolute (starts with '/')
* @pre root is absolute (starts with '/')
*
* @note WCET: O(min(strlen(path), strlen(root)))
*/
int ftp_path_is_within_root(const char *path, const char *root);
/**
* @brief Validate path safety
*
* Checks for:
* - Null bytes (string injection)
* - Excessive length
* - Invalid characters
*
* @param path Path to validate
*
* @return 1 if safe, 0 if unsafe, negative on error
*
* @pre path != NULL
*/
int ftp_path_is_safe(const char *path);
/**
* @brief Get basename from path
*
* EXAMPLE: "/home/user/file.txt" -> "file.txt"
*
* @param path Input path
* @param basename Output buffer for basename
* @param size Size of output buffer
*
* @return FTP_OK on success, negative error code on failure
*
* @pre path != NULL
* @pre basename != NULL
* @pre size > 0
*
* @post basename contains filename component
*/
ftp_error_t ftp_path_basename(const char *path,
char *basename,
size_t size);
/**
* @brief Get directory name from path
*
* EXAMPLE: "/home/user/file.txt" -> "/home/user"
*
* @param path Input path
* @param dirname Output buffer for directory
* @param size Size of output buffer
*
* @return FTP_OK on success, negative error code on failure
*
* @pre path != NULL
* @pre dirname != NULL
* @pre size > 0
*/
ftp_error_t ftp_path_dirname(const char *path,
char *dirname,
size_t size);
/**
* @brief Join two path components
*
* EXAMPLE: ("/home/user", "file.txt") -> "/home/user/file.txt"
*
* @param base Base path
* @param append Path to append
* @param output Output buffer
* @param size Size of output buffer
*
* @return FTP_OK on success, negative error code on failure
*
* @pre base != NULL
* @pre append != NULL
* @pre output != NULL
* @pre size >= FTP_PATH_MAX
*
* @post output contains joined path
* @post output is normalized
*/
ftp_error_t ftp_path_join(const char *base,
const char *append,
char *output,
size_t size);
#endif /* FTP_PATH_H */