initial commit

This commit is contained in:
cylgom 2017-05-23 20:12:12 +02:00
commit 7870f668f3
15 changed files with 1586 additions and 0 deletions

13
LICENSE.md Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

73
README.md Normal file
View File

@ -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.
▌▐
█▄▄ ▐ █ █▄▄ █▄▄ █▄▄ ▄▄█ ▄▄█▌ ▄▄▄▄
▐████▄█ █▌ ▐████▄ ▐████▄ ▐████▄ ▄████ ▄█████ █████▀
▐██████ ▐█ ▐█████▌ ▐█████▌ ▐█████▌ ▐█████▌ █████▌ █████
████▐██ █▌█████▀ ████ ▀ ████▐██ ██▐████ ▐█████ ▐████▌
███▌▐██ ▐█████▌ ███▌ ███▌▐██ ██ ████ ████▌▌██▐██▌
███▌ ██ ▐ █████▌ ▐███▌ ███▌▐██ ██ ████ ████ ▐█ ▐██▌
███▌ █ ▐████ ▐███▌ █ ███▌▄▄▄ ██ ████ ████ ▐▌ ███▌
▐██▌ ▄██▌▐████ ████▌ ██ ▐██▌▐██ ██ ███▌ ███▌ ▐ ▐████
▐███▄████▌ ████ ▐████████ ▐███▐██ ██▐███▌ ▐███▌ ▐████▌
█████▀ ▐▌ ████ █████████▄ ▐████▀ ▀████▌ ▐██▌ ████▌
▀▀▀ ▌ ████ ▐██▀▀ ▀▀ ▄█▀▀ ▀▀█▄ ███ ▀███
▐ ████ ▄█▀ ▄ ▐█ █▌▄██ ▀▀▄
▌ █████▀ █ ▀█▄▄ ▄▄█▀▄█▀
▄▄▄▀ ▐███▀ ▐██▄ ▀▀▀▀▀▀▀▀▀ ▄█▀
█▀ ▐█▀ ▀████▄▄▄▄▄▄▄▄▄▄▄▄▄█▀▀
█▄ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▄▄▄▄████████████▄▄
▀▀██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄████████▀▀▀▀▀▀ ▀▀▀▄
▀▀▀▀▀▀▀▀▀▀▀▀ ▄▀

15
ly.service Executable file
View File

@ -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

14
makefile Executable file
View File

@ -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

45
src/config.h Executable file
View File

@ -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_ */

148
src/desktop.c Normal file
View File

@ -0,0 +1,148 @@
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#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);
}

25
src/desktop.h Normal file
View File

@ -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_ */

49
src/lang.h Executable file
View File

@ -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_ */

588
src/login.c Normal file
View File

@ -0,0 +1,588 @@
#define _XOPEN_SOURCE 700
#define _DEFAULT_SOURCE
/* std lib */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* linux */
#include <sys/wait.h>
#include <paths.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <grp.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <sys/stat.h>
#include <fcntl.h>
/* ncurses */
#include <form.h>
/* pam */
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#define PAM_SYSTEMD
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
/* 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;
}

24
src/login.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef _LOGIN_H_
#define _LOGIN_H_
#include <security/pam_appl.h>
#include <pwd.h>
#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_ */

213
src/main.c Normal file
View File

@ -0,0 +1,213 @@
#define _XOPEN_SOURCE
/* stdlib */
#include <string.h>
#include <stdlib.h>
/* linux */
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/vt.h>
#include <unistd.h>
/* ncurses */
#include <form.h>
/* pam */
#include <security/pam_appl.h>
/* 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;
}

166
src/ncui.c Normal file
View File

@ -0,0 +1,166 @@
#define _XOPEN_SOURCE
#include "ncui.h"
/* stdlib */
#include <string.h>
#include <stdlib.h>
/* linux */
#include <sys/ioctl.h>
#include <linux/vt.h>
/* ncurses */
#include <form.h>
/* 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]);
}

34
src/ncui.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef _NCUI_H_
#define _NCUI_H_
/* ncurses */
#include <form.h>
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_ */

164
src/utils.c Normal file
View File

@ -0,0 +1,164 @@
#define _XOPEN_SOURCE 500
/* std lib */
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/wait.h>
/* ncurses */
#include <form.h>
/* ly */
#include "config.h"
#include "utils.h"
/* important stuff */
#include <ctype.h>
#include <time.h>
#include <unistd.h>
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);
}
}
}

15
src/utils.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _UTILS_H_
#define _UTILS_H_
/* ncurses */
#include <form.h>
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_ */