Initial public release: fresh history
This commit is contained in:
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal 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
21
LICENSE
Normal 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
124
Makefile
Normal 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
236
README.md
Normal 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.
|
||||
* Ctrl‑D:
|
||||
* Pressing Ctrl‑D 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
272
docs/ARCHITECTURE.md
Normal 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 buffer–based 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 user’s 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:**
|
||||
fprintd’s 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 algorithm’s thresholds are carefully tuned.
|
||||
|
||||
### 3.4 fprintd Code-Level Implementation Details
|
||||
|
||||
- fprintd’s 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 user’s 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 user’s 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 user’s 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. PAM’s 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 Programmer’s 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
46
docs/CHANGELOG.md
Normal 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 user’s 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
37
docs/Contributing.md
Normal 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
5
docs/TODO.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# TODO
|
||||
|
||||
## Priority 1:
|
||||
|
||||
Security audit
|
||||
42
include/config.h
Normal file
42
include/config.h
Normal 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
37
include/fb.h
Normal 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 UI’s 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
152
include/font8x8_basic.h
Normal 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
11
include/input.h
Normal 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
48
include/pam_auth.h
Normal 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
25
include/ui.h
Normal 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
6
include/version.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
#define FBLOGIN_VERSION "1.0.0"
|
||||
|
||||
#endif
|
||||
135
man/fblogin.1
Normal file
135
man/fblogin.1
Normal file
@@ -0,0 +1,135 @@
|
||||
.TH FBLOGIN 1 "2025-02-25" "fblogin v1.0" "User Login Interface"
|
||||
.SH NAME
|
||||
fblogin \- frame buffer–based 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 (Ctrl‑D, Ctrl‑C, etc.) are trapped to allow for input editing and prompt
|
||||
restarting. The program’s 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 drop‑in override which replaces the default ExecStart with the fblogin binary.
|
||||
Upon successful authentication, the program transitions from the root‐managed login
|
||||
environment to the authenticated user’s session by performing a series of system calls
|
||||
(setuid, setgid, initgroups, setsid, chdir, and finally execv to launch the user’s shell).
|
||||
|
||||
.SH SHORTCOMINGS AND FUTURE IMPROVEMENTS
|
||||
While fblogin demonstrates a proof-of-concept for a frame buffer–based 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 open‑source components such as
|
||||
the 8×8 bitmap font.)
|
||||
|
||||
.SH COPYRIGHT
|
||||
This manual page is distributed in the public domain.
|
||||
|
||||
154
setup.sh
Executable file
154
setup.sh
Executable 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
161
src/fb.c
Normal 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
36
src/input.c
Normal 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
649
src/main.c
Normal 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 shouldn’t 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: don’t 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
203
src/pam_auth.c
Normal 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
208
src/ui.c
Normal 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);
|
||||
}
|
||||
|
||||
8
system/issue/issue.fblogin
Normal file
8
system/issue/issue.fblogin
Normal 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
29
system/pamd/fblogin
Normal 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
|
||||
|
||||
34
system/systemd/fblogin@.service
Normal file
34
system/systemd/fblogin@.service
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user