Initial public release: fresh history

This commit is contained in:
2025-10-22 04:28:20 -04:00
commit 10e8dfba2d
25 changed files with 2704 additions and 0 deletions

25
.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
# build artifacts
/obj/
/fblogin
*.o
*.d
# logs, cores, misc
*.log
core
core.*
# editor/OS junk
*~
*.swp
*.swo
.DS_Store
# backups: files and directories ending in .bak
*.bak
**/*.bak
**/*.bak/
# manpage compressed variant
man/*.gz

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Klein
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.

124
Makefile Normal file
View File

@@ -0,0 +1,124 @@
# Makefile for fblogin (clean, non-root build; root needed only for install)
# ---- config ----
TARGET := fblogin
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := include
PREFIX := /usr/local
BINDIR := $(PREFIX)/bin
# Banner (issue) file used by pam_issue.so
ISSUE_DIR ?= system/issue
ISSUE_FILE ?= $(ISSUE_DIR)/issue.fblogin
ISSUE_DEST ?= /etc/issue.fblogin
# Compiler/Linker
CC := gcc
CPPFLAGS := -I$(INC_DIR) -D_GNU_SOURCE -MMD -MP
CFLAGS := -Wall -Wextra -Wpedantic -O2
LDFLAGS := -Wl,--as-needed
LDLIBS := -lpam -lpam_misc
# Optional libsystemd (sd-login diagnostics). Auto-detect.
SYSTEMD_CFLAGS := $(shell pkg-config --cflags libsystemd 2>/dev/null)
SYSTEMD_LIBS := $(shell pkg-config --libs libsystemd 2>/dev/null)
ifneq ($(strip $(SYSTEMD_LIBS)),)
CPPFLAGS += $(SYSTEMD_CFLAGS) -DHAVE_SYSTEMD_LOGIN=1
LDLIBS += $(SYSTEMD_LIBS)
endif
# Sources/Objects
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))
DEPS := $(OBJS:.o=.d)
# ---- rules ----
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(OBJ_DIR):
mkdir -p $@
# ---- convenience modes ----
debug: CFLAGS := -Wall -Wextra -Wpedantic -O0 -g3
debug: LDFLAGS := -Wl,--as-needed
debug: clean all
release: CFLAGS := -Wall -Wextra -Wpedantic -O3 -DNDEBUG
release: LDFLAGS := -Wl,--as-needed -s
release: clean all
asan: CFLAGS := -Wall -Wextra -Wpedantic -O1 -g3 -fsanitize=address,undefined
asan: LDFLAGS := -fsanitize=address,undefined
asan: clean all
# ---- install ----
install: $(TARGET)
install -d -m 0755 $(DESTDIR)$(BINDIR)
install -o root -g root -m 4755 $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET)
install-systemd:
install -d -m 0755 $(DESTDIR)/etc/systemd/system
install -m 0644 system/systemd/fblogin@.service $(DESTDIR)/etc/systemd/system/fblogin@.service
install-pam:
install -d -m 0755 $(DESTDIR)/etc/pam.d
install -m 0644 system/pamd/fblogin $(DESTDIR)/etc/pam.d/fblogin
# Install custom banner used by pam_issue.so (skip gracefully if file missing)
install-issue:
@if [ -f "$(ISSUE_FILE)" ]; then \
echo "Installing banner: $(ISSUE_FILE) -> $(DESTDIR)$(ISSUE_DEST)"; \
install -d -m 0755 $(DESTDIR)/etc; \
install -m 0644 "$(ISSUE_FILE)" "$(DESTDIR)$(ISSUE_DEST)"; \
else \
echo "No banner file at $(ISSUE_FILE); skipping install-issue"; \
fi
install-man:
install -d -m 0755 $(DESTDIR)$(MAN1DIR)
install -m 0644 $(MAN1PAGES) $(DESTDIR)$(MAN1DIR)/
@# gzip if available, keep original if not
@if command -v gzip >/dev/null 2>&1; then \
gzip -9nf $(DESTDIR)$(MAN1DIR)/fblogin.1; \
fi
# Convenience: install everything we ship
install-all: install install-systemd install-pam install-issue install-man
# ---- uninstall ----
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(TARGET)
uninstall-systemd:
rm -f $(DESTDIR)/etc/systemd/system/fblogin@.service
rm -f $(DESTDIR)/etc/systemd/system/multi-user.target.wants/fblogin@*.service
uninstall-pam:
rm -f $(DESTDIR)/etc/pam.d/fblogin
uninstall-issue:
rm -f $(DESTDIR)$(ISSUE_DEST)
uninstall-man:
rm -f $(DESTDIR)$(MAN1DIR)/fblogin.1 $(DESTDIR)$(MAN1DIR)/fblogin.1.gz
uninstall-all: uninstall uninstall-systemd uninstall-pam uninstall-issue uninstall-man
# ---- clean ----
clean:
rm -rf $(OBJ_DIR) $(TARGET)
distclean: clean
.PHONY: all debug release asan install uninstall clean distclean \
install-systemd install-pam install-issue install-man install-all \
uninstall-systemd uninstall-pam uninstall-issue uninstall-man uninstall-all
-include $(DEPS)

236
README.md Normal file
View File

@@ -0,0 +1,236 @@
# fblogin: Framebuffer-Based Graphical Login for Debian
## **Introduction**
fblogin is a **lightweight, framebuffer-based graphical login manager** designed for Debian systems. It replaces the standard tty1 login prompt with a visually enhanced interface utilizing the **Linux framebuffer** for graphics, **PAM** for authentication, and optional **fingerprint verification via fprintd**. The system supports:
- **Direct framebuffer rendering** for a graphical login screen without X11/Wayland.
- **Pluggable Authentication Modules (PAM)** for authentication.
- **Fingerprint authentication** using `fprintd`.
- **Raw terminal input processing** for an interactive TUI-style experience.
- **Minimal system overhead**, replacing `getty` via `systemd` overrides.
This document provides an exhaustive technical breakdown of `fblogin`, covering **system dependencies, low-level memory operations, device interactions, authentication flow, and graphical rendering.**
## Note
> If you use ubuntu run at ur own risk.
---
## **System Dependencies**
### **1. Required Header Files**
`fblogin` requires both **standard C libraries** and **Linux-specific headers** for framebuffer and authentication control.
#### **Standard Libraries:**
- `<stddef.h>` Defines `size_t` and other fundamental types.
- `<stdint.h>` Fixed-width integer types.
- `<stdio.h>` Input/output functions.
- `<stdlib.h>` Memory allocation, system functions.
- `<string.h>` String handling utilities.
- `<time.h>` Timing functions.
#### **System-Level Libraries:**
- `<unistd.h>` Low-level system calls.
- `<sys/ioctl.h>` Device communication.
- `<sys/mman.h>` Memory mapping (`mmap`).
- `<sys/types.h>` Defines `pid_t`, `uid_t`, etc.
- `<sys/wait.h>` Process synchronization (`waitpid`).
#### **Framebuffer Graphics:**
- `<linux/fb.h>` Framebuffer device control.
- `"fb.h"` Custom framebuffer manipulation.
- `"font8x8_basic.h"` Bitmap font rendering.
#### **Authentication and User Management:**
- `<security/pam_appl.h>` PAM API for authentication.
- `"pam_auth.h"` Custom PAM authentication handler.
- `<pwd.h>` User account management.
- `<grp.h>` Group management.
#### **Terminal Input Handling:**
- `<termios.h>` Raw keyboard input control.
- `"input.h"` Custom keyboard processing.
#### **Process and Signal Management:**
- `<signal.h>` Signal handling.
- `"ui.h"` User interface logic.
### **2. System Calls Used**
The core functionality of `fblogin` relies on direct **syscalls** for process control, user authentication, and framebuffer manipulation.
#### **File and Memory Operations:**
- `open()` Opens `/dev/fb0`.
- `read()` Reads user input.
- `close()` Closes descriptors.
- `mmap()` Maps framebuffer memory.
- `munmap()` Unmaps memory.
#### **Process Control:**
- `fork()` Creates login subprocess.
- `waitpid()` Waits for completion.
- `setsid()` Creates a new session (daemon-like behavior).
#### **User and Permission Management:**
- `setuid()` Changes user ID.
- `setgid()` Changes group ID.
- `getuid()` Retrieves user ID.
#### **Framebuffer Operations:**
- `ioctl()` Direct framebuffer control.
#### **Authentication & Fingerprint Handling:**
- `pam_*` PAM API functions.
- `fprintd-verify` Fingerprint authentication via `fprintd`.
---
## Setup and Installation
1. **Clone or Download the repo**:
```Bash
git clone https://github.com/yourusername/fblogin.git
cd fblogin
```
2. **Install required Dependencies**:
on Debian you might install PAM and fprintd with:
```Bash
sudo apt-get update
sudo apt-get install libpam0g-dev fprintd libfprint-dev
```
3. **Build the project**
The project comes with a make file and a bash script
```Bash
make
```
For direct installation
```Bash
make install
```
For uninstallation
```Bash
make uninstall
```
4. Ensure your computer environment is propely setup
```Bash
./setup.sh
```
### Note
> Please create issues if any of these steps produce errors
> For your consideration: The program should be ran as root because it needs access to /dev/fb0 but ultimately it is designed to be ran by systemd
> For your consideration: The program has a strict isolation aspect that limits the running of this program to /dev/tty1 only. This can be changed in the code base to be a different tty or entirely commented out to run on all ttys and truly override the login command for your entire computer (NOT RECOMMENDED. THIS CODE IS STILL IN DEVELOPMENT).
---
## Usage
1. **Switch to TTY1:**
fblogin is configured to run only on /dev/tty1. You can switch to tty1 with:
```bash
Ctrl+Alt+F1
```
2. **Run fblogin as Root:**
```bash
sudo ./fblogin
```
3. **Login Process:**
* Username Entry:
* The login screen initially displays an input field for the username. Type your username.
* Toggle Fields with Tab:
* Press Tab to switch between the username and password fields.
* Fingerprint Authentication:
* If a fingerprint reader is detected (fprintd-list is available), the program will attempt fingerprint authentication as soon as the username is entered.
* If fingerprint authentication fails, it falls back to the password entry.
* Password Entry:
* When editing the password field, type your password and press Enter to authenticate.
* CtrlD:
* Pressing CtrlD will clear both fields and restart the login prompt.
* Post-Authentication:
* On successful authentication, fblogin will adjust tty permissions, set environment variables (such as HOME, USER, SHELL), and then launch the user's shell as a login shell.
### Troubleshooting
1. Framebuffer Initialization:
If you encounter an error initializing the framebuffer (/dev/fb0), ensure your user has the correct permissions or that the framebuffer device exists.
2. Input Device Issues:
If no input is detected, verify that the keyboard input library is properly set up and that /dev/tty1 is active.
3. Fingerprint Reader Issues:
Make sure that fprintd-list and fprintd-verify are installed, executable, and that your fingerprint sensor is supported by the fprintd library.
---
## **System Architecture**
### **1. Authentication Flow (PAM + fprintd)**
1. The program **initializes PAM** with `pam_start("fblogin", username, &conv, &pamh)`.
2. If **fingerprint authentication is enabled**, `fprintd-list` checks for stored fingerprints.
3. If **a fingerprint is found**, `fprintd-verify` is called and its output determines success.
4. If **fingerprint authentication fails**, the system **falls back to password login**.
5. Upon successful authentication, `pam_acct_mgmt()` verifies **account validity**.
6. If approved, `pam_open_session()` starts a session and `execv()` spawns the user's shell.
### **2. Framebuffer Rendering Pipeline**
1. The framebuffer device `/dev/fb0` is **opened with `open()`**.
2. `ioctl(FBIOGET_VSCREENINFO)` retrieves **display dimensions**.
3. `mmap()` maps framebuffer memory for **direct pixel access**.
4. The screen is **cleared to black** using `memset()`.
5. An **8×8 bitmap font** is rendered with `font8x8_basic.h` and **scaling factors**.
6. The **Debian logo (PFP) is drawn at the center** using per-pixel transformations.
7. The **cmatrix animation (if enabled) updates the background** dynamically.
### **3. Terminal Input Handling**
1. `termios` switches tty **to raw mode** (`ECHO` and `ICANON` disabled).
2. Keypresses are read **one character at a time** using `read()`.
3. **Backspace is handled manually** by clearing screen regions.
4. On `Enter`, the input is **validated and passed to PAM**.
5. If **Ctrl+C is pressed**, `fblogin` exits immediately.
### **4. System Integration (Replacing getty via systemd)**
1. `fblogin` replaces `getty` using a **systemd override**:
```ini
[Service]
ExecStart=/usr/local/bin/fblogin
```
2. `setsid()` ensures `fblogin` runs as a **session leader**.
3. After login, `execv()` **spawns the user shell** (e.g., `/bin/bash`).
---
## **Security Considerations**
- **Direct framebuffer access** is only possible for root; `fblogin` runs with **setuid-root**.
- **PAM sessions are properly closed** via `pam_close_session()`.
- **No passwords are stored**; authentication is handled via PAM modules.
- **SIGINT/SIGTERM handling** ensures graceful exit.
---
## **Future Improvements**
- **Dynamic resolution scaling** (currently, framebuffer dimensions are fixed at launch).
- **DRM/KMS support** to replace **legacy fbdev**.
- **Enhanced cmatrix animations** with smoother transitions.
---
## **Conclusion**
`fblogin` is a **high-performance framebuffer-based login manager** that integrates with PAM authentication and fingerprint recognition. It is a lightweight alternative to graphical login managers, optimized for minimalism and direct hardware interaction.

272
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,272 @@
# In-Depth Technical Document: PAM Authentication, fprintd, Linux Framebuffer, and fblogin
## Table of Contents
1. [Introduction](#introduction)
2. [PAM Authentication](#pam-authentication)
2.1 [Architecture and PAM Stack](#architecture-and-pam-stack)
2.2 [Module Types and Control Flags](#module-types-and-control-flags)
2.3 [Authentication Flow and Security Considerations](#authentication-flow-and-security-considerations)
2.4 [Code-Level Implementation Examples](#code-level-implementation-examples)
3. [fprintd Fingerprint Authentication Daemon](#fprintd)
3.1 [Architecture and D-Bus Integration](#architecture-and-d-bus-integration)
3.2 [Fingerprint Scanning Algorithms and Hardware Communication](#fingerprint-scanning-algorithms-and-hardware-communication)
3.3 [Interactions with PAM and Security Implications](#interactions-with-pam-and-security-implications)
3.4 [Code-Level Implementation Details](#fprintd-code-level-implementation-details)
4. [Linux Framebuffer Subsystem](#linux-framebuffer-subsystem)
4.1 [Architecture and Direct Memory Access](#architecture-and-direct-memory-access)
4.2 [Pixel Rendering and Graphics Formats](#pixel-rendering-and-graphics-formats)
4.3 [Device Node Interactions and Relevant System Calls](#device-node-interactions-and-relevant-system-calls)
4.4 [Performance Considerations and Optimization Strategies](#framebuffer-performance-considerations)
5. [fblogin: Integration of PAM, fprintd, and Framebuffer](#fblogin-integration)
5.1 [Overall Architecture and Authentication Flow](#overall-architecture-and-authentication-flow)
5.2 [System Calls, Kernel Interactions, and Process Transitions](#system-calls-and-kernel-interactions)
5.3 [UI Rendering and Aesthetic Integration](#ui-rendering-and-aesthetic-integration)
5.4 [Security Implications and Error Handling](#fblogin-security-and-error-handling)
6. [Comparative Analysis](#comparative-analysis)
6.1 [PAM-Based vs. Alternative Authentication Methods](#pam-vs-alternatives)
6.2 [Biometric Authentication: fprintd vs. Other Methods](#biometric-authentication-comparison)
6.3 [Frame Buffer UI Rendering vs. Graphical Toolkits](#framebuffer-vs-graphical-toolkits)
7. [Conclusion](#conclusion)
8. [References](#references)
---
## 1. Introduction
This document provides an in-depth technical analysis of several key Linux technologies and their integration within a custom login system—**fblogin**. fblogin is a frame bufferbased login program that integrates PAM for authentication, fprintd for optional biometric verification, and direct frame buffer manipulation for rendering a custom user interface. The following sections detail the theoretical background, system-level interactions, code-level implementation, and security and performance considerations for each component.
---
## 2. PAM Authentication
Pluggable Authentication Modules (PAM) is a highly flexible and modular authentication framework used on Linux and Unix-like systems.
### 2.1 Architecture and PAM Stack
- **Modular Design:**
PAM separates authentication policy from application logic by using a stack of modules. Each PAM service (e.g., login, ssh, sudo) defines a PAM configuration file (e.g., `/etc/pam.d/login`) that lists the modules to be invoked.
- **PAM Stack:**
The stack is processed in order, where each module can perform tasks such as authentication, account management, session setup, and password management. A typical PAM stack might include:
- **Authentication Modules:** (e.g., `pam_unix.so`, `pam_fprintd.so`)
Responsible for verifying user credentials.
- **Account Modules:** (e.g., `pam_unix.so`)
Validate account restrictions (e.g., login times, nologin file presence).
- **Session Modules:** (e.g., `pam_motd.so`, `pam_limits.so`)
Manage session initialization and cleanup.
- **Password Modules:** (e.g., `pam_cracklib.so`)
Enforce password quality during changes.
- **Control Flags:**
Each module line in the PAM configuration can be prefixed with control flags (`required`, `requisite`, `sufficient`, `optional`) that dictate how failures and successes propagate through the stack.
### 2.2 Module Types and Control Flags
- **Module Types:**
- **auth:** Authenticate a users identity.
- **account:** Validate account-specific constraints.
- **session:** Set up or tear down user sessions.
- **password:** Manage password changes.
- **Control Flags:**
- **required:** The module must succeed; failure does not immediately exit but is noted.
- **requisite:** Failure immediately terminates the stack.
- **sufficient:** Success is enough to continue without further checks.
- **optional:** Module result is not critical to overall success.
### 2.3 Authentication Flow and Security Considerations
- **Authentication Flow:**
1. The application calls `pam_start()`, specifying a service name (e.g., “fblogin”) and the target username.
2. The PAM stack is executed sequentially via `pam_authenticate()`.
3. The custom conversation function (implemented in fblogin) provides necessary input (e.g., password) to modules.
4. `pam_acct_mgmt()` checks for account restrictions.
5. On success, `pam_end()` is called to close the PAM session.
- **Security Considerations:**
- **Credential Handling:** Sensitive data such as passwords must be securely handled in memory and transmitted only over secure channels.
- **Module Isolation:** Each module runs independently; misconfigured modules could weaken overall security.
- **Policy Enforcement:** PAM allows for centralized policy enforcement; however, complexity can introduce potential attack vectors if modules are not properly maintained.
### 2.4 Code-Level Implementation Examples
- In fblogin, the conversation function in `pam_auth.c` dynamically allocates responses for PAM prompts and returns the supplied password. Real-world modules such as `pam_unix.so` rely on this mechanism to perform password verification using cryptographic hashes stored in `/etc/shadow`.
---
## 3. fprintd
fprintd is an open-source daemon providing fingerprint authentication services on Linux.
### 3.1 Architecture and D-Bus Integration
- **Daemon Architecture:**
fprintd runs as a background daemon and exposes fingerprint functionality over D-Bus, allowing client applications to perform enrollment, listing, verification, and deletion of fingerprints.
- **D-Bus Communication:**
fprintds client utilities (e.g., `fprintd-list`, `fprintd-verify`) use D-Bus to send commands and receive results. This abstraction enables hardware-agnostic interaction with fingerprint sensors.
### 3.2 Fingerprint Scanning Algorithms and Hardware Communication
- **Scanning Algorithms:**
fprintd typically interfaces with low-level drivers that implement fingerprint scanning algorithms. These algorithms process raw sensor data, extract minutiae, and compare fingerprint templates against stored data.
- **Hardware Communication:**
The daemon communicates with fingerprint hardware via vendor-provided drivers. It handles sensor initialization, image capture, and pre-processing before passing data to matching algorithms.
### 3.3 Interactions with PAM and Security Implications
- **PAM Integration:**
fprintd can be integrated with PAM (via modules such as `pam_fprintd.so`) to allow biometric authentication as part of the PAM stack.
- **Security Considerations:**
- **Template Protection:** Fingerprint templates must be securely stored, often in encrypted form.
- **Replay Attacks:** Measures such as challenge-response protocols help prevent replay attacks.
- **False Accept/Reject Rates:** Balancing usability with security is crucial; the matching algorithms thresholds are carefully tuned.
### 3.4 fprintd Code-Level Implementation Details
- fprintds source code (available on GitHub) shows detailed D-Bus interfaces, error handling routines, and callbacks for sensor events. Client utilities invoke these interfaces and interpret exit codes to determine authentication outcomes.
---
## 4. Linux Framebuffer Subsystem
The Linux framebuffer (fbdev) provides an abstraction for video output that bypasses high-level graphical systems.
### 4.1 Architecture and Direct Memory Access
- **Device Node Interface:**
The framebuffer is exposed as a device node (e.g., `/dev/fb0`). Applications can open, read, and write to this device.
- **Memory Mapping:**
The fbdev driver supports memory mapping via `mmap(2)`, allowing applications to access video memory directly. This permits low-latency, high-speed pixel manipulation.
### 4.2 Pixel Rendering and Graphics Formats
- **Pixel Formats:**
Framebuffer devices support various color depths and pixel formats (e.g., 16-bit RGB565, 24-bit RGB888, 32-bit ARGB). fblogin assumes a 32-bit format for simplicity.
- **Drawing Primitives:**
Low-level functions manipulate pixel data to draw lines, rectangles, and text. The 8×8 bitmap font (from the font8x8 project) is scaled by a factor defined at compile time.
### 4.3 Device Node Interactions and Relevant System Calls
- **Key System Calls:**
- `open(2)`: Opens the fbdev device.
- `ioctl(2)`: Used with commands such as `FBIOGET_VSCREENINFO` and `FBIOPAN_DISPLAY` to query and set display parameters.
- `mmap(2)`: Maps framebuffer memory for direct access.
- `msync(2)`: Ensures modifications to mapped memory are flushed to the display.
- **Kernel Interactions:**
The fbdev driver interacts directly with the DRM/KMS subsystem on modern hardware, though it presents a legacy interface to applications.
### 4.4 Performance Considerations and Optimization Strategies
- **Direct Memory Access:**
Direct framebuffer manipulation minimizes overhead, but careful attention must be paid to synchronization (using msync) and avoiding per-pixel system calls.
- **Hardware Acceleration:**
fbdev generally lacks hardware acceleration; thus, all rendering is done in software. This tradeoff is acceptable for low-resolution UIs such as login screens.
---
## 5. fblogin: Integration of PAM, fprintd, and Framebuffer
fblogin integrates the aforementioned technologies into a cohesive login system.
### 5.1 Overall Architecture and Authentication Flow
- **Startup:**
fblogin is designed to be launched (e.g., via a systemd override on tty1) as a root process that manages user login.
- **Authentication Flow:**
1. **Input Phase:** The program switches the terminal into raw mode and captures username and password keystrokes.
2. **Fingerprint Verification:** If fprintd is available and fingerprints are enrolled, the program attempts biometric authentication.
3. **PAM Verification:** Failing fingerprint authentication (or if not available), the program invokes PAM (via `pam_start()`, `pam_authenticate()`, and `pam_acct_mgmt()`) to verify the users credentials.
4. **Session Transition:** Upon successful authentication, the program cleans up and switches the process user ID (via setuid, setgid, initgroups) and finally execs the users shell.
### 5.2 System Calls and Kernel Interactions
- **Terminal and Input:**
fblogin uses termios to configure the terminal and read input character-by-character.
- **Framebuffer:**
The program opens `/dev/fb0`, uses `mmap` to map video memory, and renders the UI using pixel operations. It forces updates with `msync` and `ioctl(FBIOPAN_DISPLAY)`.
- **Process Management:**
For fingerprint authentication, the program forks and uses exec to run fprintd utilities. On successful authentication, it calls setsid, setuid, setgid, and execv to switch sessions.
### 5.3 UI Rendering and Aesthetic Integration
- **Base UI Composition:**
The UI is built in layers: a background (optionally animated as cmatrix), a title, a Debian spiral (PFP), and input boxes for username and password.
- **Dynamic Layout:**
Hardcoded offsets determine the vertical positions of the UI elements. Adjustments (e.g., lowering the text area) are controlled by specific constants.
- **Transparency and Animation:**
Input boxes are drawn with outline-only rectangles to allow the background to be visible. The cmatrix background is animated by updating an offset on each redraw.
### 5.4 Security Implications and Error Handling
- **Privilege Transition:**
Running as root for login presents inherent risks. fblogin carefully transitions to the users credentials after successful authentication.
- **Input Security:**
Passwords and biometric data are handled in memory only transiently. PAM abstracts much of the sensitive processing, but care must be taken to avoid memory leaks.
- **Error Handling:**
The program uses fallback strategies (e.g., falling back to password authentication if fingerprint verification fails) and signal handling to restart the login prompt when necessary.
---
## 6. Comparative Analysis
### 6.1 PAM-Based vs. Alternative Authentication Methods
- **PAM Advantages:**
Flexibility, modularity, and centralized policy enforcement. PAMs design allows administrators to mix and match modules (including biometric modules) without modifying application code.
- **Alternatives:**
Methods such as LDAP or Kerberos provide network-based authentication but do not offer the modularity of PAM. PAM can integrate with these systems via appropriate modules.
### 6.2 Biometric Authentication: fprintd vs. Other Methods
- **fprintd Advantages:**
Open-source, integrates with D-Bus, and provides a standardized interface to fingerprint sensors.
- **Comparative Technologies:**
Proprietary biometric systems or multi-modal biometric systems (face, iris) may offer higher accuracy or better integration with mobile devices, but fprintd is designed for the Linux desktop/server ecosystem.
### 6.3 Framebuffer-Based UI Rendering vs. Graphical Toolkits
- **Frame Buffer Pros:**
Low-level, minimal dependencies, works in non-graphical environments (e.g., TTYs). Ideal for simple UIs like login screens.
- **Graphical Toolkits:**
Systems like X11 or Wayland provide hardware acceleration and richer user interfaces but require more resources and are unsuitable for early boot or secure TTY contexts.
---
## 7. Conclusion
This document has provided a comprehensive technical overview of PAM authentication, the fprintd daemon, the Linux framebuffer subsystem, and their integration within fblogin—a custom login program for Debian systems. By exploring system calls, kernel interactions, code-level implementation details, security implications, and performance considerations, we have highlighted the complexity and elegance of modern Linux authentication and graphical systems. While fblogin represents a minimalistic approach, its design demonstrates how low-level system interfaces can be combined to build secure, modular, and extensible authentication systems.
---
## 8. References
- Linux-PAM Project Documentation and Source Code
- fprintd GitHub Repository and D-Bus API Documentation
- Linux Kernel Documentation on fbdev and DRM/KMS
- The Linux Programmers Manual (mmap(2), ioctl(2), termios(3))
- font8x8 Basic Font Repository Documentation
- systemd and getty@tty1.service Drop-In Override Documentation

46
docs/CHANGELOG.md Normal file
View File

@@ -0,0 +1,46 @@
# Changelog
All notable changes to fblogin will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] - 2025-02-26
### Added
- **Core fblogin Architecture:**
- Integration of PAM-based authentication with a custom conversation function.
- fprintd fingerprint authentication support with fallback to traditional password input.
- Linux framebuffer-based UI rendering with direct pixel manipulation, including an 8×8 bitmap font scaled for improved readability.
- UI elements such as a Debian spiral (PFP) graphic, outlined (transparent) username and password input boxes, and an optional animated cmatrix background.
- **TTY Enforcement:**
- Strict enforcement that fblogin only runs on `/dev/tty1` via a runtime check (using `ttyname(STDIN_FILENO)`) to prevent accidental use on other terminals.
- **Privilege Transition & TTY Permissions:**
- Code reordering to adjust tty ownership and permissions (using `chown` and `chmod`) while still running as root, ensuring that subsequent X server startup (e.g. via startx) works correctly.
- **Shell Invocation:**
- Improved method for launching the users shell as a proper login shell by passing the `--login` flag to ensure that user profiles (e.g., `~/.bash_profile` or `~/.zprofile`) are sourced.
- **System Integration:**
- Creation of a minimal PAM configuration file for fblogin (located in `/etc/pam.d/fblogin`).
- A systemd drop-in override for `getty@tty1.service` to replace the default login process with fblogin.
- **Bash Setup Script:**
- A comprehensive setup script (`setup_fblogin.sh`) that checks for required dependencies (installing any missing packages on Debian), performs an OS check (with custom messages for Ubuntu, macOS, Windows, and other Linux distros), verifies that required configuration files exist (creating the systemd override and PAM config if needed), and checks for the installation of the fblogin binary.
- The script uses colorized output for better user feedback and includes safety checks and interactive prompts for non-Debian systems.
### Changed
- **UI Layout Adjustments:**
- Lowered the position of text input boxes and ensured that the Debian spiral remains visible during fingerprint reading and welcome screens.
- **Error Handling Improvements:**
- Debug output from fingerprint authentication and welcome messages has been commented out to avoid cluttering the on-screen display.
- **Performance & Security:**
- Improved ordering of operations so that tty permissions are updated before dropping root privileges, thereby preventing X server permission errors.
### Fixed
- **TTY Detection:**
- Corrected tty detection logic to ensure that fblogin only executes on `/dev/tty1` (with proper messaging if run on any other terminal).
- **X Server Permissions:**
- Resolved issues where startx failed due to incorrect tty ownership and permissions by moving chown/chmod operations before privilege dropping.
## [1.0.0] - 2025-02-25
### Added
- Initial release of fblogin featuring:
- Basic PAM authentication integration.
- Linux framebuffer rendering for a minimalistic login UI.
- Fingerprint authentication via fprintd with fallback to password input.
- Fundamental privilege and session management to switch from root to the authenticated user.

37
docs/Contributing.md Normal file
View File

@@ -0,0 +1,37 @@
# Contributing to fblogin
Thank you for your interest in contributing to fblogin! We welcome contributions from the community. Please review the guidelines below before submitting issues or pull requests.
## Code of Conduct
Please adhere to the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/) in all interactions.
## How to Contribute
### Reporting Issues
- If you discover a bug or have a feature request, please first check the existing issues.
- When reporting a bug, include detailed steps to reproduce, logs (if applicable), and a description of your system configuration.
### Submitting Patches
1. **Fork** the repository and create a new branch (naming it descriptively, e.g., `feature/biometric-enhancement`).
2. **Code Standards:**
- Follow the existing coding style (indentation, naming, etc.).
- Write clear, concise commit messages.
3. **Testing:**
- Ensure your changes compile without warnings.
- Add tests if possible, and verify the functionality on a system with the relevant hardware (e.g., a fingerprint sensor, framebuffer).
4. **Documentation:**
- Update or add documentation as needed (see below for additional docs).
5. **Pull Request:**
- Submit your pull request to the main branch with a description of your changes and the problem they address.
## Documentation Contributions
- Contributions to improving the README, INSTALL instructions, or technical documentation (e.g., ARCHITECTURE.md) are welcome.
- If you propose a significant change, please discuss it in an issue first.
## Additional Resources
- [GitHub Flow](https://guides.github.com/introduction/flow/)
- [Contributor Covenant](https://www.contributor-covenant.org/)
Thank you for helping make fblogin better!

5
docs/TODO.md Normal file
View File

@@ -0,0 +1,5 @@
# TODO
## Priority 1:
Security audit

42
include/config.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef FBLOGIN_CONFIG_H
#define FBLOGIN_CONFIG_H
/* Build-time feature toggles (can be overridden via -D in CFLAGS) */
/* Enable fingerprint-first path via pam_fprintd (runtime can still disable with FBLOGIN_FINGERPRINT=0) */
#ifndef FBLOGIN_WITH_FPRINTD
#define FBLOGIN_WITH_FPRINTD 1
#endif
/* Enforce that user sessions must always succeed pam_open_session in production. */
#ifndef FBLOGIN_STRICT_SESSION
#define FBLOGIN_STRICT_SESSION 1
#endif
/* Auto-enable sd-login diagnostics if libsystemd is available (Makefile sets -DHAVE_SYSTEMD_LOGIN=1) */
#ifndef HAVE_SYSTEMD_LOGIN
#define HAVE_SYSTEMD_LOGIN 0
#endif
/* Default framebuffer device path */
#ifndef FBLOGIN_DEFAULT_FBDEV
#define FBLOGIN_DEFAULT_FBDEV "/dev/fb0"
#endif
/* Default: draw “cmatrix” background only if FBLOGIN_CMATRIX=1 is present */
#ifndef FBLOGIN_DEFAULT_CMATRIX
#define FBLOGIN_DEFAULT_CMATRIX 0
#endif
/* Greeter session env/class */
#ifndef FBLOGIN_GREETER_USER
#define FBLOGIN_GREETER_USER "root" /* override to a dedicated system user if you create one */
#endif
/* Log timestamp format: 1 = realtime (us), 0 = none */
#ifndef FBLOGIN_LOG_TIMESTAMP
#define FBLOGIN_LOG_TIMESTAMP 1
#endif
#endif /* FBLOGIN_CONFIG_H */

