From 7870f668f3a6ee795ddb9fdcd76de43678832e71 Mon Sep 17 00:00:00 2001 From: cylgom Date: Tue, 23 May 2017 20:12:12 +0200 Subject: [PATCH] initial commit --- LICENSE.md | 13 ++ README.md | 73 +++++++ ly.service | 15 ++ makefile | 14 ++ src/config.h | 45 ++++ src/desktop.c | 148 +++++++++++++ src/desktop.h | 25 +++ src/lang.h | 49 +++++ src/login.c | 588 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/login.h | 24 +++ src/main.c | 213 ++++++++++++++++++ src/ncui.c | 166 ++++++++++++++ src/ncui.h | 34 +++ src/utils.c | 164 ++++++++++++++ src/utils.h | 15 ++ 15 files changed, 1586 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100755 ly.service create mode 100755 makefile create mode 100755 src/config.h create mode 100644 src/desktop.c create mode 100644 src/desktop.h create mode 100755 src/lang.h create mode 100644 src/login.c create mode 100644 src/login.h create mode 100644 src/main.c create mode 100644 src/ncui.c create mode 100644 src/ncui.h create mode 100644 src/utils.c create mode 100644 src/utils.h diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3d3e235 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a1e4e6 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +### Ly - ncurses display manager - v0.0.1 + +![ly screenshot](https://cloud.githubusercontent.com/assets/5473047/26368771/ef4aa91a-3ff2-11e7-8f3e-1b3e3ea67e49.png "ly on st") + +This is a lightweight login/display manager for linux made with ncurses. +It is based on linux-pam and systemd and was developped in good old C99. +This program was designed to be simple, modular and highly configurable. +Ly was made for crazy people. If you're not insane, run away. + +### Configuration and installation +Ly tries not to reinvent the wheel and uses linux-utils and xorg-xinit +instead of providing heavy, incomplete and outdated implementations. +Make sure all the following tools and libraries are available on your +distribution before going further: +- systemd +- linux-pam +- ncurses +- tput +- libX11 (required if you wish to use X) +- xorg-xinit (required if you wish to use X) +- xorg-xauth (required if you wish to use X) +- mcookie (required if you wish to use X) + +You can now configure Ly by editing "src/config.h". Modify "ly.service" +as well if you changed the default tty, then build the executable: +``` +make +``` +Check if it works on the tty you configured (default is tty2). You can +also run it in terminal emulators, but desktop environments won't start. +``` +sudo build/ly +``` +Then, install Ly and the systemd service file: +``` +sudo make install +``` +Now enable the systemd service to make it spawn on startup: +``` +sudo systemctl enable ly.service +``` +If you need to switch between ttys after Ly's start you also have to +disable getty on Ly's tty to prevent "login" from spawning on top of it: +``` +sudo systemctl disable getty@tty2.service +``` + +### Additionnal informations +The name "Ly" is a tribute to the fairy from the game Rayman. +Ly was tested by oxodao, who is some seriously awesome dude. (you rock!) +I wish to thank ncurses, linux-pam, X11 and systemd developers for not +providing anything close to a reference or documentation. + + ▌ + ▌▐ + █▄▄ ▐ █ █▄▄ █▄▄ █▄▄ ▄▄█ ▄▄█▌ ▄▄▄▄ + ▐████▄█ █▌ ▐████▄ ▐████▄ ▐████▄ ▄████ ▄█████ █████▀ + ▐██████ ▐█ ▐█████▌ ▐█████▌ ▐█████▌ ▐█████▌ █████▌ █████ + ████▐██ █▌█████▀ ████ ▀ ████▐██ ██▐████ ▐█████ ▐████▌ + ███▌▐██ ▐█████▌ ███▌ ███▌▐██ ██ ████ ████▌▌██▐██▌ + ███▌ ██ ▐ █████▌ ▐███▌ ███▌▐██ ██ ████ ████ ▐█ ▐██▌ + ███▌ █ ▐████ ▐███▌ █ ███▌▄▄▄ ██ ████ ████ ▐▌ ███▌ + ▐██▌ ▄██▌▐████ ████▌ ██ ▐██▌▐██ ██ ███▌ ███▌ ▐ ▐████ + ▐███▄████▌ ████ ▐████████ ▐███▐██ ██▐███▌ ▐███▌ ▐████▌ + █████▀ ▐▌ ████ █████████▄ ▐████▀ ▀████▌ ▐██▌ ████▌ + ▀▀▀ ▌ ████ ▐██▀▀ ▀▀ ▄█▀▀ ▀▀█▄ ███ ▀███ + ▐ ████ ▄█▀ ▄ ▐█ █▌▄██ ▀▀▄ + ▌ █████▀ █ ▀█▄▄ ▄▄█▀▄█▀ + ▄▄▄▀ ▐███▀ ▐██▄ ▀▀▀▀▀▀▀▀▀ ▄█▀ + █▀ ▐█▀ ▀████▄▄▄▄▄▄▄▄▄▄▄▄▄█▀▀ + █▄ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▄▄▄▄████████████▄▄ + ▀▀██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄████████▀▀▀▀▀▀ ▀▀▀▄ + ▀▀▀▀▀▀▀▀▀▀▀▀ ▄▀ diff --git a/ly.service b/ly.service new file mode 100755 index 0000000..1d0c7ee --- /dev/null +++ b/ly.service @@ -0,0 +1,15 @@ +[Unit] +Description=ly ncurses display manager +After=systemd-user-sessions.service plymouth-quit-wait.service +After=getty@tty2.service + +[Service] +Type=simple +ExecStart=/bin/ly +StandardInput=tty +TTYPath=/dev/tty2 +TTYReset=yes +TTYVHangup=yes + +[Install] +Alias=display-manager.service diff --git a/makefile b/makefile new file mode 100755 index 0000000..3aefe39 --- /dev/null +++ b/makefile @@ -0,0 +1,14 @@ +ly : + mkdir -p ./build + cc -std=c99 -pedantic -Wall -I src -L/usr/lib/security -lform -lncurses -lpam -lpam_misc -lX11 -l:pam_loginuid.so -o build/ly src/main.c src/utils.c src/login.c src/ncui.c src/desktop.c + +install : ly + cp build/ly /bin/ly + mkdir -p /etc/ly + cp ly.service /lib/systemd/system/ly.service + ln -sf /usr/lib/security/pam_loginuid.so /lib/pam_loginuid.so + +all : install + +clean : + rm -rf build/ly diff --git a/src/config.h b/src/config.h new file mode 100755 index 0000000..52f2787 --- /dev/null +++ b/src/config.h @@ -0,0 +1,45 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* UI */ +#define LY_MARGIN_H 3 +#define LY_MARGIN_V 2 + +/* array sizes */ +#define LY_LIM_LINE_FILE 256 +#define LY_LIM_LINE_CONSOLE 256 +#define LY_LIM_PATH 256 +#define LY_LIM_CMD 256 + +/* behaviour */ +#define LY_CFG_SAVE "/etc/ly/ly.save" +#define LY_CFG_READ_SAVE 1 +#define LY_CFG_WRITE_SAVE 1 +#define LY_CFG_CLR_USR 0 +/* 0-10 */ +#define LY_CFG_FCHANCE 7 +#define LY_CFG_AUTH_TRIG 10 +#define LY_CFG_FPS 60 +#define LY_CFG_FMAX 100 + +/* commands */ +#define LY_CMD_X "/usr/bin/X" +#define LY_CMD_TPUT "/usr/bin/tput" +#define LY_CMD_HALT "/sbin/shutdown" +#define LY_CMD_XINITRC "exec /bin/bash --login ~/.xinitrc " +#define LY_CMD_MCOOKIE "/usr/bin/mcookie" +#define LY_XAUTHORITY ".lyxauth" + +/* paths */ +#define LY_PATH "/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/env" +#define LY_PATH_XSESSIONS "/usr/share/xsessions" + +/* console */ +#define LY_CONSOLE_DEV "/dev/console" +#define LY_CONSOLE_TERM "TERM=linux" +#define LY_CONSOLE_TTY 2 + +/* pam breaks if you don't set the service name at "login" */ +#define LY_SERVICE_NAME "login" + +#endif /* _CONFIG_H_ */ diff --git a/src/desktop.c b/src/desktop.c new file mode 100644 index 0000000..6acbf24 --- /dev/null +++ b/src/desktop.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include + +#include "lang.h" +#include "config.h" +#include "utils.h" +#include "desktop.h" + +#define LY_XSESSION_EXEC "Exec=" +#define LY_XSESSION_NAME "Name=" + +/* returns a list containing all the DE for all the display servers */ +struct delist_t* list_de(void) +{ + /* xsession */ + FILE* file; + DIR* dir; + struct dirent* dir_info; + /* buffers */ + char path[LY_LIM_PATH]; + char* name; + char* command; + /* de list */ + int count = 2; + struct delist_t* list = init_list(count); + /* reads xorg's desktop environments entries */ + dir = opendir(LY_PATH_XSESSIONS); + + /* exits if the folder can't be read */ + if(!dir) + { + error_print(LY_ERR_DELIST); + end_list(list, count); + return NULL; + } + + /* cycles through the folder */ + while((dir_info = readdir(dir))) + { + /* gets rid of ".", ".." and ".*" files */ + if((dir_info->d_name)[0] == '.') + { + continue; + } + + /* opens xsession file */ + snprintf(path, sizeof(path), "%s/%s", LY_PATH_XSESSIONS, + dir_info->d_name); + file = fopen(path, "r"); + + /* stops the entire procedure if the file can't be read */ + if(!file) + { + error_print(LY_ERR_DELIST); + closedir(dir); + break; + } + + /* reads xsession file */ + name = NULL; + command = NULL; + get_props(file, &name, &command); + + /* frees memory when the entries are incomplete */ + if((name && !command) || (!name && command)) + { + free(name ? name : command); + break; + } + + /* adds the new entry to the list */ + list->names = realloc(list->names, + (count + 2) * (sizeof * (list->names))); + list->names[count] = name; + list->props = realloc(list->props, + (count + 1) * (sizeof * (list->props))); + list->props[count].cmd = command; + list->props[count].type = xorg; + ++count; + fclose(file); + } + + closedir(dir); + end_list(list, count); + return list; +} + +/* writes default entries to the DE list */ +struct delist_t* init_list(int count) +{ + struct delist_t* list = malloc(sizeof * list); + list->names = malloc((count + 1) * (sizeof * (list->names))); + list->names[0] = strdup(LY_LANG_SHELL); + list->names[1] = strdup(LY_LANG_XINITRC); + list->props = malloc(count * (sizeof * (list->props))); + list->props[0].cmd = strdup(""); + list->props[0].type = shell; + list->props[1].cmd = strdup(LY_CMD_XINITRC); + list->props[1].type = xorg; + return list; +} + +void end_list(struct delist_t* list, int count) +{ + list->names[count] = NULL; + list->count = count; +} + +/* extracts the name and command of a DE from its .desktop file */ +void get_props(FILE* file, char** name, char** command) +{ + char line[LY_LIM_LINE_FILE]; + + while(fgets(line, sizeof(line), file)) + { + if(!strncmp(LY_XSESSION_NAME, line, (sizeof(LY_XSESSION_NAME) - 1))) + { + *name = strdup(trim(line + (sizeof(LY_XSESSION_NAME) - 1))); + } + else if(!strncmp(LY_XSESSION_EXEC, line, + (sizeof(LY_XSESSION_EXEC) - 1))) + { + *command = strdup(trim(line + (sizeof(LY_XSESSION_EXEC) - 1))); + } + + if(*name && *command) + { + break; + } + } +} + +void free_list(struct delist_t* list) +{ + int count; + + for(count = 0; count < list->count; ++count) + { + free(list->names[count]); + free(list->props[count].cmd); + } + + free(list->names); + free(list->props); + free(list); +} diff --git a/src/desktop.h b/src/desktop.h new file mode 100644 index 0000000..2468ee5 --- /dev/null +++ b/src/desktop.h @@ -0,0 +1,25 @@ +#ifndef _DESKTOP_H_ +#define _DESKTOP_H_ + +enum deserv_t {shell, xorg, wayland}; + +struct deprops_t +{ + char* cmd; + enum deserv_t type; +}; + +struct delist_t +{ + char** names; + struct deprops_t* props; + int count; +}; + +struct delist_t* init_list(int count); +void end_list(struct delist_t* list, int count); +void get_props(FILE* file, char** name, char** command); +struct delist_t* list_de(void); +void free_list(struct delist_t* list); + +#endif /* _DESKTOP_H_ */ diff --git a/src/lang.h b/src/lang.h new file mode 100755 index 0000000..c4f8412 --- /dev/null +++ b/src/lang.h @@ -0,0 +1,49 @@ +#ifndef _LANG_H_ +#define _LANG_H_ + +/* UI strings */ +#define LY_LANG_GREETING "Welcome to ly !" +#define LY_LANG_VALID_CREDS "Logged In" +#define LY_LANG_LOGOUT "Logged out" +#define LY_LANG_SHELL "shell" +#define LY_LANG_XINITRC "xinitrc" +#define LY_LANG_SHUTDOWN "shutdown" +#define LY_LANG_REBOOT "reboot" +#define LY_LANG_LOGIN "login : " +#define LY_LANG_PASSWORD "password : " + +/* ioctl */ +#define LY_ERR_FD "Failed to create the console file descriptor" +#define LY_ERR_FD_ADVICE "(ly probably wasn't run with enough privileges)" + +/* pam */ +#define LY_ERR_PAM_BUF "Memory buffer error" +#define LY_ERR_PAM_SYSTEM "System error" +#define LY_ERR_PAM_ABORT "Pam transaction aborted" +#define LY_ERR_PAM_AUTH "Authentication error" +#define LY_ERR_PAM_CRED_INSUFFICIENT "Insufficient credentials" +#define LY_ERR_PAM_AUTHINFO_UNAVAIL "Failed to get user info" +#define LY_ERR_PAM_MAXTRIES "Reached maximum tries limit" +#define LY_ERR_PAM_USER_UNKNOWN "Unknown user" +#define LY_ERR_PAM_ACCT_EXPIRED "Account expired" +#define LY_ERR_PAM_NEW_AUTHTOK_REQD "Token expired" +#define LY_ERR_PAM_PERM_DENIED "Permission denied" +#define LY_ERR_PAM_CRED "Failed to set credentials" +#define LY_ERR_PAM_CRED_EXPIRED "Credentials expired" +#define LY_ERR_PAM_CRED_UNAVAIL "Failed to get credentials" +#define LY_ERR_PAM_SESSION "Session error" +#define LY_ERR_PAM_SET_TTY "Failed to set tty for pam" +#define LY_ERR_PAM_SET_RUSER "Failed to set ruser for pam" + +/* ncurses */ +#define LY_ERR_NC_BUFFER "Failed to refresh ncurses buffer" + +/* de listing */ +#define LY_ERR_DELIST "Failed to open xsessions" + +/* permissions */ +#define LY_ERR_PERM_GROUP "Failed to downgrade group permissions" +#define LY_ERR_PERM_USER "Failed to downgrade user permissions" +#define LY_ERR_PERM_DIR "Failed to change current directory" + +#endif /* _LANG_H_ */ diff --git a/src/login.c b/src/login.c new file mode 100644 index 0000000..0313727 --- /dev/null +++ b/src/login.c @@ -0,0 +1,588 @@ +#define _XOPEN_SOURCE 700 +#define _DEFAULT_SOURCE + +/* std lib */ +#include +#include +#include +/* linux */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* ncurses */ +#include +/* pam */ +#include +#include +#define PAM_SYSTEMD +#include +#include +/* ly */ +#include "lang.h" +#include "config.h" +#include "utils.h" +#include "login.h" +#include "desktop.h" + +int login_conv(int num_msg, const struct pam_message** msg, +struct pam_response** resp, void* appdata_ptr) +{ + int i; + int result = PAM_SUCCESS; + + if(!(*resp = calloc(num_msg, sizeof(struct pam_response)))) + { + return PAM_BUF_ERR; + } + + for(i = 0; i < num_msg; i++) + { + char* username, *password; + + switch(msg[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + username = ((char**) appdata_ptr)[0]; + (*resp)[i].resp = strdup(username); + break; + + case PAM_PROMPT_ECHO_OFF: + password = ((char**) appdata_ptr)[1]; + (*resp)[i].resp = strdup(password); + break; + + case PAM_ERROR_MSG: + fprintf(stderr, "%s\n", msg[i]->msg); + result = PAM_CONV_ERR; + break; + + case PAM_TEXT_INFO: + printf("%s\n", msg[i]->msg); + break; + } + + if(result != PAM_SUCCESS) + { + break; + } + } + + if(result != PAM_SUCCESS) + { + free(*resp); + *resp = 0; + } + + return result; +} + +int start_env(const char* username, const char* password, +const char* de_command, enum deserv_t display_server) +{ + pid_t pid_display; + int display_status; + /* login info */ + int pam_result; + const char* creds[2] = {username, password}; + struct pam_conv conv = {login_conv, creds}; + struct passwd* pwd = NULL; + pam_handle_t* login_handle; + /* session info */ + int display_id; + char display_name[3]; + char tty_id[3]; + char vt[5]; + /* generates console and display id and updates the environment */ + destroy_env(); + display_id = get_free_display(); + snprintf(display_name, sizeof(display_name), ":%d", display_id); + snprintf(tty_id, sizeof(tty_id), "%d", LY_CONSOLE_TTY); + snprintf(vt, sizeof(vt), "vt%d", LY_CONSOLE_TTY); + init_xdg(tty_id, display_name, display_server); + /* pam_start and error handling */ + pam_result = pam_start(LY_SERVICE_NAME, username, &conv, &login_handle); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_BUF_ERR : + error_print(LY_ERR_PAM_BUF); + break; + + case PAM_SYSTEM_ERR : + error_print(LY_ERR_PAM_SYSTEM); + break; + + case PAM_ABORT : + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_authenticate and error handling */ + pam_result = pam_authenticate(login_handle, 0); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_AUTH_ERR : + error_print(LY_ERR_PAM_AUTH); + break; + + case PAM_CRED_INSUFFICIENT : + error_print(LY_ERR_PAM_CRED_INSUFFICIENT); + break; + + case PAM_AUTHINFO_UNAVAIL : + error_print(LY_ERR_PAM_AUTHINFO_UNAVAIL); + break; + + case PAM_MAXTRIES : + error_print(LY_ERR_PAM_MAXTRIES); + break; + + case PAM_USER_UNKNOWN : + error_print(LY_ERR_PAM_USER_UNKNOWN); + break; + + case PAM_ABORT : + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_acct_mgmt and error handling */ + pam_result = pam_acct_mgmt(login_handle, 0); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_ACCT_EXPIRED : + error_print(LY_ERR_PAM_ACCT_EXPIRED); + break; + + case PAM_AUTH_ERR : + error_print(LY_ERR_PAM_AUTH); + break; + + case PAM_NEW_AUTHTOK_REQD : + error_print(LY_ERR_PAM_NEW_AUTHTOK_REQD); + break; + + case PAM_PERM_DENIED : + error_print(LY_ERR_PAM_PERM_DENIED); + break; + + case PAM_USER_UNKNOWN : + error_print(LY_ERR_PAM_USER_UNKNOWN); + break; + + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_setcred and error handling */ + pam_result = pam_setcred(login_handle, PAM_ESTABLISH_CRED); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_BUF_ERR : + error_print(LY_ERR_PAM_BUF); + break; + + case PAM_CRED_ERR : + error_print(LY_ERR_PAM_CRED); + break; + + case PAM_CRED_EXPIRED : + error_print(LY_ERR_PAM_CRED_EXPIRED); + break; + + case PAM_CRED_UNAVAIL : + error_print(LY_ERR_PAM_CRED_UNAVAIL); + break; + + case PAM_SYSTEM_ERR : + error_print(LY_ERR_PAM_SYSTEM); + break; + + case PAM_USER_UNKNOWN : + error_print(LY_ERR_PAM_USER_UNKNOWN); + break; + + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_open_session and error handling */ + pam_result = pam_open_session(login_handle, 0); + + if(pam_result != PAM_SUCCESS) + { + pam_setcred(login_handle, PAM_DELETE_CRED); + + switch(pam_result) + { + case PAM_BUF_ERR : + error_print(LY_ERR_PAM_BUF); + break; + + case PAM_CRED_ERR : + error_print(LY_ERR_PAM_CRED); + break; + + case PAM_CRED_EXPIRED : + error_print(LY_ERR_PAM_CRED_EXPIRED); + break; + + case PAM_CRED_UNAVAIL : + error_print(LY_ERR_PAM_CRED_UNAVAIL); + break; + + case PAM_SYSTEM_ERR : + error_print(LY_ERR_PAM_SYSTEM); + break; + + case PAM_USER_UNKNOWN : + error_print(LY_ERR_PAM_USER_UNKNOWN); + break; + + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* login error */ + if(login_handle == NULL) + { + return 1; + } + + /* temporarily exits ncurses mode */ + def_prog_mode(); + endwin(); + pwd = getpwnam(username); + /* launches the DE */ + pid_display = fork(); + + if(pid_display == 0) + { + /* downgrades group permissions and checks for an error */ + if(setgid(pwd->pw_gid) < 0) + { + error_print(LY_ERR_PERM_GROUP); + pam_end(login_handle, pam_result); + return 1; + } + + /* initializes environment variables */ + init_env(login_handle, pwd); + + /* downgrades user permissions and checks for an error */ + if(setuid(pwd->pw_uid) < 0) + { + error_print(LY_ERR_PERM_USER); + pam_end(login_handle, pam_result); + return 1; + } + + /* changes directory and checks for an error */ + if(chdir(pwd->pw_dir) < 0) + { + error_print(LY_ERR_PERM_DIR); + pam_end(login_handle, pam_result); + return 1; + } + + /* starts the chosen environment */ + switch(display_server) + { + case shell: + launch_shell(pwd, login_handle); + + case wayland: + launch_wayland(pwd, login_handle, de_command); + break; + + case xorg: + default : + launch_xorg(pwd, login_handle, de_command, display_name, vt); + break; + } + + exit(0); + } + + /* waits for the de/shell to exit */ + waitpid(pid_display, &display_status, 0); + /* pam_close_session and error handling */ + pam_result = pam_close_session(login_handle, 0); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_BUF_ERR : + error_print(LY_ERR_PAM_BUF); + break; + + case PAM_SESSION_ERR : + error_print(LY_ERR_PAM_SESSION); + break; + + case PAM_ABORT : + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_setcred and error handling */ + pam_result = pam_setcred(login_handle, PAM_DELETE_CRED); + + if(pam_result != PAM_SUCCESS) + { + switch(pam_result) + { + case PAM_BUF_ERR : + error_print(LY_ERR_PAM_BUF); + break; + + case PAM_CRED_ERR : + error_print(LY_ERR_PAM_CRED); + break; + + case PAM_CRED_EXPIRED : + error_print(LY_ERR_PAM_CRED_EXPIRED); + break; + + case PAM_CRED_UNAVAIL : + error_print(LY_ERR_PAM_CRED_UNAVAIL); + break; + + case PAM_SYSTEM_ERR : + error_print(LY_ERR_PAM_SYSTEM); + break; + + case PAM_USER_UNKNOWN : + error_print(LY_ERR_PAM_USER_UNKNOWN); + break; + + default: + error_print(LY_ERR_PAM_ABORT); + break; + } + + pam_end(login_handle, pam_result); + return 1; + } + + /* pam_end and error handling */ + pam_result = pam_end(login_handle, pam_result); + + if(pam_result != PAM_SUCCESS) + { + error_print(LY_ERR_PAM_SYSTEM); + refresh(); + return 1; + } + + error_print(LY_LANG_LOGOUT); + refresh(); + return 0; +} + +void launch_xorg(struct passwd* pwd, pam_handle_t* pam_handle, +const char* de_command, const char* display_name, const char* vt) +{ + FILE* file; + pid_t xauth_pid; + int xauth_status; + char cmd[LY_LIM_CMD]; + /* updates cookie */ + snprintf(cmd, sizeof(cmd), "exec xauth add %s . `%s`", display_name, + LY_CMD_MCOOKIE); + /* creates the file if it can't be found */ + file = fopen(getenv("XAUTHORITY"), "ab+"); + fclose(file); + /* generates the cookie */ + xauth_pid = fork(); + + if(xauth_pid == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(0); + } + + waitpid(xauth_pid, &xauth_status, 0); + reset_terminal(pwd); + /* starts session */ + snprintf(cmd, sizeof(cmd), + "exec xinit /usr/bin/%s -- %s %s %s -auth %s", de_command, LY_CMD_X, + display_name, vt, getenv("XAUTHORITY")); + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(0); +} + +void launch_wayland(struct passwd* pwd, pam_handle_t* pam_handle, +const char* de_command) +{ + exit(EXIT_FAILURE); +} + +void launch_shell(struct passwd* pwd, pam_handle_t* pam_handle) +{ + char* pos; + char args[PATH_MAX + 2]; + reset_terminal(pwd); + args[0] = '-'; + strncpy(args + 1, ((pos = strrchr(pwd->pw_shell, + '/')) ? pos + 1 : pwd->pw_shell), sizeof(args) - 1); + execl(pwd->pw_shell, args, NULL); + exit(0); +} + +void destroy_env(void) +{ + /* our environment */ + extern char** environ; + /* completely destroys environment */ + environ = malloc(sizeof(char*)); + memset(environ, 0, sizeof(char*)); +} + +void init_xdg(const char* tty_id, const char* display_name, +enum deserv_t display_server) +{ + setenv("XDG_SESSION_CLASS", "user", 0); + setenv("XDG_SEAT", "seat0", 0); + setenv("XDG_VTNR", tty_id, 0); + setenv("DISPLAY", display_name, 1); + + switch(display_server) + { + case shell: + setenv("XDG_SESSION_TYPE", "tty", 0); + + case wayland: + setenv("XDG_SESSION_TYPE", "wayland", 0); + break; + + case xorg: + default : + setenv("XDG_SESSION_TYPE", "x11", 0); + break; + } +} + +int init_env(pam_handle_t* pam_handle, struct passwd* pw) +{ + int i; + int len; + /* buffers */ + char tmp[PATH_MAX]; + char* buf; + char** env; + char* termenv = getenv("TERM"); + setenv("HOME", pw->pw_dir, 0); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", pw->pw_shell, 1); + setenv("LOGNAME", pw->pw_name, 1); + snprintf(tmp, sizeof(tmp), "/home/%s/%s", pw->pw_name, LY_XAUTHORITY); + setenv("XAUTHORITY", tmp, 0); + buf = termenv ? strdup(termenv) : NULL; + setenv("TERM", buf ? buf : "linux", 1); + free(buf); + len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pw->pw_name); + + if((len > 0) && ((size_t) len < sizeof(tmp))) + { + setenv("MAIL", tmp, 0); + } + + if(setenv("PATH", LY_PATH, 1)) + { + return 0; + } + + env = pam_getenvlist(pam_handle); + + for(i = 0; env && env[i]; i++) + { + putenv(env[i]); + } + + return 1; +} + +void reset_terminal(struct passwd* pwd) +{ + pid_t pid; + int status; + pid = fork(); + char cmd[LY_LIM_CMD]; + strncpy(cmd, "exec tput reset", sizeof(cmd)); + + if(pid == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(0); + } + + waitpid(pid, &status, 0); +} + +int get_free_display(void) +{ + int i; + char xlock[LY_LIM_PATH]; + + for(i = 0; i < 200; ++i) + { + snprintf(xlock, sizeof(xlock), "/tmp/.X%d-lock", i); + + if(access(xlock, F_OK) == -1) + { + break; + } + } + + return i; +} diff --git a/src/login.h b/src/login.h new file mode 100644 index 0000000..7f34e6b --- /dev/null +++ b/src/login.h @@ -0,0 +1,24 @@ +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#include +#include +#include "desktop.h" + +int login_conv(int num_msg, const struct pam_message** msg, +struct pam_response** resp, void* appdata_ptr); +int start_env(const char* username, const char* password, +const char* de_command, enum deserv_t display_server); +void launch_xorg(struct passwd* pwd, pam_handle_t* pam_handle, +const char* de_command, const char* display_name, const char* vt); +void launch_wayland(struct passwd* pwd, pam_handle_t* pam_handle, +const char* de_command); +void launch_shell(struct passwd* pwd, pam_handle_t* pam_handle); +void destroy_env(void); +void init_xdg(const char* tty_id, const char* display_name, +enum deserv_t display_server); +int init_env(pam_handle_t* pam_handle, struct passwd* pw); +void reset_terminal(struct passwd* pwd); +int get_free_display(void); + +#endif /* _LOGIN_H_ */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ff0cac8 --- /dev/null +++ b/src/main.c @@ -0,0 +1,213 @@ +#define _XOPEN_SOURCE + +/* stdlib */ +#include +#include +/* linux */ +#include +#include +#include +#include +#include +/* ncurses */ +#include +/* pam */ +#include +/* ly */ +#include "lang.h" +#include "config.h" +#include "utils.h" +#include "login.h" +#include "desktop.h" +#include "ncui.h" + +#define KEY_ENTER_ASCII 10 +#define KEY_BACKSPACE_ASCII 127 + +int main(void) +{ + FILE* console; + /* user interface components */ + struct ncform form; + struct ncwin win; + /* desktop environments list */ + enum deserv_t type; + struct delist_t* de_list; + struct deprops_t* de_props; + char** de_names; + int de_count; + int de_id; + /* user input */ + int input_key; + /* processing buffers */ + int fail; + int auth_fails; + char* username; + char* password; + char* cmd; + /* prevents CTRL+C from killing the process */ + signal(SIGINT, SIG_IGN); + signal(SIGHUP, SIG_IGN); + /* gets desktop entries */ + de_list = list_de(); + de_props = de_list->props; + de_names = de_list->names; + de_count = de_list->count; + de_id = 0; + auth_fails = 0; + /* verifies if we can access the console with enough privileges */ + console = fopen(LY_CONSOLE_DEV, "w"); + + if(!console) + { + fprintf(stderr, "%s\n", LY_ERR_FD); + fprintf(stderr, "%s\n", LY_ERR_FD_ADVICE); + return 0; + } + + kernel_log(0); + /* initializes ncurses UI */ + init_ncurses(console); + init_form(&form, de_names, de_count, &de_id); + init_win(&win, &form); + init_scene(&win, &form); + init_draw(&win, &form); + close(fileno(console)); + /* enables insertion mode */ + form_driver(form.form, REQ_INS_MODE); + /* makes the password field active by default */ + set_current_field(form.form, form.fields[4]); + form_driver(form.form, REQ_END_LINE); + + while((input_key = wgetch(win.win))) + { + form.active = current_field(form.form); + + switch(input_key) + { + case KEY_ENTER_ASCII: + if(form.active == form.fields[4]) + { + /* checks for buffer errors */ + if(form_driver(form.form, REQ_VALIDATION) != E_OK) + { + error_print(LY_ERR_NC_BUFFER); + break; + } + + /* stores the user inputs in processing buffers */ + username = trim(field_buffer(form.fields[2], 0)); + password = trim(field_buffer(form.fields[4], 0)); + cmd = de_props[de_id].cmd; + type = de_props[de_id].type; + + /* saves the username and DE if enabled */ + if(LY_CFG_WRITE_SAVE) + { + FILE* file = fopen(LY_CFG_SAVE, "ab+"); + file = fopen(LY_CFG_SAVE, "wb"); + fprintf(file, "%s\n%d", username, de_id); + fclose(file); + } + + /* logs in and suspends ncurses mode if successful */ + fail = start_env(username, password, cmd, type); + /* clears the password */ + set_field_buffer(form.fields[4], 0, ""); + + if(fail) + { + ++auth_fails; + + if(auth_fails > (LY_CFG_AUTH_TRIG - 1)) + { + cascade(); + } + } + else if(LY_CFG_CLR_USR) + { + /* clears the username */ + set_field_buffer(form.fields[2], 0, ""); + /* sets cursor to the login field */ + set_current_field(form.form, form.fields[2]); + break; + } + + /* sets cursor to the password field */ + set_current_field(form.form, form.fields[4]); + break; + } + + case KEY_DOWN: + form_driver(form.form, REQ_NEXT_FIELD); + form_driver(form.form, REQ_END_LINE); + break; + + case KEY_UP: + form_driver(form.form, REQ_PREV_FIELD); + form_driver(form.form, REQ_END_LINE); + break; + + case KEY_RIGHT: + if(form.active == form.fields[0]) + { + de_id = ((de_id + 1) == de_count) ? 0 : de_id + 1; + form_driver(form.form, REQ_NEXT_CHOICE); + } + else + { + form_driver(form.form, REQ_NEXT_CHAR); + } + + break; + + case KEY_LEFT: + if(form.active == form.fields[0]) + { + de_id = (de_id == 0) ? (de_count - 1) : de_id - 1; + form_driver(form.form, REQ_PREV_CHOICE); + } + else + { + form_driver(form.form, REQ_PREV_CHAR); + } + + break; + + case KEY_BACKSPACE_ASCII: + case KEY_BACKSPACE: + form_driver(form.form, REQ_DEL_PREV); + form_driver(form.form, REQ_END_LINE); + break; + + case KEY_DC: + form_driver(form.form, REQ_DEL_CHAR); + break; + + case KEY_F(1): + end_form(&form); + endwin(); + free_list(de_list); + execl(LY_CMD_HALT, LY_CMD_HALT, "-h", "now", NULL); + break; + + case KEY_F(2): + end_form(&form); + endwin(); + free_list(de_list); + execl(LY_CMD_HALT, LY_CMD_HALT, "-r", "now", NULL); + break; + + default: + form_driver(form.form, input_key); + break; + } + } + + kernel_log(1); + fclose(console); + free_list(de_list); + end_form(&form); + endwin(); + return 0; +} diff --git a/src/ncui.c b/src/ncui.c new file mode 100644 index 0000000..953c2f1 --- /dev/null +++ b/src/ncui.c @@ -0,0 +1,166 @@ +#define _XOPEN_SOURCE + +#include "ncui.h" + +/* stdlib */ +#include +#include +/* linux */ +#include +#include +/* ncurses */ +#include +/* ly */ +#include "lang.h" +#include "config.h" +#include "utils.h" + +size_t max(size_t a, size_t b) +{ + return (a > b) ? a : b; +} + +void init_ncurses(FILE* desc) +{ + int filedesc = fileno(desc); + /* required for ncurses */ + putenv(LY_CONSOLE_TERM); + /* switches tty */ + ioctl(filedesc, VT_ACTIVATE, LY_CONSOLE_TTY); + ioctl(filedesc, VT_WAITACTIVE, LY_CONSOLE_TTY); + /* ncurses startup */ + initscr(); + cbreak(); + noecho(); +} + +void init_form(struct ncform* form, char** list, int max_de, int* de_id) +{ + FILE* file; + char line[LY_LIM_LINE_FILE]; + char user[LY_LIM_LINE_FILE]; + int de; + /* creates the file if it can't be found */ + file = fopen(LY_CFG_SAVE, "ab+"); + fclose(file); + /* opens the file */ + file = fopen(LY_CFG_SAVE, "rb"); + memset(user, '\0', LY_LIM_LINE_FILE); + de = max_de; + + /* reads the username and DE from the save file if enabled */ + if(LY_CFG_READ_SAVE) + { + if(fgets(line, sizeof(line), file)) + { + snprintf(user, sizeof(file), "%s", line); + } + + if(fgets(line, sizeof(line), file)) + { + de = (unsigned int) strtol(line, NULL, 10); + } + } + + fclose(file); + /* computes input padding from labels text */ + form->label_pad = max(strlen(LY_LANG_LOGIN), strlen(LY_LANG_PASSWORD)); + /* DE list */ + form->fields[0] = new_field(1, 32, 0, form->label_pad, 0, 0); + set_field_type(form->fields[0], TYPE_ENUM, list); + + if(de < max_de) + { + set_field_buffer(form->fields[0], 0, list[de]); + *de_id = de; + } + else + { + set_field_buffer(form->fields[0], 0, list[0]); + *de_id = 0; + } + + set_field_opts(form->fields[0], + O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE); + /* login label */ + form->fields[1] = new_field(1, form->label_pad, 2, 0, 0, 0); + set_field_buffer(form->fields[1], 0, LY_LANG_LOGIN); + set_field_opts(form->fields[1], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); + /* login field */ + form->fields[2] = new_field(1, 32, 2, form->label_pad, 0, 0); + + if(*user) + { + set_field_buffer(form->fields[2], 0, user); + } + + set_field_opts(form->fields[2], + O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE); + /* password label */ + form->fields[3] = new_field(1, form->label_pad, 4, 0, 0, 0); + set_field_buffer(form->fields[3], 0, LY_LANG_PASSWORD); + set_field_opts(form->fields[3], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); + /* password field */ + form->fields[4] = new_field(1, 32, 4, form->label_pad, 0, 0); + set_field_opts(form->fields[4], O_VISIBLE | O_EDIT | O_ACTIVE); + /* bound */ + form->fields[5] = NULL; + /* generates the form */ + form->form = new_form(form->fields); + scale_form(form->form, &(form->height), &(form->width)); +} + +void init_win(struct ncwin* win, struct ncform* form) +{ + int rows; + int cols; + /* fetches screen size */ + getmaxyx(stdscr, rows, cols); + /* adds a margin */ + win->width = LY_MARGIN_H * 2 + form->width; + win->height = LY_MARGIN_V * 2 + form->height + 2; + /* saves the position */ + win->y = (rows - win->height) / 2; + win->x = (cols - win->width) / 2; + /* generates the window */ + win->win = newwin(win->height, win->width, win->y, win->x); + /* enables advanced input (eg. "F1" key) */ + keypad(win->win, TRUE); +} + +void init_scene(struct ncwin* win, struct ncform* form) +{ + set_form_win(form->form, win->win); + set_form_sub(form->form, derwin(win->win, form->height, form->width, + LY_MARGIN_V + 2, LY_MARGIN_H)); +} + +void init_draw(struct ncwin* win, struct ncform* form) +{ + char line[LY_LIM_LINE_CONSOLE]; + /* frame */ + box(win->win, 0, 0); + /* initializes error output and prints greeting message */ + error_init(win->win, win->width, LY_LANG_GREETING); + /* prints shutdown & reboot hints */ + snprintf(line, sizeof(line), "F1 %s F2 %s", LY_LANG_SHUTDOWN, + LY_LANG_REBOOT); + mvprintw(0, 0, line); + /* dumps ncurses buffer */ + refresh(); + /* registers form */ + post_form(form->form); + /* dumps window buffer */ + wrefresh(win->win); +} + +void end_form(struct ncform* form) +{ + unpost_form(form->form); + free_form(form->form); + free_field(form->fields[0]); + free_field(form->fields[1]); + free_field(form->fields[2]); + free_field(form->fields[3]); + free_field(form->fields[4]); +} diff --git a/src/ncui.h b/src/ncui.h new file mode 100644 index 0000000..e72810a --- /dev/null +++ b/src/ncui.h @@ -0,0 +1,34 @@ +#ifndef _NCUI_H_ +#define _NCUI_H_ + +/* ncurses */ +#include + +struct ncwin +{ + WINDOW* win; + int x; + int y; + int width; + int height; +}; + +struct ncform +{ + FORM* form; + FIELD* fields[6]; + FIELD* active; + int height; + int width; + int label_pad; +}; + +void init_ncurses(FILE* desc); +void init_form(struct ncform* form, char** list, int max_de, +int* de_id); +void init_win(struct ncwin* win, struct ncform* form); +void init_scene(struct ncwin* win, struct ncform* form); +void init_draw(struct ncwin* win, struct ncform* form); +void end_form(struct ncform* form); + +#endif /* _NCUI_H_ */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..e62f659 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,164 @@ +#define _XOPEN_SOURCE 500 + +/* std lib */ +#include +#include +#include +#include +/* ncurses */ +#include +/* ly */ +#include "config.h" +#include "utils.h" +/* important stuff */ +#include +#include +#include + +void kernel_log(int mode) +{ + pid_t pid; + int status; + pid = fork(); + + if(pid == 0) + { + if(mode) + { + execl("dmesg", "-E", NULL); + } + else + { + execl("dmesg", "-D", NULL); + } + + exit(0); + } + + waitpid(pid, &status, 0); +} + +char* trim(char* s) +{ + char* end = s + strlen(s) - 1; + + while((end > s) && isspace((unsigned char) *end)) + { + --end; + } + + *(end + 1) = '\0'; + return s; +} + +char* strdup(const char* src) +{ + size_t len = strlen(src) + 1; + char* s = malloc(len); + + if(!s) + { + return NULL; + } + + return (char*)memcpy(s, src, len); +} + +void error_init(WINDOW* win, int width, const char* s) +{ + static WINDOW* win_stack = NULL; + static int width_stack = 0; + char* blank; + int i; + + if(win) + { + win_stack = win; + width_stack = width; + } + + blank = malloc((width_stack - 1) * (sizeof(char))); + + for(i = 0; i < width_stack - 2; ++i) + { + blank[i] = ' '; + } + + blank[i] = '\0'; + mvwprintw(win_stack, LY_MARGIN_V, 1, blank); + mvwprintw(win_stack, LY_MARGIN_V, (width_stack - strlen(s)) / 2, s); + free(blank); +} + +void error_print(const char* s) +{ + error_init(NULL, 0, s); +} + +chtype get_curses_char(int y, int x) +{ + return mvwinch(newscr, y, x); +} + +void cascade(void) +{ + int rows; + int cols; + int x; + int y; + chtype char_cur; + chtype char_under; + time_t time_start; + time_t time_end; + time_t time_rand; + int fps = LY_CFG_FPS; + int frame_target = LY_CFG_FMAX; + int frame_count; + float time_frame; + float time_delta = 1.0 / fps; + getmaxyx(stdscr, rows, cols); + time(&time_rand); + srand((unsigned) time_rand); + + for(frame_count = 0; frame_count < frame_target; ++frame_count) + { + time_start = clock(); + + for(y = 0; y < rows; ++y) + { + for(x = 0; x < cols; ++x) + { + char_cur = get_curses_char(y, x); + + if(isspace(char_cur & A_CHARTEXT)) + { + continue; + } + + char_under = get_curses_char(y + 1, x); + + if(!isspace(char_under & A_CHARTEXT)) + { + continue; + } + + if(((rand() % 10) > LY_CFG_FCHANCE) && (frame_count > 0)) + { + continue; + } + + mvaddch(y, x, ' '); + mvaddch(y + 1, x, char_cur); + } + } + + refresh(); + time_end = clock(); + time_frame = (time_end - time_start) / CLOCKS_PER_SEC; + + if(time_frame < time_delta) + { + usleep((time_delta - time_frame) * 1000000); + } + } +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..857f4b3 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,15 @@ +#ifndef _UTILS_H_ +#define _UTILS_H_ + +/* ncurses */ +#include + +void kernel_log(int mode); +char* trim(char* s); +char* strdup(const char* src); +void error_init(WINDOW* win, int width, const char* s); +void error_print(const char* s); +chtype get_curses_char(int y, int x); +void cascade(void); + +#endif /* _UTILS_H_ */