diff --git a/libc/src/shadow.c b/libc/src/shadow.c index 1bec4c3f..e254f861 100644 --- a/libc/src/shadow.c +++ b/libc/src/shadow.c @@ -14,9 +14,6 @@ #include "ctype.h" #include "limits.h" -/// Defines the buffer size for reading lines from the shadow file. -#define LINE_LIM 256 - /// @brief Converts a string to a long integer. /// /// @details Parses a string into a long integer and advances the string pointer. @@ -83,57 +80,91 @@ int __parsespent(char *s, struct spwd *sp) struct spwd *getspnam(const char *name) { static struct spwd spwd_buf; - static char *line; struct spwd *result; + char buffer[BUFSIZ]; int e; int orig_errno = errno; - if (!line) line = malloc(LINE_LIM); - if (!line) return 0; - e = getspnam_r(name, &spwd_buf, line, LINE_LIM, &result); + // Call the reentrant function to get the shadow password entry. + e = getspnam_r(name, &spwd_buf, buffer, BUFSIZ, &result); + + // Propagate error from getspnam_r if it fails. errno = e ? e : orig_errno; + return result; } int getspnam_r(const char *name, struct spwd *spwd_buf, char *buf, size_t buflen, struct spwd **result) { - char path[20 + NAME_MAX]; - int rv = 0; - int fd; - size_t k, l = strlen(name); - int skip = 0; - int cs; - int orig_errno = errno; + int rv = 0; // Return value to track errors (e.g., ERANGE). + int fd; // File descriptor for the shadow file. + size_t k; // Length of the current line read from the file. + size_t l = strlen(name); // Length of the username to search for. + int skip = 0; // Flag to indicate whether the current line should be skipped. + int orig_errno = errno; // Preserve the original errno value for later restoration. + + if (!spwd_buf) { + fprintf(stderr, "spwd_buf is NULL in getspnam_r.\n"); + return errno = EINVAL; + } + if (!result) { + fprintf(stderr, "result is NULL in getspnam_r.\n"); + return errno = EINVAL; + } + // Initialize the result to NULL, indicating no match found yet. *result = 0; - /* Disallow potentially-malicious user names */ - if (*name == '.' || strchr(name, '/') || !l) + // Validate the user name for security: + // - Disallow names starting with '.' or containing '/' (to prevent path traversal attacks). + // - Disallow empty names. + if (*name == '.' || strchr(name, '/') || !l) { return errno = EINVAL; + } - /* Buffer size must at least be able to hold name, plus some.. */ - if (buflen < l + 100) + // Ensure the buffer is large enough to hold the username and additional data. + if (buflen < l + 100) { return errno = ERANGE; + } + // Open the shadow file for reading. fd = open(SHADOW, O_RDONLY, 0); if (fd < 0) { return errno; } + // Read lines from the shadow file. while (fgets(buf, buflen, fd) && (k = strlen(buf)) > 0) { + // If skipping the line (due to being too long), or if the line does not match the user name: if (skip || strncmp(name, buf, l) || buf[l] != ':') { + // Set skip if the line does not end with a newline. skip = buf[k - 1] != '\n'; continue; } + + // If the line does not end with a newline, the buffer is too small. if (buf[k - 1] != '\n') { + // Buffer overflow risk; set return value to ERANGE. rv = ERANGE; break; } - if (__parsespent(buf, spwd_buf) < 0) continue; + // Parse the shadow entry from the line. + // If parsing fails, continue to the next line. + if (__parsespent(buf, spwd_buf) < 0) { + continue; + } + + // Set the result to the parsed shadow entry. *result = spwd_buf; + // Exit the loop after finding the match. break; } + + // Close the file descriptor. + close(fd); + // Restore errno to its original value unless an error occurred. errno = rv ? rv : orig_errno; + // Return 0 on success or an error code. return rv; } diff --git a/mentos/CMakeLists.txt b/mentos/CMakeLists.txt index 4e73f8ca..f578a68d 100644 --- a/mentos/CMakeLists.txt +++ b/mentos/CMakeLists.txt @@ -13,6 +13,8 @@ set(BOOTLOADER_NAME bootloader) # Enables memory allocation tracing. option(ENABLE_KMEM_TRACE "Enables kmalloc tracing." OFF) option(ENABLE_PAGE_TRACE "Enables page allocation tracing." OFF) +option(ENABLE_EXT2_TRACE "Enables EXT2 allocation tracing." OFF) +option(ENABLE_FILE_TRACE "Enables vfs_file allocation tracing." OFF) # Enables scheduling feedback on terminal. option(ENABLE_SCHEDULER_FEEDBACK "Enables scheduling feedback on terminal." OFF) @@ -144,6 +146,12 @@ endif(ENABLE_KMEM_TRACE) if(ENABLE_PAGE_TRACE) target_compile_definitions(${KERNEL_NAME} PUBLIC ENABLE_PAGE_TRACE) endif(ENABLE_PAGE_TRACE) +if(ENABLE_EXT2_TRACE) + target_compile_definitions(${KERNEL_NAME} PUBLIC ENABLE_EXT2_TRACE) +endif(ENABLE_EXT2_TRACE) +if(ENABLE_FILE_TRACE) + target_compile_definitions(${KERNEL_NAME} PUBLIC ENABLE_FILE_TRACE) +endif(ENABLE_FILE_TRACE) # ============================================================================= # Enables scheduling feedback on terminal. diff --git a/mentos/inc/fs/vfs.h b/mentos/inc/fs/vfs.h index 417188e8..47fa1df1 100644 --- a/mentos/inc/fs/vfs.h +++ b/mentos/inc/fs/vfs.h @@ -6,14 +6,12 @@ #pragma once #include "fs/vfs_types.h" +#include "os_root_path.h" #include "mem/slab.h" /// Maximum number of opened file. #define MAX_OPEN_FD 16 -/// Cache for file structures in the VFS. -extern kmem_cache_t *vfs_file_cache; - /// @brief Forward declaration of task_struct. /// Used for task management in the VFS. struct task_struct; @@ -23,6 +21,26 @@ struct task_struct; /// must be called before any other VFS functions. void vfs_init(void); +/// @brief Allocates a VFS file structure from the cache. +/// @param file Source file where the allocation is requested (for logging). +/// @param fun Function name where the allocation is requested (for logging). +/// @param line Line number where the allocation is requested (for logging). +/// @return Pointer to the allocated VFS file structure, or NULL if allocation fails. +vfs_file_t *pr_vfs_alloc_file(const char *file, const char *fun, int line); + +/// @brief Frees a VFS file structure back to the cache. +/// @param file Source file where the deallocation is requested (for logging). +/// @param fun Function name where the deallocation is requested (for logging). +/// @param line Line number where the deallocation is requested (for logging). +/// @param vfs_file Pointer to the VFS file structure to free. +void pr_vfs_dealloc_file(const char *file, const char *fun, int line, vfs_file_t *vfs_file); + +/// Wrapper that provides the filename, the function and line where the alloc is happening. +#define vfs_alloc_file(...) pr_vfs_alloc_file(__RELATIVE_PATH__, __func__, __LINE__) + +/// Wrapper that provides the filename, the function and line where the free is happening. +#define vfs_dealloc_file(...) pr_vfs_dealloc_file(__RELATIVE_PATH__, __func__, __LINE__, __VA_ARGS__) + /// @brief Register a new filesystem. /// @param fs A pointer to the information concerning the new filesystem. /// @return The outcome of the operation, 0 if fails. diff --git a/mentos/src/drivers/ata.c b/mentos/src/drivers/ata.c index ce165198..dace3b71 100644 --- a/mentos/src/drivers/ata.c +++ b/mentos/src/drivers/ata.c @@ -1300,7 +1300,7 @@ static int ata_close(vfs_file_t *file) pr_debug("ata_close: Removed file `%s` from the opened file list.\n", file->name); // Free the file from cache. - kmem_cache_free(file); + vfs_dealloc_file(file); pr_debug("ata_close: Freed memory for file `%s`.\n", file->name); } @@ -1552,7 +1552,7 @@ static vfs_file_operations_t ata_fs_operations = { static vfs_file_t *ata_device_create(ata_device_t *dev) { // Create the file. - vfs_file_t *file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + vfs_file_t *file = vfs_alloc_file(); if (file == NULL) { pr_err("Failed to create ATA device.\n"); return NULL; @@ -1613,7 +1613,7 @@ static ata_device_type_t ata_device_detect(ata_device_t *dev) if (!vfs_register_superblock(dev->fs_root->name, dev->path, &ata_file_system_type, dev->fs_root)) { pr_alert("Failed to mount ata device!\n"); // Free the memory. - kmem_cache_free(dev->fs_root); + vfs_dealloc_file(dev->fs_root); return ata_dev_type_unknown; } // Increment the drive letter. diff --git a/mentos/src/drivers/mem.c b/mentos/src/drivers/mem.c index 44d28e79..47e92858 100644 --- a/mentos/src/drivers/mem.c +++ b/mentos/src/drivers/mem.c @@ -185,7 +185,7 @@ static struct memdev *null_device_create(const char *name) dev->next = NULL; // Allocate memory for the associated file structure. - dev->file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + dev->file = vfs_alloc_file(); if (dev->file == NULL) { pr_err("null_device_create: Failed to allocate memory for file\n"); kfree(dev); // Free the previously allocated device memory. @@ -287,7 +287,7 @@ static int null_close(vfs_file_t *file) pr_debug("null_close: Removed file `%s` from the opened file list.\n", file->name); // Free the file from cache. - kmem_cache_free(file); + vfs_dealloc_file(file); pr_debug("null_close: Freed memory for file `%s`.\n", file->name); } diff --git a/mentos/src/fs/ext2.c b/mentos/src/fs/ext2.c index d1116446..d0f8ee7f 100644 --- a/mentos/src/fs/ext2.c +++ b/mentos/src/fs/ext2.c @@ -11,6 +11,12 @@ // If defined, ETX2 will debug everything. // #define EXT2_FULL_DEBUG +#ifdef ENABLE_EXT2_TRACE +#include "resource_tracing.h" +/// @brief Tracks the unique ID of the currently registered resource. +static int resource_id = -1; +#endif + #include "assert.h" #include "fcntl.h" #include "fs/ext2.h" @@ -468,6 +474,53 @@ static const char *time_to_string(uint32_t time) return s; } +/// @brief Allocate cache for EXT2 operations. +/// @param fs file system we are working with. +/// @return a pointer to the cache. +static inline uint8_t *ext2_alloc_cache(ext2_filesystem_t *fs) +{ + // Validate input. + if (!fs) { + pr_err("Invalid input: filesystem pointer is NULL."); + return NULL; + } + + // Allocate the cache. + uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL); + if (!cache) { + // Log critical error if cache allocation fails. + pr_crit("Failed to allocate cache for EXT2 operations."); + return NULL; + } + +#ifdef ENABLE_EXT2_TRACE + store_resource_info(resource_id, __RELATIVE_PATH__, 0, cache); +#endif + + // Clean the cache. + memset(cache, 0, fs->block_size); + + return cache; +} + +/// @brief Free the cache. +/// @param cache pointer to the cache. +static inline void ext2_dealloc_cache(uint8_t *cache) +{ + // Validate the input. + if (!cache) { + pr_err("Invalid input: cache pointer is NULL or already freed."); + return; + } + +#ifdef ENABLE_EXT2_TRACE + clear_resource_info(cache); +#endif + + // Free the cache. + kmem_cache_free(cache); +} + /// @brief Dumps on debugging output the superblock. /// @param sb the object to dump. static void ext2_dump_superblock(ext2_superblock_t *sb) @@ -595,9 +648,7 @@ static void ext2_dump_dirent(ext2_dirent_t *dirent) static void ext2_dump_bgdt(ext2_filesystem_t *fs) { // Allocate the cache. - uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL); - // Clean the cache. - memset(cache, 0, fs->block_size); + uint8_t *cache = ext2_alloc_cache(fs); for (uint32_t i = 0; i < fs->block_groups_count; ++i) { // Get the pointer to the current group descriptor. ext2_group_descriptor_t *gd = &(fs->block_groups[i]); @@ -633,7 +684,7 @@ static void ext2_dump_bgdt(ext2_filesystem_t *fs) } } } - kmem_cache_free(cache); + ext2_dealloc_cache(cache); } /// @brief Dumps on debugging output the filesystem. @@ -658,45 +709,6 @@ static void ext2_dump_filesystem(ext2_filesystem_t *fs) // EXT2 Core Functions // ============================================================================ -/// @brief Allocate cache for EXT2 operations. -/// @param fs file system we are working with. -/// @return a pointer to the cache. -static inline uint8_t *ext2_alloc_cache(ext2_filesystem_t *fs) -{ - // Validate input. - if (!fs) { - pr_err("Invalid input: filesystem pointer is NULL."); - return NULL; - } - - // Allocate the cache. - uint8_t *cache = kmem_cache_alloc(fs->ext2_buffer_cache, GFP_KERNEL); - if (!cache) { - // Log critical error if cache allocation fails. - pr_crit("Failed to allocate cache for EXT2 operations."); - return NULL; - } - - // Clean the cache. - memset(cache, 0, fs->block_size); - - return cache; -} - -/// @brief Free the cache. -/// @param cache pointer to the cache. -static inline void ext2_dealloc_cache(uint8_t *cache) -{ - // Validate the input. - if (!cache) { - pr_err("Invalid input: cache pointer is NULL or already freed."); - return; - } - - // Free the cache. - kmem_cache_free(cache); -} - /// @brief Returns the rec_len from the given name. /// @param name the name we use to compute the rec_len. /// @return the rec_len value. @@ -2787,7 +2799,7 @@ static vfs_file_t *ext2_creat(const char *path, mode_t mode) vfs_file_t *file = ext2_find_vfs_file_with_inode(fs, search.direntry.inode); if (file == NULL) { // Allocate the memory for the file. - file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + file = vfs_alloc_file(); if (file == NULL) { pr_err("Failed to allocate memory for the EXT2 file.\n"); goto close_parent_return_null; @@ -2829,7 +2841,7 @@ static vfs_file_t *ext2_creat(const char *path, mode_t mode) goto close_parent_return_null; } // Allocate the memory for the file. - vfs_file_t *new_file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + vfs_file_t *new_file = vfs_alloc_file(); if (new_file == NULL) { pr_err("Failed to allocate memory for the EXT2 file.\n"); goto close_parent_return_null; @@ -2916,7 +2928,7 @@ static vfs_file_t *ext2_open(const char *path, int flags, mode_t mode) vfs_file_t *file = ext2_find_vfs_file_with_inode(fs, search.direntry.inode); if (file == NULL) { // Allocate the memory for the file. - file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + file = vfs_alloc_file(); if (file == NULL) { pr_err("ext2_open(path: '%s', flags: %d, mode: %d): Failed to allocate memory for the EXT2 file.\n", path, flags, mode); @@ -3037,16 +3049,16 @@ static int ext2_close(vfs_file_t *file) return -EINVAL; } - // Ensure we are not trying to close the root. - if (file == fs->root) { - pr_warning("ext2_close: Attempted to close the root file `%s`.\n", file->name); - return -EPERM; - } - pr_debug("ext2_close(ino: %d, file: \"%s\", count: %d)\n", file->ino, file->name, file->count - 1); // Decrement the reference count for the file and close if last reference. if (--file->count == 0) { + // Ensure we are not trying to free the memory of the root. + if (file == fs->root) { + pr_warning("ext2_close: Attempted to close the root file `%s`.\n", file->name); + return -EPERM; + } + pr_debug("ext2_close: Closing file `%s` (ino: %d).\n", file->name, file->ino); // Remove the file from the list of opened files. @@ -3055,7 +3067,7 @@ static int ext2_close(vfs_file_t *file) // Free the file from cache. pr_debug("ext2_close: Freeing memory for file `%s`.\n", file->name); - kmem_cache_free(file); + vfs_dealloc_file(file); } return 0; @@ -3639,6 +3651,10 @@ static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path) NULL, NULL); +#ifdef ENABLE_EXT2_TRACE + resource_id = register_resource("ext2"); +#endif + // uint8_t *caches[100]; // for (size_t i = 0; i < 100; ++i) { // caches[i] = ext2_alloc_cache(fs); @@ -3716,7 +3732,7 @@ static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path) goto free_block_buffer; } // Allocate the memory for the root. - fs->root = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + fs->root = vfs_alloc_file(); if (!fs->root) { pr_err("Failed to allocate memory for the EXT2 root file!\n"); // Free the block_buffer, the block_groups and the filesystem. @@ -3727,6 +3743,8 @@ static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path) // Free the block_buffer, the block_groups and the filesystem. goto free_all; } + // Set the count for the root to 1. + fs->root->count = 1; // Add the root to the list of opened files. list_head_insert_before(&fs->root->siblings, &fs->opened_files); @@ -3741,7 +3759,7 @@ static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path) free_all: // Free the memory occupied by the root. - kmem_cache_free(fs->root); + vfs_dealloc_file(fs->root); free_block_buffer: // Free the memory occupied by the block buffer. kmem_cache_destroy(fs->ext2_buffer_cache); diff --git a/mentos/src/fs/pipe.c b/mentos/src/fs/pipe.c index 1593388e..720f7642 100644 --- a/mentos/src/fs/pipe.c +++ b/mentos/src/fs/pipe.c @@ -630,7 +630,7 @@ static inline int pipe_is_blocking(vfs_file_t *file) static inline vfs_file_t *pipe_create_file_struct(const char *path, int flags, mode_t mode) { // Allocate memory for the VFS file structure. - vfs_file_t *vfs_file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + vfs_file_t *vfs_file = vfs_alloc_file(); if (!vfs_file) { pr_err("Failed to allocate memory for VFS file!\n"); return NULL; @@ -879,7 +879,7 @@ static int pipe_close(vfs_file_t *file) list_head_remove(&file->siblings); // Free the file from cache. - kmem_cache_free(file); + vfs_dealloc_file(file); } return 0; diff --git a/mentos/src/fs/procfs.c b/mentos/src/fs/procfs.c index 1f4ea71c..2216b68d 100644 --- a/mentos/src/fs/procfs.c +++ b/mentos/src/fs/procfs.c @@ -317,7 +317,7 @@ static inline vfs_file_t *procfs_create_file_struct(procfs_file_t *procfs_file) pr_err("procfs_create_file_struct(%p): Procfs file not valid!\n", procfs_file); return NULL; } - vfs_file_t *vfs_file = kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + vfs_file_t *vfs_file = vfs_alloc_file(); if (!vfs_file) { pr_err("procfs_create_file_struct(%p): Failed to allocate memory for VFS file!\n", procfs_file); return NULL; @@ -557,7 +557,7 @@ static int procfs_close(vfs_file_t *file) pr_debug("procfs_close: Removed file `%s` from the opened file list.\n", file->name); // Free the file from cache. - kmem_cache_free(file); + vfs_dealloc_file(file); pr_debug("procfs_close: Freed memory for file `%s`.\n", file->name); } diff --git a/mentos/src/fs/vfs.c b/mentos/src/fs/vfs.c index e50c87d7..122788f4 100644 --- a/mentos/src/fs/vfs.c +++ b/mentos/src/fs/vfs.c @@ -25,6 +25,12 @@ #include "system/syscall.h" #include "fs/pipe.h" +#ifdef ENABLE_FILE_TRACE +#include "resource_tracing.h" +/// @brief Tracks the unique ID of the currently registered resource. +static int resource_id = -1; +#endif + /// The list of superblocks. static list_head vfs_super_blocks; /// The list of filesystems. @@ -37,7 +43,7 @@ static spinlock_t vfs_spinlock; static kmem_cache_t *vfs_superblock_cache; /// VFS memory cache for files. -kmem_cache_t *vfs_file_cache; +static kmem_cache_t *vfs_file_cache; void vfs_init(void) { @@ -48,11 +54,79 @@ void vfs_init(void) // Initialize the caches for superblocks and files. vfs_superblock_cache = KMEM_CREATE(super_block_t); vfs_file_cache = KMEM_CREATE(vfs_file_t); + // Register the resouces. +#ifdef ENABLE_FILE_TRACE + resource_id = register_resource("vfs_file"); +#endif // Initialize the spinlock. spinlock_init(&vfs_spinlock); spinlock_init(&vfs_spinlock_refcount); } +vfs_file_t *pr_vfs_alloc_file(const char *file, const char *fun, int line) +{ + // Validate that the cache is initialized. + if (!vfs_file_cache) { + pr_err("VFS file cache is not initialized.\n"); + return NULL; + } + + // Allocate the cache. + vfs_file_t *vfs_file = (vfs_file_t *)kmem_cache_alloc(vfs_file_cache, GFP_KERNEL); + + // Log a critical error if cache allocation fails. + if (!vfs_file) { + pr_crit("Failed to allocate cache for VFS file operations.\n"); + return NULL; + } + +#ifdef ENABLE_FILE_TRACE + // Store trace information for debugging resource usage. + store_resource_info(resource_id, file, line, vfs_file); +#endif + + // Zero out the allocated structure to ensure clean initialization. + memset(vfs_file, 0, sizeof(vfs_file_t)); + + return vfs_file; +} + +static const char *__vfs_print_file_details(void *ptr) +{ + vfs_file_t *file = (vfs_file_t *)ptr; + static char buffer[NAME_MAX]; + sprintf(buffer, "(0x%p) [%2u] %s", ptr, file->ino, file->name); + return buffer; +} + +void pr_vfs_dealloc_file(const char *file, const char *fun, int line, vfs_file_t *vfs_file) +{ + // Validate the input pointer. + if (!vfs_file) { + pr_err("Cannot deallocate a NULL VFS file pointer.\n"); + return; + } + + // Validate that the cache is initialized. + if (!vfs_file_cache) { + pr_err("VFS file cache is not initialized.\n"); + return; + } + +#ifdef ENABLE_FILE_TRACE + // Clear trace information for debugging resource usage. + clear_resource_info(vfs_file); +#endif + + // Free the VFS file back to the cache. + kmem_cache_free(vfs_file); + +#ifdef ENABLE_FILE_TRACE + // Clear trace information for debugging resource usage. + print_resource_usage(resource_id, __vfs_print_file_details); +#endif +} + /// @brief Finds a filesystem type by its name. /// @param name The name of the filesystem to find. /// @return Pointer to the filesystem type if found, or NULL if not found. diff --git a/mentos/src/mem/slab.c b/mentos/src/mem/slab.c index 5657eeef..e20bebbe 100644 --- a/mentos/src/mem/slab.c +++ b/mentos/src/mem/slab.c @@ -442,7 +442,7 @@ int kmem_cache_init(void) list_head_init(&kmem_caches_list); #ifdef ENABLE_KMEM_TRACE - resource_id = register_resource("kmem"); + ENABLE_EXT2_TRACE = register_resource("kmem"); #endif // Create a cache to store metadata about kmem_cache_t structures. diff --git a/programs/login.c b/programs/login.c index e39291e5..38a705cf 100644 --- a/programs/login.c +++ b/programs/login.c @@ -268,13 +268,12 @@ int main(int argc, char **argv) continue; // Retry after error } - struct spwd *spwd; - if ((spwd = getspnam(username)) == NULL) { + struct spwd *shadow; + if ((shadow = getspnam(username)) == NULL) { printf("Could not retrieve the secret password of %s: %s\n", username, strerror(errno)); continue; // Retry if unable to get shadow password } - // Hash the input password for verification unsigned char hash[SHA256_BLOCK_SIZE] = { 0 }; char hash_string[SHA256_BLOCK_SIZE * 2 + 1] = { 0 }; SHA256_ctx_t ctx; @@ -286,7 +285,7 @@ int main(int argc, char **argv) sha256_bytes_to_hex(hash, SHA256_BLOCK_SIZE, hash_string, SHA256_BLOCK_SIZE * 2 + 1); // Verify the password against the stored hash - if (strcmp(spwd->sp_pwdp, hash_string) != 0) { + if (strcmp(shadow->sp_pwdp, hash_string) != 0) { printf("Wrong password.\n"); continue; // Retry on incorrect password }