37
include/fb.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef FB_H
#define FB_H
#include <stdint.h>
#include <stddef.h>
typedef struct {
int fb_fd;
unsigned char *fb_ptr; /* mmap'ed */
size_t fb_size;
int width;
int height;
int bpp; /* bits per pixel: typically 16 or 32 */
int pitch; /* bytes per scanline */
} framebuffer_t;
/* Open /dev/fb0 (or fbdev if non-NULL), mmap, and fill fields. */
int fb_init(framebuffer_t *fb, const char *fbdev);
/* Unmap/close and zero out struct. */
void fb_close(framebuffer_t *fb);
/* Utility draws */
void fb_clear(framebuffer_t *fb, uint32_t argb);
void fb_draw_pixel(framebuffer_t *fb, int x, int y, uint32_t argb);
void fb_fill_rect(framebuffer_t *fb, int x, int y, int w, int h, uint32_t argb);
/* 8x8 glyph blit at native scale (1x). */
void fb_draw_text8x8(framebuffer_t *fb, int x, int y, const char *s, uint32_t argb);
/* Scaled text using FONT_SCALE (default 2). Matches your UIs expectations. */
void fb_draw_text(framebuffer_t *fb, int x, int y, const char *s, uint32_t argb);
/* 1px rectangle outline. */
void fb_draw_rect_outline(framebuffer_t *fb, int x, int y, int w, int h, uint32_t argb);
#endif /* FB_H */

152
include/font8x8_basic.h Normal file
View File

@@ -0,0 +1,152 @@
/**
* 8x8 monochrome bitmap fonts for rendering
* Author: Daniel Hepper <daniel@hepper.net>
*
* License: Public Domain
*
* Based on:
* // Summary: font8x8.h
* // 8x8 monochrome bitmap fonts for rendering
* //
* // Author:
* // Marcel Sondaar
* // International Business Machines (public domain VGA fonts)
* //
* // License:
* // Public Domain
*
* Fetched from: http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm
**/
// Constant: font8x8_basic
// Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin)
char font8x8_basic[128][8] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
{ 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
{ 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
{ 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
{ 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
{ 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
{ 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
{ 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
{ 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
{ 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
{ 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
{ 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
{ 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
{ 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
{ 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
{ 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
{ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
{ 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
{ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
{ 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
{ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
{ 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
{ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
{ 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
{ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
{ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
{ 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
{ 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
{ 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
{ 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
{ 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
{ 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
{ 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
{ 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
{ 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
{ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
{ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
{ 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
{ 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
{ 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
{ 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
{ 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
{ 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
{ 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
{ 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
{ 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
{ 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
{ 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
{ 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
{ 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
{ 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
{ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
{ 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
{ 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
{ 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
{ 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
{ 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
{ 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
{ 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
{ 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
{ 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
{ 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
{ 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
{ 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
{ 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
{ 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
{ 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
{ 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
{ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
{ 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
{ 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
{ 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
{ 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
{ 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
{ 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
{ 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
{ 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
{ 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
{ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
{ 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
{ 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
{ 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
{ 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
{ 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
{ 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
{ 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
{ 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
{ 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
{ 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F
};

11
include/input.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef INPUT_H
#define INPUT_H
/* Raw TTY input helpers (canonical off, echo off). */
int input_init(void);
int input_restore(void);
/* Blocking getch from /dev/tty. Returns byte [0..255] or -1 on error. */
int input_getchar(void);
#endif

48
include/pam_auth.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef PAM_AUTH_H
#define PAM_AUTH_H
#include <security/pam_appl.h>
typedef struct pam_ui_s {
void (*info)(const char *msg, void *user);
void (*error)(const char *msg, void *user);
void *user_ctx;
} pam_ui_t;
typedef struct {
pam_handle_t *pamh;
int session_opened; /* 1 if pam_open_session was called successfully */
} pam_session_t;
/* Full login path: authenticate + acct_mgmt (+chauthtok) + setcred + open_session (if open_session!=0)
tty_opt/xdisplay_opt are forwarded as PAM items; env is exported via pam_export_env().
Returns PAM_* code (PAM_SUCCESS on success). */
int pam_begin(const char *service,
const char *username,
const char *password,
const char *tty_opt,
const char *xdisplay_opt,
const pam_ui_t *ui,
int open_session,
pam_session_t *out);
/* Open a GREETER session only (no auth). Must be called in a process whose loginuid is unset.
Sets PAM_TTY and populates PAM env (XDG_SESSION_CLASS=greeter, XDG_SEAT, XDG_VTNR) before pam_open_session.
Returns PAM_* code. */
int pam_open_greeter_session(const char *service,
const char *tty_opt,
const char *xdisplay_opt,
const pam_ui_t *ui,
pam_session_t *out);
/* Export PAM env into current process. */
void pam_export_env(pam_session_t *ps);
/* Close session if opened; delete creds; end PAM. Safe to call multiple times. */
void pam_end_session(pam_session_t *ps);
/* Pretty for logs; never NULL. */
const char *pam_errstr(int code);
#endif

25
include/ui.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef UI_H
#define UI_H
#include <stdint.h>
#include "fb.h"
#ifndef FONT_SCALE
#define FONT_SCALE 2
#endif
/* Enable/disable cmatrix background animation. */
void ui_set_cmatrix(int flag);
/* Draw login screen. If focus_on_password!=0, password box is highlighted. */
void ui_draw_login(framebuffer_t *fb,
const char *username,
const char *password,
int focus_on_password);
void ui_draw_error(framebuffer_t *fb, const char *message);
void ui_draw_welcome(framebuffer_t *fb, const char *username);
void ui_draw_message(framebuffer_t *fb, const char *msg);
#endif /* UI_H */

6
include/version.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef VERSION_H
#define VERSION_H
#define FBLOGIN_VERSION "1.0.0"
#endif

135
man/fblogin.1 Normal file
View File

@@ -0,0 +1,135 @@
.TH FBLOGIN 1 "2025-02-25" "fblogin v1.0" "User Login Interface"
.SH NAME
fblogin \- frame bufferbased graphical login wrapper for Debian systems
.SH SYNOPSIS
.B fblogin
[\fI--cmatrix\fR]
.SH DESCRIPTION
\fbfblogin\fR is a minimalistic login replacement that operates directly on the Linux
frame buffer. It provides a visually enhanced login experience for tty1 by combining
Pluggable Authentication Modules (PAM) based user authentication with optional fingerprint
verification (via fprintd), direct pixel manipulation of the Linux framebuffer, and a custom
graphical user interface.
The program is designed to be lightweight and modular, interfacing directly with system
resources such as the frame buffer (/dev/fb0), termios for raw input, and PAM for secure
authentication. In addition, fblogin offers an optional animated "cmatrix" background and
displays a Debian spiral (PFP) graphic as a central visual element.
.SH FRAMEBUFFER AND GRAPHICS
fblogin uses the Linux framebuffer interface to draw its user interface. It opens the
framebuffer device, obtains screen resolution and color depth via FBIOGET_VSCREENINFO,
and maps the framebuffer memory into its address space using mmap(2). Custom routines are
provided to clear the screen, render individual pixels, draw filled and outline rectangles,
and render text using an 8×8 bitmap font scaled by a compile-time factor. The program also
forces screen updates via msync(2) and FBIOPAN_DISPLAY to ensure that changes are visible
on all connected displays.
.SH PAM AUTHENTICATION
The authentication mechanism in fblogin is based on PAM, which allows for pluggable,
policy-driven authentication. fblogin initializes a PAM session using pam_start(3) with the
service name \fBfblogin\fR and supplies a custom conversation function to pass non-interactive
password input to the underlying PAM modules. Calls to pam_authenticate(3) and
pam_acct_mgmt(3) ensure that the user credentials and account status are validated. This
approach abstracts the authentication details and ensures compliance with system security
policies.
.SH FPRINTD INTEGRATION
For systems equipped with fingerprint sensors, fblogin integrates with fprintd, the Linux
fingerprint daemon. Upon entering a username, the program invokes \fBfprintd-list\fR to
determine if any fingerprints are enrolled for the user. If fingerprints are detected, it
then calls \fBfprintd-verify\fR with the appropriate finger parameter. A successful
fingerprint verification bypasses the need for password entry. If fingerprint authentication
fails or is unavailable, fblogin gracefully falls back to traditional password verification.
.SH USER INTERFACE
The user interface of fblogin is constructed entirely in software using direct framebuffer
access. The interface comprises:
.IP "Base UI:"
A background which can be either static (black) or animated (cmatrix-style) when the
\fB--cmatrix\fR flag is provided. The base UI also includes a title—"Login for \fI<hostname>\fR"
—rendered with a bubble-style outline and a centrally positioned Debian spiral (PFP)
graphic.
.IP "Input Fields:"
Outlined (transparent) rectangular boxes for the username and password are rendered below
the spiral. The layout is adjustable via hardcoded vertical offsets, and the text is drawn
using a scaled 8×8 font.
.IP "Dynamic Behavior:"
Special keys (CtrlD, CtrlC, etc.) are trapped to allow for input editing and prompt
restarting. The programs internal state is updated dynamically based on user input.
.SH SYSTEM INTEGRATION
fblogin is intended to replace the default login prompt on tty1. In modern systems, this
is typically managed by systemd via getty@tty1.service. fblogin is designed to be launched
via a systemd dropin override which replaces the default ExecStart with the fblogin binary.
Upon successful authentication, the program transitions from the rootmanaged login
environment to the authenticated users session by performing a series of system calls
(setuid, setgid, initgroups, setsid, chdir, and finally execv to launch the users shell).
.SH SHORTCOMINGS AND FUTURE IMPROVEMENTS
While fblogin demonstrates a proof-of-concept for a frame bufferbased login system, it has
certain limitations:
.IP 1
The current cmatrix background is rudimentary and may be improved for smoother animation
and lower visual distraction.
.IP 1
The layout and scaling factors are hardcoded and may not be resolution-independent.
.IP 1
The program uses the legacy fbdev interface; future iterations may leverage DRM/KMS for
more robust display control.
.IP 1
Error handling and logging are minimal and could be enhanced for production environments.
.P
Future work may address these shortcomings, as well as add additional features such as
multi-factor authentication, theme support, and dynamic layout adaptation.
.SH FILES
.TP
\fB/etc/pam.d/fblogin\fR
A PAM configuration file must be provided to allow fblogin to use PAM for authentication.
.TP
\fB/dev/fb0\fR
The Linux framebuffer device which fblogin uses for rendering.
.TP
\fB/usr/bin/fprintd-list\fR and \fB/usr/bin/fprintd-verify\fR
Utilities used for fingerprint enrollment detection and verification.
.SH SEE ALSO
.BR pam(3),
.BR pam_appl(3),
.BR fprintd(1),
.BR termios(3),
.BR mmap(2),
.BR ioctl(2),
.BR systemd-getty@.service(5)
.SH DESCRIPTION OF SYSTEM CALLS AND LIBRARIES
fblogin makes extensive use of Unix system calls and standard C library functions. It uses:
.IP "- mmap(2)"
To map the frame buffer memory.
.IP "- ioctl(2)"
To query and update display parameters.
.IP "- termios(3)"
To manipulate terminal I/O modes for raw input.
.IP "- fork(2) and execv(3)"
To launch fingerprint verification processes and to eventually switch user sessions.
.IP "- PAM (pam_start, pam_authenticate, pam_acct_mgmt)"
For secure authentication.
.P
These calls are integrated in a manner that respects both the traditional Unix philosophy and modern
systemd-based session management.
.SH BUGS
While fblogin has been tested on several systems, differences in hardware and driver implementations
(e.g., fbdev vs. DRM/KMS) may result in unpredictable behavior on some platforms. Users are advised
to test thoroughly before deploying fblogin as a replacement for the standard login.
.SH AUTHOR ACKNOWLEDGEMENTS
(Author information omitted; see README for acknowledgements regarding opensource components such as
the 8×8 bitmap font.)
.SH COPYRIGHT
This manual page is distributed in the public domain.

154
setup.sh Executable file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env bash
# setup.sh — install & configure fblogin on tty1 with PAM/logind + logging
# Usage: ./setup.sh (will sudo when needed) or sudo ./setup.sh
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ME="${SUDO_USER:-${USER}}"
BOX() {
local msg="$1"
local w=${#msg}
local line
line="$(printf '═%.0s' $(seq 1 $((w+2))))"
echo -e "${line}╗\n║ ${msg} ║\n╚${line}"
}
LOGO() {
cat <<'ASCII'
'/¯¯¯¯/\¯¯¯¯\·. ''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|`·.' '/¯¯¯¯/\¯¯¯¯\`·.''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|`·.'/¯¯¯¯/`·.
|`·.·´`·|::'¯¯¯¯`·.\|·´`·.·´|:/____/`·.|`·.·´`·|:::' |·´`·.·´|::|`·.·´`·|:::|`·.·´`·|:::¯¯¯¯`·.\|.·´`·.·|:::|.·´`·.·|\¯¯¯¯\`·.
|`·.·´`·|\¯¯¯¯\·.¨ '|·´`·.·´|:\¯¯¯¯\·./|`·.·´`·|::|¯¯¯¯|`·.|·´`·.·´|::|`·.·´`·|:::|`·.·´`·|:\¯¯¯¯\·. ¨|.·´`·.·|:::|.·´`·.·|:|`·.·´`·|:::'
|____|::¯¯¯¯`·.\''\____\/____/`·.'\____\/____/`·.''\____\/____/`·.''\____\/____/`·. |____|:::|____|:|____|:::'
'`·.:::::`·.¨ ¨ ¨ ¨ ¨ ' `·.::::::::::::'`·./ `·.::::::::::::'`·./' '`·.:::::::::::::`·./' `·.::::::::::::'`·./ '`·.:::::`·.'`·.:::::`·`·.::::`·.
ASCII
}
need_root() {
if [[ $EUID -ne 0 ]]; then
echo "Re-running with sudo..."
exec sudo -E "$0" "$@"
fi
}
detect_pkgmgr() {
if command -v apt-get >/dev/null 2>&1; then echo apt; return; fi
if command -v dnf >/dev/null 2>&1; then echo dnf; return; fi
if command -v pacman >/dev/null 2>&1; then echo pacman; return; fi
echo none
}
install_deps() {
local pm; pm="$(detect_pkgmgr)"
case "$pm" in
apt)
local pkgs=(build-essential pkg-config libpam0g-dev libsystemd-dev)
BOX "Installing deps via apt: ${pkgs[*]}"
DEBIAN_FRONTEND=noninteractive apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}"
;;
dnf)
local pkgs=(gcc make pkgconf pam-devel systemd-devel)
BOX "Installing deps via dnf: ${pkgs[*]}"
dnf -y install "${pkgs[@]}"
;;
pacman)
local pkgs=(base-devel pam systemd pkgconf)
BOX "Installing deps via pacman: ${pkgs[*]}"
pacman --noconfirm -S --needed "${pkgs[@]}"
;;
*)
echo "Package manager not detected—please install build tools, PAM headers, and systemd headers manually."
;;
esac
}
build_as_user() {
# Build as $ME to keep artifacts owned by developer account
if [[ -n "${SUDO_USER:-}" ]]; then
BOX "Building as $ME"
su - "$ME" -c "cd '$PROJECT_ROOT' && make -j$(nproc)"
else
BOX "Building as $USER"
make -C "$PROJECT_ROOT" -j"$(nproc)"
fi
}
install_all() {
BOX "Installing (setuid root + systemd + PAM + issue)"
make -C "$PROJECT_ROOT" install-all
systemctl daemon-reload
}
setup_logging() {
BOX "Configuring /var/log/fblogin and logrotate"
install -d -m 0755 /var/log/fblogin
touch /var/log/fblogin/fblogin.log
chown root:adm /var/log/fblogin/fblogin.log
chmod 0640 /var/log/fblogin/fblogin.log
cat >/etc/logrotate.d/fblogin <<'ROT'
/var/log/fblogin/fblogin.log {
rotate 7
daily
missingok
notifempty
compress
create 0640 root adm
postrotate
systemctl kill -s SIGUSR1 fblogin@tty1.service 2>/dev/null || true
endscript
}
ROT
}
setup_systemd_dropin() {
BOX "Adding systemd drop-in for debug + log file"
install -d -m 0755 /etc/systemd/system/fblogin@.service.d
cat >/etc/systemd/system/fblogin@.service.d/override.conf <<'OVR'
[Service]
Environment=FBLOGIN_DEBUG=1
Environment=FBLOGIN_LOG_FILE=/var/log/fblogin/fblogin.log
OVR
systemctl daemon-reload
}
disable_gettys() {
BOX "Disabling conflicting gettys (tty1/tty2)"
systemctl disable --now getty@tty1.service 2>/dev/null || true
systemctl disable --now getty@tty2.service 2>/dev/null || true
}
enable_fblogin() {
BOX "Enabling fblogin@tty1"
systemctl enable --now fblogin@tty1.service
}
show_status() {
echo
BOX "Status"
systemctl status fblogin@tty1.service --no-pager || true
echo
echo "loginctl:"
loginctl || true
echo
[[ -f /etc/issue.fblogin ]] && { BOX "/etc/issue.fblogin"; sed -n '1,80p' /etc/issue.fblogin; }
echo
echo "Logs: /var/log/fblogin/fblogin.log"
}
main() {
LOGO
need_root "$@"
install_deps
build_as_user
install_all
setup_logging
setup_systemd_dropin
disable_gettys
enable_fblogin
show_status
BOX "Done. Switch to tty1 and test a login. Exiting."
}
main "$@"

161
src/fb.c Normal file
View File

@@ -0,0 +1,161 @@
#include "fb.h"
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#ifndef FONT_SCALE
#define FONT_SCALE 2
#endif
/* 8x8 font: do not modify per your requirement */
#include "font8x8_basic.h"
static uint32_t to_native(uint32_t argb, int bpp) {
uint8_t r = (argb >> 16) & 0xFF;
uint8_t g = (argb >> 8) & 0xFF;
uint8_t b = (argb ) & 0xFF;
if (bpp == 32) {
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
} else if (bpp == 16) {
return (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
}
return 0;
}
int fb_init(framebuffer_t *fb, const char *fbdev) {
if (!fb) return -1;
memset(fb, 0, sizeof(*fb));
const char *dev = fbdev ? fbdev : "/dev/fb0";
fb->fb_fd = open(dev, O_RDWR);
if (fb->fb_fd < 0) return -1;
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
if (ioctl(fb->fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0) goto fail;
if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0) goto fail;
fb->width = (int)vinfo.xres;
fb->height = (int)vinfo.yres;
fb->bpp = (int)vinfo.bits_per_pixel;
fb->pitch = (int)finfo.line_length;
fb->fb_size = (size_t)(fb->pitch * fb->height);
void *p = mmap(NULL, fb->fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb->fb_fd, 0);
if (p == MAP_FAILED) goto fail;
fb->fb_ptr = (unsigned char *)p;
return 0;
fail:
if (fb->fb_ptr && fb->fb_ptr != MAP_FAILED) munmap(fb->fb_ptr, fb->fb_size);
if (fb->fb_fd >= 0) close(fb->fb_fd);
memset(fb, 0, sizeof(*fb));
return -1;
}
void fb_close(framebuffer_t *fb) {
if (!fb) return;
if (fb->fb_ptr && fb->fb_ptr != MAP_FAILED) munmap(fb->fb_ptr, fb->fb_size);
if (fb->fb_fd >= 0) close(fb->fb_fd);
memset(fb, 0, sizeof(*fb));
}
void fb_clear(framebuffer_t *fb, uint32_t argb) {
if (!fb || !fb->fb_ptr) return;
if (fb->bpp == 32) {
uint32_t pix = to_native(argb, 32);
uint32_t *p = (uint32_t *)fb->fb_ptr;
size_t n = fb->fb_size / 4;
for (size_t i = 0; i < n; ++i) p[i] = pix;
} else if (fb->bpp == 16) {
uint16_t pix = (uint16_t)to_native(argb, 16);
uint16_t *p = (uint16_t *)fb->fb_ptr;
size_t n = fb->fb_size / 2;
for (size_t i = 0; i < n; ++i) p[i] = pix;
}
}
void fb_draw_pixel(framebuffer_t *fb, int x, int y, uint32_t argb) {
if (!fb || !fb->fb_ptr) return;
if ((unsigned)x >= (unsigned)fb->width || (unsigned)y >= (unsigned)fb->height) return;
uint8_t *base = fb->fb_ptr + y * fb->pitch + (x * fb->bpp / 8);
if (fb->bpp == 32) {
*(uint32_t*)base = to_native(argb, 32);
} else if (fb->bpp == 16) {
*(uint16_t*)base = (uint16_t)to_native(argb, 16);
}
}
void fb_fill_rect(framebuffer_t *fb, int x, int y, int w, int h, uint32_t argb) {
if (!fb || w <= 0 || h <= 0) return;
for (int j = 0; j < h; ++j)
for (int i = 0; i < w; ++i)
fb_draw_pixel(fb, x + i, y + j, argb);
}
void fb_draw_rect_outline(framebuffer_t *fb, int x, int y, int w, int h, uint32_t argb) {
if (!fb || w <= 0 || h <= 0) return;
for (int i = 0; i < w; ++i) {
fb_draw_pixel(fb, x + i, y, argb);
fb_draw_pixel(fb, x + i, y + h - 1, argb);
}
for (int j = 0; j < h; ++j) {
fb_draw_pixel(fb, x, y + j, argb);
fb_draw_pixel(fb, x + w - 1, y + j, argb);
}
}
void fb_draw_text8x8(framebuffer_t *fb, int x, int y, const char *s, uint32_t argb) {
if (!fb || !fb->fb_ptr || !s) return;
int dx = x;
for (; *s; ++s) {
unsigned char uc = (unsigned char)*s;
const unsigned char *glyph = (const unsigned char*)(const void*)font8x8_basic[uc];
for (int row = 0; row < 8; ++row) {
unsigned char bits = glyph[row];
for (int col = 0; col < 8; ++col) {
if (bits & (1u << col)) {
fb_draw_pixel(fb, dx + col, y + row, argb);
}
}
}
dx += 8;
}
}
void fb_draw_text(framebuffer_t *fb, int x, int y, const char *s, uint32_t argb) {
if (!fb || !s) return;
const int scale = (FONT_SCALE <= 0) ? 1 : FONT_SCALE;
int dx = x;
for (; *s; ++s) {
unsigned char uc = (unsigned char)*s;
const unsigned char *glyph = (const unsigned char*)(const void*)font8x8_basic[uc];
for (int row = 0; row < 8; ++row) {
unsigned char bits = glyph[row];
for (int col = 0; col < 8; ++col) {
if (bits & (1u << col)) {
for (int sy = 0; sy < scale; ++sy)
for (int sx = 0; sx < scale; ++sx)
fb_draw_pixel(fb, dx + col*scale + sx, y + row*scale + sy, argb);
}
}
}
dx += 8 * scale;
}
}

36
src/input.c Normal file
View File

@@ -0,0 +1,36 @@
#include "input.h"
#include <termios.h>
#include <unistd.h>
static struct termios saved;
static int inited = 0;
int input_init(void) {
if (inited) return 0;
if (!isatty(STDIN_FILENO)) return -1;
if (tcgetattr(STDIN_FILENO, &saved) != 0) return -1;
struct termios t = saved;
t.c_lflag &= ~(ICANON | ECHO);
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) != 0) return -1;
inited = 1;
return 0;
}
int input_restore(void) {
if (!inited) return 0;
int rc = tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved);
inited = 0;
return rc;
}
int input_getchar(void) {
unsigned char c;
ssize_t r = read(STDIN_FILENO, &c, 1);
if (r == 1) return (int)c;
return -1;
}

649
src/main.c Normal file
View File

@@ -0,0 +1,649 @@
#include "fb.h"
#include "input.h"
#include "pam_auth.h"
#include "ui.h"
#include "version.h"
#include "config.h"
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <libgen.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/reboot.h>
#include <linux/reboot.h>
#include <sys/prctl.h>
#include <termios.h>
#include <time.h>
#include <poll.h>
#if HAVE_SYSTEMD_LOGIN
#include <systemd/sd-login.h>
#endif
/* ---------- debug logger ---------- */
static FILE *g_log = NULL;
static int debug_enabled = 0;
static char g_log_path[512] = {0};
/* when nonzero, we pin logs to a specific file and never switch to per-user */
static int g_log_forced = 0;
static char g_log_forced_path[512] = {0};
static void ts_now(char *buf, size_t n) {
#if FBLOGIN_LOG_TIMESTAMP
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm;
localtime_r(&ts.tv_sec, &tm);
snprintf(buf, n, "%04d-%02d-%02d %02d:%02d:%02d.%06ld",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec/1000);
#else
(void)snprintf(buf, n, "-");
#endif
}
static void dlog(const char *fmt, ...) {
if (!debug_enabled || !g_log) return;
char t[64]; ts_now(t, sizeof(t));
pid_t pid = getpid(), ppid = getppid();
pid_t pgid = getpgid(0), sid = getsid(0);
va_list ap; va_start(ap, fmt);
fprintf(g_log, "[%s pid=%ld ppid=%ld pgid=%ld sid=%ld] ",
t, (long)pid, (long)ppid, (long)pgid, (long)sid);
vfprintf(g_log, fmt, ap);
fputc('\n', g_log);
fflush(g_log);
va_end(ap);
}
/* open an explicit path for logging (used by forced global log mode) */
static void log_open_path(const char *path) {
if (!debug_enabled || g_log || !path || !*path) return;
int fd = open(path, O_CREAT|O_APPEND|O_WRONLY, 0640);
if (fd < 0) return;
FILE *nf = fdopen(fd, "a");
if (!nf) { close(fd); return; }
g_log = nf;
snprintf(g_log_path, sizeof(g_log_path), "%s", path);
dlog("=== fblogin %s (pre-user) pid=%ld (forced log) ===", FBLOGIN_VERSION, (long)getpid());
}
static void log_open_preuser(void) {
if (!debug_enabled || g_log) return;
if (g_log_forced && g_log_forced_path[0]) {
log_open_path(g_log_forced_path);
if (g_log) return;
/* fall back to tmp if forced failed */
}
snprintf(g_log_path, sizeof(g_log_path), "/tmp/fblogin-%ld.log", (long)getpid());
g_log = fopen(g_log_path, "a");
if (g_log) dlog("=== fblogin %s (pre-user) pid=%ld ===", FBLOGIN_VERSION, (long)getpid());
}
static void log_switch_to_user(const char *user) {
if (!debug_enabled) return;
if (g_log_forced) {
/* keep writing to the forced global log; don't switch into $HOME */
dlog("log pinned to %s; skipping per-user switch", g_log_path[0] ? g_log_path : "(unset)");
return;
}
if (!user || !*user) return;
struct passwd *pw = getpwnam(user);
if (!pw || !pw->pw_dir || !*pw->pw_dir) { dlog("no home for %s", user); return; }
char path[512];
snprintf(path, sizeof(path), "%s/.fblogin.log", pw->pw_dir);
int fd = open(path, O_CREAT|O_APPEND|O_WRONLY, 0600);
if (fd < 0) {
dlog("open(%s): %s; fallback to /tmp", path, strerror(errno));
snprintf(path, sizeof(path), "/tmp/fblogin-%s.log", user);
fd = open(path, O_CREAT|O_APPEND|O_WRONLY, 0600);
if (fd < 0) { dlog("fallback open(%s): %s", path, strerror(errno)); return; }
}
if (geteuid() == 0) (void)fchown(fd, pw->pw_uid, pw->pw_gid);
FILE *nf = fdopen(fd, "a");
if (!nf) { close(fd); dlog("fdopen: %s", strerror(errno)); return; }
if (g_log) fclose(g_log);
g_log = nf;
snprintf(g_log_path, sizeof(g_log_path), "%s", path);
dlog("=== fblogin %s (user=%s) pid=%ld ===", FBLOGIN_VERSION, user, (long)getpid());
}
/* ---------- signals ---------- */
static volatile sig_atomic_t stop_flag = 0;
/* self-pipe used to wake the poll loop on SIGTERM */
static int s_stop_pipe[2] = { -1, -1 };
static void on_sigterm(int s){
(void)s;
stop_flag = 1;
if (s_stop_pipe[1] >= 0) {
/* async-signal-safe */
ssize_t _ = write(s_stop_pipe[1], "x", 1);
(void)_;
}
}
static int setup_stop_pipe(void) {
if (pipe(s_stop_pipe) != 0) return -1;
(void)fcntl(s_stop_pipe[0], F_SETFL, O_NONBLOCK);
(void)fcntl(s_stop_pipe[1], F_SETFL, O_NONBLOCK);
return 0;
}
/* ensure signals interrupt blocking syscalls (no SA_RESTART) */
static void install_signal_handlers(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* no SA_RESTART */
/* SIGTERM from systemd -> graceful shutdown via self-pipe */
sa.sa_handler = on_sigterm;
sigaction(SIGTERM, &sa, NULL);
/* user key combos shouldnt kill or suspend the DM */
struct sigaction ign;
memset(&ign, 0, sizeof(ign));
ign.sa_handler = SIG_IGN;
sigemptyset(&ign.sa_mask);
sigaction(SIGINT, &ign, NULL); /* ^C */
sigaction(SIGTSTP, &ign, NULL); /* ^Z */
sigaction(SIGQUIT, &ign, NULL); /* ^\ */
}
/* ---------- helpers ---------- */
static const char *best_tty_name(void) {
const char *t = ttyname(STDIN_FILENO);
if (t && *t) return t;
static char buf[L_ctermid];
if (ctermid(buf) && buf[0]) return buf;
return "/dev/tty1";
}
static int running_under_systemd_unit(void) {
const char *inv = getenv("INVOCATION_ID");
return (inv && *inv) ? 1 : 0;
}
/* return 1 if /proc/self/loginuid is unset (4294967295), 0 otherwise */
static int loginuid_is_unset(void) {
FILE *f = fopen("/proc/self/loginuid", "re");
if (!f) return 1;
unsigned long long v = 0ULL;
int ok = (fscanf(f, "%llu", &v) == 1);
fclose(f);
return ok ? (v == 4294967295ULL) : 1;
}
#if HAVE_SYSTEMD_LOGIN
static void log_sd_login_probe(const char *tty) {
uid_t uid = 0;
char *sid = NULL;
int r = sd_seat_get_active("seat0", &sid, &uid);
if (r < 0) {
dlog("sd-login: seat0 active query failed (%d)", r);
return;
}
char *tty_of = NULL, *cls = NULL, *svc = NULL;
(void)sd_session_get_tty(sid, &tty_of);
(void)sd_session_get_class(sid, &cls);
(void)sd_session_get_service(sid, &svc);
dlog("sd-login: active seat0 session=%s tty=%s class=%s service=%s (our tty=%s)",
sid, tty_of?tty_of:"?", cls?cls:"?", svc?svc:"?", tty?tty:"?");
free(tty_of); free(cls); free(svc); free(sid);
}
static void log_sd_self_context(const char *our_tty) {
char *sid = NULL, *cls = NULL, *seat = NULL, *sess_tty = NULL, *svc = NULL;
uid_t suid = (uid_t)-1;
if (sd_pid_get_session(0, &sid) >= 0) {
(void)sd_session_get_class(sid, &cls);
(void)sd_session_get_seat (sid, &seat);
(void)sd_session_get_tty (sid, &sess_tty);
(void)sd_session_get_uid (sid, &suid);
(void)sd_session_get_service(sid, &svc);
dlog("sd-login: self sid=%s class=%s seat=%s tty=%s uid=%ld service=%s (our tty=%s)",
sid?sid:"-", cls?cls:"-", seat?seat:"-", sess_tty?sess_tty:"-",
(long)suid, svc?svc:"-", our_tty?our_tty:"-");
} else {
dlog("sd-login: self not in a logind session (expected for DM greeter)");
}
free(sid); free(cls); free(seat); free(sess_tty); free(svc);
}
#endif
static int parse_vtnr(const char *tty) {
if (!tty) return -1;
if (strncmp(tty, "/dev/tty", 8) != 0) return -1;
const char *p = tty + 8;
if (!*p) return -1;
char *end = NULL;
long v = strtol(p, &end, 10);
return (end && *end == '\0' && v > 0 && v < 1000) ? (int)v : -1;
}
static void kill_pgrp(pid_t pgid) {
if (pgid <= 1) return;
kill(-pgid, SIGHUP);
usleep(200 * 1000);
kill(-pgid, SIGTERM);
for (int i = 0; i < 25; ++i) {
if (kill(-pgid, 0) != 0 && errno == ESRCH) return;
usleep(100 * 1000);
}
kill(-pgid, SIGKILL);
}
/* PAM UI -> framebuffer + log */
static void pam_ui_info_cb(const char *msg, void *user) {
framebuffer_t *fb = (framebuffer_t *)user;
dlog("PAM_INFO: %s", msg ? msg : "");
if (fb) ui_draw_message(fb, (msg && *msg) ? msg : "");
}
static void pam_ui_err_cb(const char *msg, void *user) {
framebuffer_t *fb = (framebuffer_t *)user;
dlog("PAM_ERROR: %s", msg ? msg : "");
if (fb) ui_draw_error(fb, (msg && *msg) ? msg : "authentication error");
}
/* Ensure controlling TTY in child; dup to stdio; drop to user; exec login shell */
static int run_login_shell_on_tty(const char *user, const char *tty_path, pam_session_t *ps) {
struct passwd *pw = getpwnam(user);
if (!pw) return -1;
pam_export_env(ps);
pid_t pid = fork();
if (pid < 0) return -1;
if (pid == 0) {
(void)prctl(PR_SET_PDEATHSIG, SIGTERM);
setsid();
int tfd = open(tty_path ? tty_path : "/dev/tty1", O_RDWR|O_CLOEXEC);
if (tfd >= 0) {
(void)ioctl(tfd, TIOCSCTTY, 0);
(void)dup2(tfd, STDIN_FILENO);
(void)dup2(tfd, STDOUT_FILENO);
(void)dup2(tfd, STDERR_FILENO);
if (tfd > 2) close(tfd);
}
if (initgroups(pw->pw_name, pw->pw_gid) != 0) _exit(125);
if (setgid(pw->pw_gid) != 0) _exit(126);
if (setuid(pw->pw_uid) != 0) _exit(127);
const char *shell = (pw->pw_shell && *pw->pw_shell) ? pw->pw_shell : "/bin/sh";
setenv("HOME", pw->pw_dir, 1);
setenv("SHELL", shell, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
if (!getenv("PATH")) setenv("PATH", "/usr/local/bin:/usr/bin:/bin", 1);
char *bn = basename((char *)shell);
size_t n = strlen(bn) + 2;
char *arg0 = (char *)malloc(n);
if (!arg0) _exit(129);
arg0[0] = '-';
memcpy(arg0 + 1, bn, n - 1);
execl(shell, arg0, "-l", (char*)NULL);
free(arg0);
_exit(128);
}
int status = 0;
while (waitpid(pid, &status, 0) < 0) {
if (errno == EINTR) continue;
break;
}
pid_t pg = getpgid(pid);
if (pg > 1) kill_pgrp(pg);
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
static void draw_and_pause(framebuffer_t *fb, const char *msg, unsigned ms) {
dlog("UI: %s", msg ? msg : "");
ui_draw_error(fb, msg);
usleep(ms * 1000);
}
/* Greeter PAM session keeper: open greeter session in a child, hold until pipe closes */
static pid_t greeter_keeper_pid = -1;
static int greeter_keeper_wfd = -1;
static int start_greeter_keeper(const char *service, const char *tty, pam_ui_t *pui) {
int pfd[2];
if (pipe(pfd) != 0) { dlog("pipe(): %s", strerror(errno)); return -1; }
pid_t pid = fork();
if (pid < 0) { dlog("fork(): %s", strerror(errno)); close(pfd[0]); close(pfd[1]); return -1; }
if (pid == 0) {
close(pfd[1]);
pam_session_t gs = {0};
int rc = pam_open_greeter_session(service, tty, ":0", pui, &gs);
dlog("greeter_session open rc=%d (%s), tty=%s", rc, pam_errstr(rc), tty);
if (rc != PAM_SUCCESS) _exit(10);
char buf[1];
while (1) {
ssize_t r = read(pfd[0], buf, 1);
if (r == 0) break;
if (r < 0 && errno == EINTR) continue;
if (r < 0) break;
}
pam_end_session(&gs);
dlog("greeter_session closed");
_exit(0);
}
close(pfd[0]);
greeter_keeper_pid = pid;
greeter_keeper_wfd = pfd[1];
dlog("greeter_keeper pid=%ld started", (long)pid);
return 0;
}
static void stop_greeter_keeper(void) {
if (greeter_keeper_wfd >= 0) {
close(greeter_keeper_wfd);
greeter_keeper_wfd = -1;
}
if (greeter_keeper_pid > 0) {
int st = 0;
(void)waitpid(greeter_keeper_pid, &st, 0);
dlog("greeter_keeper pid=%ld exit st=%d", (long)greeter_keeper_pid, st);
greeter_keeper_pid = -1;
}
}
/* ---------- main ---------- */
int main(int argc, char **argv) {
int dev_mode = 0;
int fp_enabled = FBLOGIN_WITH_FPRINTD;
int fp_max = 3; /* cosmetic; PAM module controls real retries */
/* allow --log-file=/path to also imply debug on */
/* (systemd env FBLOGIN_DEBUG=1 still supported/used below) */
const char *arg_log_file = NULL;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) debug_enabled = 1;
else if (!strcmp(argv[i], "--dev")) dev_mode = 1;
else if (!strcmp(argv[i], "--no-fp")) fp_enabled = 0;
else if (!strncmp(argv[i], "--fp-max=", 9)) {
int v = atoi(argv[i] + 9);
if (v > 0 && v < 10) fp_max = v;
}
else if (!strncmp(argv[i], "--log-file=", 11)) {
arg_log_file = argv[i] + 11;
if (arg_log_file && *arg_log_file) {
strncpy(g_log_forced_path, arg_log_file, sizeof(g_log_forced_path)-1);
g_log_forced_path[sizeof(g_log_forced_path)-1] = '\0';
g_log_forced = 1;
debug_enabled = 1; /* make --log-file alone sufficient */
}
}
}
if (!debug_enabled) {
const char *env = getenv("FBLOGIN_DEBUG");
if (env && *env == '1') debug_enabled = 1;
}
{
const char *envfp = getenv("FBLOGIN_FINGERPRINT");
if (envfp && *envfp == '0') fp_enabled = 0;
}
/* environment override for global log path */
if (!g_log_forced) {
const char *env_log = getenv("FBLOGIN_LOG_FILE");
if (env_log && *env_log) {
strncpy(g_log_forced_path, env_log, sizeof(g_log_forced_path)-1);
g_log_forced_path[sizeof(g_log_forced_path)-1] = '\0';
g_log_forced = 1;
/* do not force debug here; we expect FBLOGIN_DEBUG=1 in service */
}
}
if (!dev_mode) {
const char *envd = getenv("FBLOGIN_DEV");
if (envd && *envd == '1') dev_mode = 1;
}
if (running_under_systemd_unit()) dev_mode = 0;
if (debug_enabled) log_open_preuser();
(void)setup_stop_pipe(); /* best-effort; still fine if it fails */
/* install handlers after pipe so SIGTERM can wake poll() */
install_signal_handlers();
if (!isatty(STDIN_FILENO)) {
fprintf(stderr, "fblogin: stdin is not a TTY\n");
return 1;
}
const char *tty = best_tty_name();
int vtnr = parse_vtnr(tty);
if (geteuid() != 0) {
fprintf(stderr, "fblogin must be setuid root. Run `sudo make install`.\n");
return 1;
}
dlog("Start fblogin %s on TTY=%s (vtnr=%d), EUID=%d, mode=%s, fp_max=%d",
FBLOGIN_VERSION, tty, vtnr, (int)geteuid(), dev_mode ? "dev" : "prod", fp_max);
#if HAVE_SYSTEMD_LOGIN
log_sd_login_probe(tty);
log_sd_self_context(tty);
#endif
dlog("loginuid unset? %d", loginuid_is_unset());
framebuffer_t fb;
if (fb_init(&fb, FBLOGIN_DEFAULT_FBDEV) != 0) {
fprintf(stderr, "fblogin: fb init failed\n");
return 1;
}
if (input_init() != 0) {
fb_close(&fb);
fprintf(stderr, "fblogin: input init failed\n");
return 1;
}
const char *env_cmx = getenv("FBLOGIN_CMATRIX");
if (env_cmx ? (*env_cmx == '1') : FBLOGIN_DEFAULT_CMATRIX) ui_set_cmatrix(1);
pam_ui_t pui = (pam_ui_t){ .info = pam_ui_info_cb, .error = pam_ui_err_cb, .user_ctx = &fb };
/* Context guard: dont behave like a DM inside a user session. */
int enable_greeter = 1;
#if HAVE_SYSTEMD_LOGIN
{
char *sid=NULL, *cls=NULL, *sess_tty=NULL;
if (sd_pid_get_session(0, &sid) >= 0) {
(void)sd_session_get_class(sid, &cls);
(void)sd_session_get_tty(sid, &sess_tty);
dlog("Context guard: in-session sid=%s class=%s tty=%s", sid?sid:"?", cls?cls:"?", sess_tty?sess_tty:"?");
if (!dev_mode) {
ui_draw_error(&fb, "Already running inside a user session.\nStart fblogin via systemd on a free VT (e.g., tty1).");
usleep(1200 * 1000);
input_restore();
fb_close(&fb);
if (g_log) fclose(g_log);
free(sid); free(cls); free(sess_tty);
return 2;
} else {
enable_greeter = 0;
dlog("Dev-mode: disabling greeter; running without greeter session.");
}
}
free(sid); free(cls); free(sess_tty);
}
#endif
if (enable_greeter) (void)start_greeter_keeper("fblogin", tty, &pui);
char user[64] = {0};
char pass[128] = {0};
size_t ulen = 0, plen = 0;
enum { FOCUS_USER = 0, FOCUS_PASS = 1 } focus = FOCUS_USER;
while (!stop_flag) {
ui_draw_login(&fb, user, pass, (focus == FOCUS_PASS));
/* Wait for either keyboard input or a stop request */
int ch = -1;
struct pollfd fds[2];
fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN;
fds[1].fd = s_stop_pipe[0]; fds[1].events = POLLIN;
int pr = poll(fds, (s_stop_pipe[0] >= 0) ? 2 : 1, -1);
if (pr < 0) {
if (errno == EINTR) continue;
break;
}
if (s_stop_pipe[0] >= 0 && (fds[1].revents & POLLIN)) {
/* drain pipe, then exit loop */
char buf[64];
while (read(s_stop_pipe[0], buf, sizeof(buf)) > 0) {}
break;
}
if (fds[0].revents & POLLIN) {
ch = input_getchar();
} else {
/* nothing useful; re-check stop_flag */
continue;
}
if (ch < 0) continue;
if (ch == '\t') { focus = (focus == FOCUS_USER) ? FOCUS_PASS : FOCUS_USER; dlog("TAB -> focus=%s", focus==FOCUS_PASS?"PASS":"USER"); continue; }
if (ch == 0x15) { if (focus==FOCUS_PASS){ memset(pass,0,sizeof(pass)); plen=0; dlog("Ctrl-U: clear password"); } else { memset(user,0,sizeof(user)); ulen=0; dlog("Ctrl-U: clear username"); } continue; }
if (ch == 0x12) { memset(user,0,sizeof(user)); ulen=0; memset(pass,0,sizeof(pass)); plen=0; focus=FOCUS_USER; dlog("Ctrl-R: reset all"); continue; }
if (ch == 0x04) { /* Ctrl-D -> restart greeter (do NOT reboot) */
dlog("Ctrl-D: restart greeter");
memset(user,0,sizeof(user)); ulen=0; memset(pass,0,sizeof(pass)); plen=0; focus=FOCUS_USER;
ui_draw_login(&fb, user, pass, 0);
continue;
}
if (ch == '\n' || ch == '\r') {
dlog("Enter on focus=%s, ulen=%zu, plen=%zu", focus==FOCUS_PASS?"PASS":"USER", ulen, plen);
if (focus == FOCUS_USER) {
if (ulen == 0) { draw_and_pause(&fb, "enter username", 800); continue; }
if (plen == 0) {
if (debug_enabled) log_switch_to_user(user);
#if FBLOGIN_WITH_FPRINTD
if (fp_enabled) {
dlog("Attempt fingerprint-first for user=%s (empty password)", user);
pam_session_t ps = (pam_session_t){0};
int rc = pam_begin("fblogin", user, "", tty, ":0", &pui, 1, &ps);
if (rc == PAM_SUCCESS) {
dlog("Fingerprint path OK");
ui_draw_welcome(&fb, user);
if (enable_greeter) stop_greeter_keeper();
int rcsh = run_login_shell_on_tty(user, tty, &ps);
pam_end_session(&ps);
dlog("Shell exit rc=%d", rcsh);
if (enable_greeter) (void)start_greeter_keeper("fblogin", tty, &pui);
memset(user, 0, sizeof(user)); ulen = 0; focus = FOCUS_USER;
draw_and_pause(&fb, "session ended; returning to greeter", 800);
continue;
}
dlog("Fingerprint path FAILED (%s)", pam_errstr(rc));
draw_and_pause(&fb, "fingerprint failed; enter password", 900);
focus = FOCUS_PASS;
continue;
}
#endif
dlog("Fingerprint disabled; focusing password");
focus = FOCUS_PASS;
draw_and_pause(&fb, "enter password", 300);
continue;
}
}
if (focus == FOCUS_PASS) {
if (ulen == 0) { draw_and_pause(&fb, "enter username first", 600); focus = FOCUS_USER; continue; }
if (plen == 0) { draw_and_pause(&fb, "enter password or use fingerprint", 700); continue; }
if (debug_enabled) log_switch_to_user(user);
}
dlog("Password auth attempt for user=%s", user);
pam_session_t ps = (pam_session_t){0};
int rc = pam_begin("fblogin", user, pass, tty, ":0", &pui, 1, &ps);
memset(pass, 0, sizeof(pass)); plen = 0;
if (rc != PAM_SUCCESS) {
#if !FBLOGIN_STRICT_SESSION
if (rc == PAM_SESSION_ERR || rc == PAM_PERM_DENIED) {
dlog("Session open failed (%s). Dev-mode fallback AUTH-ONLY.", pam_errstr(rc));
pam_session_t ps2 = (pam_session_t){0};
rc = pam_begin("fblogin", user, "", tty, ":0", &pui, 0, &ps2);
if (rc == PAM_SUCCESS) {
dlog("AUTH-ONLY OK");
ui_draw_welcome(&fb, user);
if (enable_greeter) stop_greeter_keeper();
int rcsh = run_login_shell_on_tty(user, tty, &ps2);
pam_end_session(&ps2);
dlog("Shell exit rc=%d", rcsh);
if (enable_greeter) (void)start_greeter_keeper("fblogin", tty, &pui);
memset(user,0,sizeof(user)); ulen=0; focus=FOCUS_USER;
draw_and_pause(&fb, "session ended; returning to greeter", 800);
continue;
}
}
#endif
dlog("Password path FAILED (%s)", pam_errstr(rc));
if (rc == PAM_SESSION_ERR)
draw_and_pause(&fb, "PAM session failed (TTY busy or started inside a session).", 1200);
else
draw_and_pause(&fb, "authentication failed", 1000);
focus = FOCUS_PASS;
continue;
}
dlog("Password path OK");
ui_draw_welcome(&fb, user);
if (enable_greeter) stop_greeter_keeper();
int rcsh = run_login_shell_on_tty(user, tty, &ps);
pam_end_session(&ps);
dlog("Shell exit rc=%d", rcsh);
if (enable_greeter) (void)start_greeter_keeper("fblogin", tty, &pui);
memset(user, 0, sizeof(user)); ulen = 0; focus = FOCUS_USER;
draw_and_pause(&fb, "session ended; returning to greeter", 800);
continue;
}
if (ch == 0x7F || ch == 0x08) { if (focus==FOCUS_PASS){ if (plen) pass[--plen]=0; } else { if (ulen) user[--ulen]=0; } continue; }
if (ch >= 0x20 && ch <= 0x7E) { if (focus==FOCUS_PASS){ if (plen+1<sizeof(pass)){ pass[plen++]=(char)ch; pass[plen]=0; } } else { if (ulen+1<sizeof(user)){ user[ulen++]=(char)ch; user[ulen]=0; } } }
}
(void)input_restore();
fb_close(&fb);
if (enable_greeter) stop_greeter_keeper();
if (s_stop_pipe[0] >= 0) { close(s_stop_pipe[0]); s_stop_pipe[0] = -1; }
if (s_stop_pipe[1] >= 0) { close(s_stop_pipe[1]); s_stop_pipe[1] = -1; }
if (g_log) fclose(g_log);
return 0;
}

203
src/pam_auth.c Normal file
View File

@@ -0,0 +1,203 @@
#include "pam_auth.h"
#include <security/pam_misc.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
typedef struct conv_ctx_s {
const char *password; /* may be NULL or "" */
const pam_ui_t *ui; /* may be NULL */
} conv_ctx_t;
static int conv_fn(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr) {
if (num_msg <= 0) return PAM_CONV_ERR;
struct pam_response *r = calloc((size_t)num_msg, sizeof(*r));
if (!r) return PAM_BUF_ERR;
conv_ctx_t *ctx = (conv_ctx_t *)appdata_ptr;
const char *password = ctx ? ctx->password : NULL;
const pam_ui_t *ui = ctx ? ctx->ui : NULL;
for (int i = 0; i < num_msg; i++) {
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON: {
const char *ans = (password && *password) ? password : "";
r[i].resp = strdup(ans ? ans : "");
r[i].resp_retcode = 0;
break;
}
case PAM_TEXT_INFO:
if (ui && ui->info && msg[i]->msg) ui->info(msg[i]->msg, ui->user_ctx);
r[i].resp = NULL;
r[i].resp_retcode = 0;
break;
case PAM_ERROR_MSG:
if (ui && ui->error && msg[i]->msg) ui->error(msg[i]->msg, ui->user_ctx);
r[i].resp = NULL;
r[i].resp_retcode = 0;
break;
default:
free(r);
return PAM_CONV_ERR;
}
}
*resp = r;
return PAM_SUCCESS;
}
static void set_item_if(pam_handle_t *pamh, int item_type, const char *val) {
if (val && *val) (void)pam_set_item(pamh, item_type, (const void *)val);
}
static void set_env_if(pam_handle_t *pamh, const char *k, const char *v) {
if (!pamh || !k || !*k || !v) return;
char *kv = NULL;
size_t len = strlen(k) + 1 + strlen(v) + 1;
kv = malloc(len);
if (!kv) return;
snprintf(kv, len, "%s=%s", k, v);
(void)pam_putenv(pamh, kv);
free(kv);
}
int pam_begin(const char *service,
const char *username,
const char *password,
const char *tty_opt,
const char *xdisplay_opt,
const pam_ui_t *ui,
int open_session,
pam_session_t *out) {
if (!service || !username || !out) return PAM_SYSTEM_ERR;
out->pamh = NULL;
out->session_opened = 0;
conv_ctx_t cctx = { .password = password ? password : "", .ui = ui };
struct pam_conv conv = { .conv = conv_fn, .appdata_ptr = (void *)&cctx };
pam_handle_t *pamh = NULL;
int rc = pam_start(service, username, &conv, &pamh);
if (rc != PAM_SUCCESS) goto fail;
set_item_if(pamh, PAM_TTY, tty_opt);
set_item_if(pamh, PAM_XDISPLAY, xdisplay_opt);
rc = pam_authenticate(pamh, 0);
if (rc != PAM_SUCCESS) goto fail;
rc = pam_acct_mgmt(pamh, 0);
if (rc == PAM_NEW_AUTHTOK_REQD)
rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
if (rc != PAM_SUCCESS) goto fail;
rc = pam_setcred(pamh, PAM_ESTABLISH_CRED);
if (rc != PAM_SUCCESS) goto fail;
if (open_session) {
rc = pam_open_session(pamh, 0);
if (rc != PAM_SUCCESS) {
(void)pam_setcred(pamh, PAM_DELETE_CRED);
goto fail;
}
out->session_opened = 1;
}
out->pamh = pamh;
return PAM_SUCCESS;
fail:
if (ui && ui->error) ui->error(pam_strerror(pamh, rc), (void*)ui->user_ctx);
if (pamh) (void)pam_end(pamh, rc);
return rc ? rc : PAM_SYSTEM_ERR;
}
int pam_open_greeter_session(const char *service,
const char *tty_opt,
const char *xdisplay_opt,
const pam_ui_t *ui,
pam_session_t *out) {
if (!service || !out) return PAM_SYSTEM_ERR;
out->pamh = NULL;
out->session_opened = 0;
/* Greeter: no auth conv needed, but PAM wants a conv. Provide a no-op one. */
conv_ctx_t cctx = { .password = "", .ui = ui };
struct pam_conv conv = { .conv = conv_fn, .appdata_ptr = (void *)&cctx };
pam_handle_t *pamh = NULL;
/* Use the current euid's name; typical DM greeter runs as root. */
uid_t e = geteuid();
struct passwd *pw = getpwuid(e);
const char *uname = pw && pw->pw_name ? pw->pw_name : "root";
int rc = pam_start(service, uname, &conv, &pamh);
if (rc != PAM_SUCCESS) goto fail;
set_item_if(pamh, PAM_TTY, tty_opt);
set_item_if(pamh, PAM_XDISPLAY, xdisplay_opt);
/* Set greeter class + seat/vtnr via PAM env so pam_systemd can see it. */
set_env_if(pamh, "XDG_SESSION_CLASS", "greeter");
/* Seat and VT can be inferred by pam_systemd, but pass when known. */
if (tty_opt && !strncmp(tty_opt, "/dev/tty", 8)) {
const char *n = tty_opt + 8;
if (*n) set_env_if(pamh, "XDG_VTNR", n);
}
set_env_if(pamh, "XDG_SEAT", "seat0");
rc = pam_open_session(pamh, 0);
if (rc != PAM_SUCCESS) goto fail;
out->pamh = pamh;
out->session_opened = 1;
return PAM_SUCCESS;
fail:
if (ui && ui->error) ui->error(pam_strerror(pamh, rc), (void*)ui->user_ctx);
if (pamh) (void)pam_end(pamh, rc);
return rc ? rc : PAM_SYSTEM_ERR;
}
void pam_export_env(pam_session_t *ps) {
if (!ps || !ps->pamh) return;
char **envlist = pam_getenvlist(ps->pamh);
if (!envlist) return;
for (char **p = envlist; *p; ++p) {
putenv(*p); /* intentionally leak into process env */
}
}
void pam_end_session(pam_session_t *ps) {
if (!ps || !ps->pamh) return;
if (ps->session_opened) (void)pam_close_session(ps->pamh, 0);
(void)pam_setcred(ps->pamh, PAM_DELETE_CRED);
(void)pam_end(ps->pamh, PAM_SUCCESS);
ps->pamh = NULL;
ps->session_opened = 0;
}
const char *pam_errstr(int code) {
switch (code) {
case PAM_SUCCESS: return "PAM_SUCCESS";
case PAM_OPEN_ERR: return "PAM_OPEN_ERR";
case PAM_SYMBOL_ERR: return "PAM_SYMBOL_ERR";
case PAM_SERVICE_ERR: return "PAM_SERVICE_ERR";
case PAM_SYSTEM_ERR: return "PAM_SYSTEM_ERR";
case PAM_BUF_ERR: return "PAM_BUF_ERR";
case PAM_PERM_DENIED: return "PAM_PERM_DENIED";
case PAM_AUTH_ERR: return "PAM_AUTH_ERR";
case PAM_CRED_INSUFFICIENT: return "PAM_CRED_INSUFFICIENT";
case PAM_AUTHINFO_UNAVAIL: return "PAM_AUTHINFO_UNAVAIL";
case PAM_USER_UNKNOWN: return "PAM_USER_UNKNOWN";
case PAM_MAXTRIES: return "PAM_MAXTRIES";
case PAM_NEW_AUTHTOK_REQD: return "PAM_NEW_AUTHTOK_REQD";
case PAM_ACCT_EXPIRED: return "PAM_ACCT_EXPIRED";
case PAM_SESSION_ERR: return "PAM_SESSION_ERR";
default: return "PAM_OTHER";
}
}

208
src/ui.c Normal file
View File

@@ -0,0 +1,208 @@
#include "ui.h"
#include "fb.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#ifndef FONT_SCALE
#define FONT_SCALE 2
#endif
/* --- style/colors --- */
#define COLOR_BG 0x000000
#define COLOR_TITLE_FG 0xFFFFFF
#define COLOR_TITLE_OUT 0x000000
#define COLOR_TEXT 0xFFFFFF
#define COLOR_INPUT 0x00FF00
#define COLOR_BOX 0xFFFFFF
#define COLOR_BOX_FOCUS 0x2A7FFF
#define COLOR_ERROR 0xFF0000
#define COLOR_MATRIX 0x001100
/* Global flag for cmatrix animation. */
static int ui_use_cmatrix = 0;
void ui_set_cmatrix(int flag) { ui_use_cmatrix = flag; }
/* Force display update via FBIOPAN_DISPLAY */
static void fb_update_display(framebuffer_t *fb) {
if (!fb) return;
struct fb_var_screeninfo vinfo;
if (ioctl(fb->fb_fd, FBIOGET_VSCREENINFO, &vinfo) == 0) {
vinfo.xoffset = 0;
vinfo.yoffset = 0;
(void)ioctl(fb->fb_fd, FBIOPAN_DISPLAY, &vinfo);
}
}
/* Moving cmatrix background (if enabled) */
static void ui_draw_cmatrix_background(framebuffer_t *fb) {
static int offset = 0;
static int seeded = 0;
const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int charCount = (int)strlen(charset);
int step = 8 * FONT_SCALE;
if (!seeded) { srand((unsigned)time(NULL)); seeded = 1; }
for (int y = 0; y < fb->height; y += step) {
for (int x = -offset; x < fb->width; x += step) {
char c = charset[rand() % charCount];
char buf[2] = { c, '\0' };
fb_draw_text(fb, x, y, buf, COLOR_MATRIX);
}
}
offset = (offset + 1) % step;
}
/* Bubble text with outline */
static void ui_draw_bubble_text(framebuffer_t *fb, int x, int y,
const char *text, uint32_t color, uint32_t outline_color) {
static const int offs[8][2] = {
{-1,-1},{0,-1},{1,-1},
{-1, 0}, {1, 0},
{-1, 1},{0, 1},{1, 1}
};
for (int i = 0; i < 8; i++)
fb_draw_text(fb, x + offs[i][0], y + offs[i][1], text, outline_color);
fb_draw_text(fb, x, y, text, color);
}
/* Debian spiral ASCII */
static void ui_draw_pfp(framebuffer_t *fb, int x, int y) {
static const char *debian_spiral[] = {
" #%%% ### ",
" %%%%%%%%%%%%%%%% ",
" %%%%%%% %%%%%% ",
" %%%% #%%%% ",
" %%% %%%% ",
" #%% # %%% ",
" %% %# %%# ",
"%%% % #%% ",
"%% # #%# ",
"%% #% %% ",
"%% % %% ",
"%%# % %% #% ",
" %% %%%%%%%%% ",
" #%%% ",
" %%% ",
" %%% ",
" %% ",
" %%# ",
" #%# ",
" %# "
};
int lines = (int)(sizeof(debian_spiral) / sizeof(debian_spiral[0]));
for (int i = 0; i < lines; i++)
fb_draw_text(fb, x, y + i * 8 * FONT_SCALE, debian_spiral[i], 0xFF0000);
}
/* Base UI (background, title, spiral) */
static void ui_draw_base(framebuffer_t *fb, int base_offset_y) {
if (ui_use_cmatrix) ui_draw_cmatrix_background(fb);
else fb_clear(fb, COLOR_BG);
char hostname[128] = {0};
(void)gethostname(hostname, sizeof(hostname));
char title[256];
snprintf(title, sizeof(title), "Login for %s", hostname[0] ? hostname : "localhost");
int title_width = (int)strlen(title) * 8 * FONT_SCALE;
int title_x = (fb->width - title_width) / 2;
int title_y = base_offset_y;
ui_draw_bubble_text(fb, title_x, title_y, title, COLOR_TITLE_FG, COLOR_TITLE_OUT);
/* Spiral below title */
int spiral_width = 29 * 8 * FONT_SCALE;
int spiral_x = (fb->width - spiral_width) / 2;
int spiral_y = title_y + 60;
ui_draw_pfp(fb, spiral_x, spiral_y);
}
/* Public: login screen; focus flag picks highlighted box */
void ui_draw_login(framebuffer_t *fb,
const char *username,
const char *password,
int focus_on_password) {
if (!fb) return;
ui_draw_base(fb, 20);
const int box_width = 200;
const int box_height = 30 * FONT_SCALE;
const int username_box_x = (fb->width - box_width) / 2;
const int username_box_y = 450;
const int password_box_x = username_box_x;
const int password_box_y = username_box_y + box_height + 10;
uint32_t user_box_color = focus_on_password ? COLOR_BOX : COLOR_BOX_FOCUS;
uint32_t pass_box_color = focus_on_password ? COLOR_BOX_FOCUS : COLOR_BOX;
fb_draw_rect_outline(fb, username_box_x - 5, username_box_y - 5,
box_width + 10, box_height + 10, user_box_color);
fb_draw_rect_outline(fb, password_box_x - 5, password_box_y - 5,
box_width + 10, box_height + 10, pass_box_color);
fb_draw_text(fb, username_box_x, username_box_y - 20, "Username:", COLOR_TEXT);
fb_draw_text(fb, username_box_x, username_box_y + 5,
(username && *username) ? username : "", COLOR_INPUT);
fb_draw_text(fb, password_box_x, password_box_y - 20, "Password:", COLOR_TEXT);
char masked[256] = {0};
if (password) {
size_t len = strlen(password);
if (len >= sizeof(masked)) len = sizeof(masked) - 1;
memset(masked, '*', len);
masked[len] = '\0';
}
fb_draw_text(fb, password_box_x, password_box_y + 5, masked, COLOR_INPUT);
(void)msync(fb->fb_ptr, fb->fb_size, MS_SYNC);
fb_update_display(fb);
}
/* Public: error message */
void ui_draw_error(framebuffer_t *fb, const char *message) {
if (!fb) return;
ui_draw_base(fb, 20);
fb_draw_text(fb, 10, fb->height - 40,
message ? message : "error", COLOR_ERROR);
(void)msync(fb->fb_ptr, fb->fb_size, MS_SYNC);
fb_update_display(fb);
}
/* Public: welcome screen */
void ui_draw_welcome(framebuffer_t *fb, const char *username) {
if (!fb) return;
ui_draw_base(fb, 20);
char welcome[256];
snprintf(welcome, sizeof(welcome), "Welcome, %s!",
(username && *username) ? username : "user");
int text_width = (int)strlen(welcome) * 8 * FONT_SCALE;
int x = (fb->width - text_width) / 2;
int y = 420;
fb_draw_text(fb, x, y, welcome, COLOR_TEXT);
(void)msync(fb->fb_ptr, fb->fb_size, MS_SYNC);
fb_update_display(fb);
}
/* Public: generic message screen */
void ui_draw_message(framebuffer_t *fb, const char *msg) {
if (!fb) return;
ui_draw_base(fb, 20);
const char *m = (msg && *msg) ? msg : "";
int text_width = (int)strlen(m) * 8 * FONT_SCALE;
int x = (fb->width - text_width) / 2;
int y = 420;
fb_draw_text(fb, x, y, m, COLOR_TEXT);
(void)msync(fb->fb_ptr, fb->fb_size, MS_SYNC);
fb_update_display(fb);
}

View File

@@ -0,0 +1,8 @@
************************************************************
* Welcome to The Klarch *
* Authorized use only — activity logged. *
* *
* If you are reading this it is too late *
* I know who you are... *
************************************************************

29
system/pamd/fblogin Normal file
View File

@@ -0,0 +1,29 @@
# fblogin — PAM stack for framebuffer display manager + greeter
# One file handles both:
# - Greeter process: we call pam_open_session() only (no auth); pam_systemd marks class=greeter.
# - User login: full auth + session.
# --- Auth (user login path) ---
auth optional pam_issue.so issue=/etc/issue.fblogin
auth [success=1 default=ignore] pam_fprintd.so max_tries=3
auth requisite pam_nologin.so
auth required pam_unix.so nullok
# --- Account checks ---
account required pam_unix.so
# --- Password updates (if a module requests it) ---
password required pam_unix.so nullok sha512 shadow
# --- Session (runs both for greeter + user) ---
session required pam_limits.so
session required pam_env.so readenv=1
session optional pam_loginuid.so
session optional pam_lastlog2.so
# pam_systemd registers a proper logind session over D-Bus.
# XDG_SESSION_CLASS=greeter is provided by the program for greeter child.
session optional pam_systemd.so
# Keep at end so we still get a traditional session when needed.
session required pam_unix.so

View File

@@ -0,0 +1,34 @@
[Unit]
Description=Framebuffer Login on %I
Documentation=man:systemd-logind.service(8) man:logind.conf(5)
After=systemd-user-sessions.service systemd-logind.service plymouth-quit-wait.service
Wants=systemd-logind.service
Conflicts=getty@%i.service
ConditionPathExists=/dev/%I
[Service]
Environment=FBLOGIN_DEBUG=1
Environment=FBLOGIN_LOG_FILE=/var/log/fblogin/fblogin.log
# Bind to a real TTY
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
StandardInput=tty
StandardOutput=tty
StandardError=tty
# Program does PAM (auth + session) itself; do NOT set PAMName here.
# Production defaults (no debug spam; strict PAM session)
Environment=FBLOGIN_DEBUG=0
Environment=FBLOGIN_STRICT_SESSION=1
# Optional: enable matrix background by flipping to 1
# Environment=FBLOGIN_CMATRIX=1
ExecStart=/usr/local/bin/fblogin
Type=simple
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.target