diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1746e32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6e29c3e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "sub/inih"] + path = sub/inih + url = https://github.com/benhoyt/inih.git +[submodule "sub/termbox-next"] + path = sub/termbox-next + url = https://github.com/cylgom/termbox-next.git diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..5c93f45 --- /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..705d177 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +### Ly - a TUI display manager + +![ly screenshot](https://user-images.githubusercontent.com/5473047/42466218-8cb53d3c-83ae-11e8-8e53-bae3669f959c.png "ly on st") + +Ly is a lightweight, TUI (ncurses-like) display manager for linux. + +### Dependencies +Make sure all the following packages are properly installed and configured +on your linux distribution before going further: +- a c99 compiler (tested with gcc and tcc) +- a c standard library +- make +- linux-pam +- xorg +- xorg-xinit +- xorg-xauth +- mcookie +- tput +- shutdown + +### Cloning and Compiling +This repository uses submodules, so you must clone it like so +``` +git clone --recurse-submodules https://github.com/cylgom/ly.git +``` + +To compile you just need to launch make in the created folder +``` +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 make run +``` + +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 +``` + +If messages from other services pop over the login prompt, +edit open the configuration and make sure `force_update` is enabled +``` +[box_main] +force_update=1 +``` + +### Configuration +All the configuration takes place in `/etc/ly/config.ini`. +A complete reference is available on the wiki. + +### Controls +Use the up and down arrow keys to change the current field, and the +left and right arrow keys to change the target desktop environment +while on the desktop field (above the login field). + +### Tips +The numlock and capslock state is printed in the top-right corner. +Use the F1 and F2 keys to respectively shutdown and reboot. + +### 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. +I wish to thank 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 100644 index 0000000..f9bae24 --- /dev/null +++ b/ly.service @@ -0,0 +1,15 @@ +[Unit] +Description=TUI display manager +After=systemd-user-sessions.service plymouth-quit-wait.service +After=getty@tty2.service + +[Service] +Type=simple +ExecStart=/usr/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 100644 index 0000000..774250f --- /dev/null +++ b/makefile @@ -0,0 +1,71 @@ +NAME=ly +CC=gcc +#CC=gcc -O3 +#CC=tcc +FLAGS=-std=c99 -pedantic -Wall -Werror=vla -Werror -g + +BIND=bin +SRCD=src +SUBD=sub +OBJD=obj +RESD=res +LANG=$(RESD)/lang +INCL=-I$(SRCD) -I$(SUBD)/termbox-next/src -I$(SUBD)/inih +LINK=-lm -lpam -lpam_misc + +SRCS=$(SRCD)/main.c +SRCS+=$(SRCD)/draw.c +SRCS+=$(SRCD)/util.c +SRCS+=$(SRCD)/config.c +SRCS+=$(SRCD)/widgets.c +SRCS+=$(SRCD)/desktop.c +SRCS+=$(SRCD)/inputs.c +SRCS+=$(SRCD)/login.c +SRCS+=$(SUBD)/inih/ini.c + +OBJS:=$(patsubst $(SRCD)/%.c,$(OBJD)/$(SRCD)/%.o,$(SRCS)) +OBJS+=$(SUBD)/termbox-next/bin/termbox.a + +.PHONY:all +all:$(BIND)/$(NAME) + +$(OBJD)/%.o:%.c + @echo "building source object $@" + @mkdir -p $(@D) + @$(CC) $(INCL) $(FLAGS) -c -o $@ $< + +$(SUBD)/termbox-next/bin/termbox.a: + @echo "building static object $@" + @cd $(SUBD)/termbox-next && make + +$(BIND)/$(NAME):$(OBJS) + @echo "compiling $@" + @mkdir -p $(BIND) + @$(CC) $(INCL) $(FLAGS) $(LINK) -o $(BIND)/$(NAME) $(OBJS) + @cp -r $(LANG) $(BIND)/lang + @cp $(RESD)/config.ini $(BIND) + +run:$(BIND)/$(NAME) + @cd ./$(BIND) && ./$(NAME) + +valgrind:$(BIND)/$(NAME) + @cd ./$(BIND) && valgrind --show-leak-kinds=all --track-origins=yes --leak-check=full --suppressions=../res/valgrind.supp 2> ../valgrind.log ./ly + +install:$(BIND)/$(NAME) + install -dZ ${DESTDIR}/etc/ly + install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin + install -DZ xsetup.sh -t ${DESTDIR}/etc/ly + install -DZ $(RESD)/config.ini -t ${DESTDIR}/etc/ly + install -dZ ${DESTDIR}/etc/ly/lang + install -DZ $(RESD)/lang/* -t ${DESTDIR}/etc/ly/lang + install -DZ ly.service -t ${DESTDIR}/usr/lib/systemd/system + +uninstall: + rm -rf ${DESTDIR}/etc/ly + rm -f ${DESTDIR}/usr/bin/ly + rm -f ${DESTDIR}/usr/lib/systemd/system/ly.service + +clean: + @echo "cleaning workspace" + @rm -rf $(BIND) + @rm -rf $(OBJD) diff --git a/res/config.ini b/res/config.ini new file mode 100644 index 0000000..c9d607f --- /dev/null +++ b/res/config.ini @@ -0,0 +1,13 @@ +[box_main] +bg=0x000000 +fg=0x00ff00 +margin_box_main_h=2 +margin_box_main_v=1 +input_len=34 +blank_box=1 +min_refresh_delta=10 +force_update=1 +animate=1 +save=1 +load=1 +hide_x=1 diff --git a/res/lang/en.ini b/res/lang/en.ini new file mode 100644 index 0000000..2d9b6cd --- /dev/null +++ b/res/lang/en.ini @@ -0,0 +1,29 @@ +[box_main] +login=login: +password=password: +f1=F1 shutdown +f2=F2 reboot +shell=shell +xinitrc=xinitrc +logout=logout +capslock=capslock +numlock=numlock +err_pam_buf=memory buffer error +err_pam_sys=system error +err_pam_auth=authentication error +err_pam_cred_insufficient=insufficient credentials +err_pam_authinfo_unavail=failed to get user info +err_pam_maxtries=reached maximum tries limit +err_pam_user_unknown=unknown user +err_pam_acct_expired=account expired +err_pam_authok_reqd=token expired +err_pam_perm_denied=permission denied +err_pam_cred_err=failed to set credentials +err_pam_cred_expired=credentials expired +err_pam_cred_unavail=failed to get credentials +err_pam_session=session error +err_pam_abort=pam transaction aborted +err_perm_group=failed to downgrade group permissions +err_perm_user=failed to downgrade user permissions +err_perm_dir=failed to change current directory +err_console_dev=failed to access console diff --git a/res/lang/fr.ini b/res/lang/fr.ini new file mode 100644 index 0000000..6054486 --- /dev/null +++ b/res/lang/fr.ini @@ -0,0 +1,29 @@ +[box_main] +login=identifiant : +password=mot de passe : +f1=F1 éteindre +f2=F2 redémarrer +shell=shell +xinitrc=xinitrc +logout=déconnection +capslock=verr.maj +numlock=verr.num +err_pam_buf=erreur de mémoire tampon +err_pam_sys=erreur système +err_pam_auth=erreur d'authentification +err_pam_cred_insufficient=identifiants insuffisants +err_pam_authinfo_unavail=échec de l'obtention des infos utilisateur +err_pam_maxtries=limite d'essais atteinte +err_pam_user_unknown=utilisateur inconnu +err_pam_acct_expired=compte expiré +err_pam_authok_reqd=tiquet expiré +err_pam_perm_denied=permission refusée +err_pam_cred_err=échec de la modification des identifiants +err_pam_cred_expired=identifiants expirés +err_pam_cred_unavail=échec de l'obtention des identifiants +err_pam_session=erreur de session +err_pam_abort=transaction pam avortée +err_perm_group=échec du déclassement des permissions de groupe +err_perm_user=échec du déclassement des permissions utilisateur +err_perm_dir=échec de changement de répertoire +err_console_dev=échec d'accès à la console diff --git a/res/valgrind.supp b/res/valgrind.supp new file mode 100644 index 0000000..274f2f0 --- /dev/null +++ b/res/valgrind.supp @@ -0,0 +1,31 @@ +{ + pam + Memcheck:Leak + ... + obj:/usr/lib/libpam.so.0.84.2 + ... +} + +{ + termbox + Memcheck:Leak + ... + fun:tb_init + ... +} + +{ + libc/dynamic + Memcheck:Leak + ... + fun:_dl_catch_exception + ... +} + +{ + libc/groups + Memcheck:Leak + ... + fun:initgroups + ... +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..6df1f10 --- /dev/null +++ b/src/config.c @@ -0,0 +1,691 @@ +#define _XOPEN_SOURCE 700 +#include "config.h" +#include "cylgom.h" +#include "ini.h" +#include +#include +#include +#include // login max length +#include + +struct lang lang = {0}; +struct config config = {0}; +char* info_line = NULL; + +u16 compute_box_main_width() +{ + u16 login_len = strlen(lang.login); + u16 password_len = strlen(lang.password); + u16 label_len = login_len > password_len ? login_len : password_len; + + return 2 * config.margin_box_main_h + config.input_len + 1 + label_len; +} + +// smart-dup for config loaders +void cfg_dup(char** name, const char* value) +{ + // this is not a mistake, struct was initialized with zeros + // empty fields are zero-valued pointers because of that + // we probably don't care about that, but let's pretend systems + // where NULL != 0 actually exist and are used. + if (*name != 0) + { + free(*name); + } + + *name = strdup(value); +} + +int config_lang_handler(void* user, const char* section, const char* name, const char* value) +{ + (void)(user); + + if (strcmp(section, "box_main") == 0) + { + if (strcmp(name, "login") == 0) + { + cfg_dup(&lang.login, value); + } + else if (strcmp(name, "password") == 0) + { + cfg_dup(&lang.password, value); + } + else if (strcmp(name, "f1") == 0) + { + cfg_dup(&lang.f1, value); + } + else if (strcmp(name, "f2") == 0) + { + cfg_dup(&lang.f2, value); + } + else if (strcmp(name, "shell") == 0) + { + cfg_dup(&lang.shell, value); + } + else if (strcmp(name, "xinitrc") == 0) + { + cfg_dup(&lang.xinitrc, value); + } + else if (strcmp(name, "logout") == 0) + { + cfg_dup(&lang.logout, value); + } + else if (strcmp(name, "capslock") == 0) + { + cfg_dup(&lang.capslock, value); + } + else if (strcmp(name, "numlock") == 0) + { + cfg_dup(&lang.numlock, value); + } + else if (strcmp(name, "err_pam_buf") == 0) + { + cfg_dup(&lang.err_pam_buf, value); + } + else if (strcmp(name, "err_pam_sys") == 0) + { + cfg_dup(&lang.err_pam_sys, value); + } + else if (strcmp(name, "err_pam_auth") == 0) + { + cfg_dup(&lang.err_pam_auth, value); + } + else if (strcmp(name, "err_pam_cred_insufficient") == 0) + { + cfg_dup(&lang.err_pam_cred_insufficient, value); + } + else if (strcmp(name, "err_pam_authinfo_unavail") == 0) + { + cfg_dup(&lang.err_pam_authinfo_unavail, value); + } + else if (strcmp(name, "err_pam_maxtries") == 0) + { + cfg_dup(&lang.err_pam_maxtries, value); + } + else if (strcmp(name, "err_pam_user_unknown") == 0) + { + cfg_dup(&lang.err_pam_user_unknown, value); + } + else if (strcmp(name, "err_pam_acct_expired") == 0) + { + cfg_dup(&lang.err_pam_acct_expired, value); + } + else if (strcmp(name, "err_pam_authok_reqd") == 0) + { + cfg_dup(&lang.err_pam_authok_reqd, value); + } + else if (strcmp(name, "err_pam_perm_denied") == 0) + { + cfg_dup(&lang.err_pam_perm_denied, value); + } + else if (strcmp(name, "err_pam_cred_err") == 0) + { + cfg_dup(&lang.err_pam_cred_err, value); + } + else if (strcmp(name, "err_pam_cred_expired") == 0) + { + cfg_dup(&lang.err_pam_cred_expired, value); + } + else if (strcmp(name, "err_pam_cred_unavail") == 0) + { + cfg_dup(&lang.err_pam_cred_unavail, value); + } + else if (strcmp(name, "err_pam_session") == 0) + { + cfg_dup(&lang.err_pam_session, value); + } + else if (strcmp(name, "err_pam_abort") == 0) + { + cfg_dup(&lang.err_pam_abort, value); + } + else if (strcmp(name, "err_perm_group") == 0) + { + cfg_dup(&lang.err_perm_group, value); + } + else if (strcmp(name, "err_perm_user") == 0) + { + cfg_dup(&lang.err_perm_user, value); + } + else if (strcmp(name, "err_perm_dir") == 0) + { + cfg_dup(&lang.err_perm_dir, value); + } + else if (strcmp(name, "err_console_dev") == 0) + { + cfg_dup(&lang.err_console_dev, value); + } + } + + return 1; +} + +void config_lang_patch() +{ + if (lang.login == 0) + { + lang.login = strdup("login:"); + } + if (lang.password == 0) + { + lang.password = strdup("password:"); + } + if (lang.f1 == 0) + { + lang.f1 = strdup("F1 shutdown"); + } + if (lang.f2 == 0) + { + lang.f2 = strdup("F2 reboot"); + } + if (lang.shell == 0) + { + lang.shell = strdup("shell"); + } + if (lang.xinitrc == 0) + { + lang.xinitrc = strdup("xinitrc"); + } + if (lang.logout == 0) + { + lang.logout = strdup("logout"); + } + if (lang.capslock == 0) + { + lang.capslock = strdup("capslock"); + } + if (lang.numlock == 0) + { + lang.numlock = strdup("numlock"); + } + if (lang.err_pam_buf == 0) + { + lang.err_pam_buf = strdup("memory buffer error"); + } + if (lang.err_pam_sys == 0) + { + lang.err_pam_sys = strdup("system error"); + } + if (lang.err_pam_auth == 0) + { + lang.err_pam_auth = strdup("authentication error"); + } + if (lang.err_pam_cred_insufficient == 0) + { + lang.err_pam_cred_insufficient = strdup("insufficient credentials"); + } + if (lang.err_pam_authinfo_unavail == 0) + { + lang.err_pam_authinfo_unavail = strdup("failed to get user info"); + } + if (lang.err_pam_maxtries == 0) + { + lang.err_pam_maxtries = strdup("reached maximum tries limit"); + } + if (lang.err_pam_user_unknown == 0) + { + lang.err_pam_user_unknown = strdup("unknown user"); + } + if (lang.err_pam_acct_expired == 0) + { + lang.err_pam_acct_expired = strdup("account expired"); + } + if (lang.err_pam_authok_reqd == 0) + { + lang.err_pam_authok_reqd = strdup("token expired"); + } + if (lang.err_pam_perm_denied == 0) + { + lang.err_pam_perm_denied = strdup("permission denied"); + } + if (lang.err_pam_cred_err == 0) + { + lang.err_pam_cred_err = strdup("failed to set credentials"); + } + if (lang.err_pam_cred_expired == 0) + { + lang.err_pam_cred_expired = strdup("credentials expired"); + } + if (lang.err_pam_cred_unavail == 0) + { + lang.err_pam_cred_unavail = strdup("failed to get credentials"); + } + if (lang.err_pam_session == 0) + { + lang.err_pam_session = strdup("session error"); + } + if (lang.err_pam_abort == 0) + { + lang.err_pam_abort = strdup("pam transaction aborted"); + } + if (lang.err_perm_group == 0) + { + lang.err_perm_group = strdup("failed to downgrade group permissions"); + } + if (lang.err_perm_user == 0) + { + lang.err_perm_user = strdup("failed to downgrade user permissions"); + } + if (lang.err_perm_dir == 0) + { + lang.err_perm_dir = strdup("failed to change current directory"); + } + if (lang.err_console_dev == 0) + { + lang.err_console_dev = strdup("failed to access console"); + } +} + +void config_lang_free() +{ + free(lang.login); + free(lang.password); + free(lang.f1); + free(lang.f2); + free(lang.shell); + free(lang.xinitrc); + free(lang.logout); + free(lang.capslock); + free(lang.numlock); + free(lang.err_pam_buf); + free(lang.err_pam_sys); + free(lang.err_pam_auth); + free(lang.err_pam_cred_insufficient); + free(lang.err_pam_authinfo_unavail); + free(lang.err_pam_maxtries); + free(lang.err_pam_user_unknown); + free(lang.err_pam_acct_expired); + free(lang.err_pam_authok_reqd); + free(lang.err_pam_perm_denied); + free(lang.err_pam_cred_err); + free(lang.err_pam_cred_expired); + free(lang.err_pam_cred_unavail); + free(lang.err_pam_session); + free(lang.err_pam_abort); + free(lang.err_perm_group); + free(lang.err_perm_user); + free(lang.err_perm_dir); + free(lang.err_console_dev); +} + +int config_config_handler(void* user, const char* section, const char* name, const char* value) +{ + (void)(user); + + if (strcmp(section, "box_main") == 0) + { + if (strcmp(name, "margin_box_main_h") == 0) + { + config.margin_box_main_h = abs(atoi(value)); + } + else if (strcmp(name, "margin_box_main_v") == 0) + { + config.margin_box_main_v = abs(atoi(value)); + } + else if (strcmp(name, "input_len") == 0) + { + config.input_len = abs(atoi(value)); + } + else if (strcmp(name, "bg") == 0) + { + config.bg = strtoul(value, NULL, 16); + } + else if (strcmp(name, "fg") == 0) + { + config.fg = strtoul(value, NULL, 16); + } + else if (strcmp(name, "max_desktop_len") == 0) + { + config.max_desktop_len = abs(atoi(value)); + } + else if (strcmp(name, "max_login_len") == 0) + { + config.max_login_len = abs(atoi(value)); + } + else if (strcmp(name, "max_password_len") == 0) + { + config.max_password_len = abs(atoi(value)); + } + else if (strcmp(name, "min_refresh_delta") == 0) + { + config.min_refresh_delta = abs(atoi(value)); + } + else if (strcmp(name, "blank_box") == 0) + { + config.blank_box = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "force_update") == 0) + { + config.force_update = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "animate") == 0) + { + config.animate = abs(atoi(value)); + } + else if (strcmp(name, "xsessions") == 0) + { + cfg_dup(&config.xsessions, value); + } + else if (strcmp(name, "service_name") == 0) + { + cfg_dup(&config.service_name, value); + } + else if (strcmp(name, "tty_id") == 0) + { + config.tty_id = abs(atoi(value)); + } + else if (strcmp(name, "x_cmd") == 0) + { + cfg_dup(&config.x_cmd, value); + } + else if (strcmp(name, "x_cmd_setup") == 0) + { + cfg_dup(&config.x_cmd_setup, value); + } + else if (strcmp(name, "mcookie_cmd") == 0) + { + cfg_dup(&config.mcookie_cmd, value); + } + else if (strcmp(name, "xauthority") == 0) + { + cfg_dup(&config.xauthority, value); + } + else if (strcmp(name, "path") == 0) + { + cfg_dup(&config.path, value); + } + else if (strcmp(name, "shutdown_cmd") == 0) + { + cfg_dup(&config.shutdown_cmd, value); + } + else if (strcmp(name, "console_dev") == 0) + { + cfg_dup(&config.console_dev, value); + } + else if (strcmp(name, "tty") == 0) + { + config.tty = abs(atoi(value)); + } + else if (strcmp(name, "save") == 0) + { + config.save = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "load") == 0) + { + config.load = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "save_file") == 0) + { + cfg_dup(&config.save_file, value); + } + else if (strcmp(name, "custom_res") == 0) + { + config.custom_res = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "res_width") == 0) + { + config.res_width = abs(atoi(value)); + } + else if (strcmp(name, "res_height") == 0) + { + config.res_height = abs(atoi(value)); + } + else if (strcmp(name, "hide_x") == 0) + { + config.hide_x = (atoi(value) > 0) ? true : false; + } + else if (strcmp(name, "hide_x_save_log") == 0) + { + cfg_dup(&config.hide_x_save_log, value); + } + else if (strcmp(name, "lang") == 0) + { + cfg_dup(&config.lang, value); + } + } + + return 1; +} + +void config_config_patch() +{ + if (config.margin_box_main_h == 0) + { + config.margin_box_main_h = 2; + } + if (config.margin_box_main_v == 0) + { + config.margin_box_main_v = 1; + } + if (config.input_len == 0) + { + config.input_len = 34; + } + if (config.bg == 0) + { + config.bg = 0x000000; + } + if (config.fg == 0) + { + config.fg = 0xffffff; + } + if (config.max_desktop_len == 0) + { + // arbitrary one + config.max_desktop_len = 100; + } + if (config.max_login_len == 0) + { + // for "useradd" the max is 32 + config.max_login_len = 32; + + #ifdef LOGIN_NAME_MAX + if (config.max_login_len < LOGIN_NAME_MAX) + { + // the posix standard specifies it includes the terminating NULL + // http://pubs.opengroup.org/onlinepubs/007908799/xsh/limits.h.html + config.max_login_len = LOGIN_NAME_MAX - 1; + } + #endif + + #ifdef _POSIX_LOGIN_NAME_MAX + if (config.max_login_len < _POSIX_LOGIN_NAME_MAX) + { + config.max_login_len = _POSIX_LOGIN_NAME_MAX - 1; + } + #endif + } + if (config.max_password_len == 0) + { + // for "passwd" the max is 200 + // https://github.com/shadow-maint/shadow/blob/master/src/passwd.c#L217 + // for "sudo" it is 255 + // https://www.sudo.ws/repos/sudo/file/tip/include/sudo_plugin.h + // https://www.sudo.ws/repos/sudo/file/tip/src/sudo.c + // "su" and "login" user linux-pam and do not seem to have a limit + config.max_password_len = 255; + } + if (config.min_refresh_delta == 0) + { + config.min_refresh_delta = 1000; + } + + // commenting theses because the defaults are 0 + //#if 0 + if (config.blank_box == 0) + { + config.blank_box = false; + } + if (config.force_update == 0) + { + config.force_update = false; + } + if (config.animate == 0) + { + config.animate = 0; + } + //#endif + + if (config.xsessions == 0) + { + config.xsessions = strdup("/usr/share/xsessions"); + } + if (config.service_name == 0) + { + config.service_name = strdup("login"); + } + if (config.tty_id == 0) + { + config.tty_id = 2; + } + if (config.x_cmd == 0) + { + config.x_cmd = strdup("/usr/bin/X"); + } + if (config.x_cmd_setup == 0) + { + config.x_cmd_setup = strdup("/etc/ly/xsetup.sh"); + } + if (config.mcookie_cmd == 0) + { + config.mcookie_cmd = strdup("/usr/bin/mcookie"); + } + if (config.xauthority == 0) + { + config.xauthority = strdup(".lyxauth"); + } + if (config.path == 0) + { + config.path = strdup("/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/env"); + } + if (config.shutdown_cmd == 0) + { + config.shutdown_cmd = strdup("/sbin/shutdown"); + } + if (config.console_dev == 0) + { + config.console_dev = strdup("/dev/console"); + } + if (config.tty == 0) + { + config.tty = 2; + } + if (config.save_file == 0) + { + config.save_file = strdup("/etc/ly/ly.save"); + } + if ((config.res_width == 0) || (config.res_height == 0)) + { + config.custom_res = false; + } + if (config.hide_x_save_log == 0) + { + config.hide_x_save_log = strdup("/dev/null"); + } + if (config.lang == 0) + { + config.lang = strdup("/etc/ly/lang/en.ini"); + } + + // fill secret parameters + config.box_main_w = compute_box_main_width(); + config.box_main_h = 9; +} + +void config_config_free() +{ + free(config.xsessions); + free(config.service_name); + free(config.x_cmd); + free(config.x_cmd_setup); + free(config.mcookie_cmd); + free(config.xauthority); + free(config.path); + free(config.shutdown_cmd); + free(config.console_dev); + free(config.save_file); + free(config.hide_x_save_log); + free(config.lang); +} + +// loads the ini configs +void config_load(const char* file_config) +{ + // we don't care about this function's success + ini_parse(file_config, config_config_handler, NULL); + ini_parse(config.lang, config_lang_handler, NULL); + // because we check for missing strings anyway + config_lang_patch(); // config patch depends on lang + config_config_patch(); // so we call them in this order +} + +void set_error(enum err error) +{ + switch (error) + { + case ERR_PERM_GROUP: + info_line = lang.err_perm_group; + break; + case ERR_PERM_USER: + info_line = lang.err_perm_user; + break; + case ERR_PERM_DIR: + info_line = lang.err_perm_dir; + break; + default: + info_line = lang.err_pam_abort; + break; + } +} + +void pam_diagnose(int error) +{ + switch (error) + { + case PAM_BUF_ERR: + info_line = lang.err_pam_buf; + break; + case PAM_SYSTEM_ERR: + info_line = lang.err_pam_sys; + break; + case PAM_AUTH_ERR: + info_line = lang.err_pam_auth; + break; + case PAM_CRED_INSUFFICIENT: + info_line = lang.err_pam_cred_insufficient; + break; + case PAM_AUTHINFO_UNAVAIL: + info_line = lang.err_pam_authinfo_unavail; + break; + case PAM_MAXTRIES: + info_line = lang.err_pam_maxtries; + break; + case PAM_USER_UNKNOWN: + info_line = lang.err_pam_user_unknown; + break; + case PAM_ACCT_EXPIRED: + info_line = lang.err_pam_acct_expired; + break; + case PAM_NEW_AUTHTOK_REQD: + info_line = lang.err_pam_authok_reqd; + break; + case PAM_PERM_DENIED: + info_line = lang.err_pam_perm_denied; + break; + case PAM_CRED_ERR: + info_line = lang.err_pam_cred_err; + break; + case PAM_CRED_EXPIRED: + info_line = lang.err_pam_cred_expired; + break; + case PAM_CRED_UNAVAIL: + info_line = lang.err_pam_cred_unavail; + break; + case PAM_SESSION_ERR: + info_line = lang.err_pam_session; + break; + case PAM_ABORT: + default: + info_line = lang.err_pam_abort; + break; + } +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..44a8550 --- /dev/null +++ b/src/config.h @@ -0,0 +1,96 @@ +#ifndef H_CONFIG +#define H_CONFIG + +#include "cylgom.h" + +extern char* info_line; + +#define LY_SYSTEMD +enum err {OK, ERR, SECURE_RAM, XSESSIONS_MISSING, XSESSIONS_READ, ERR_PERM_GROUP, ERR_PERM_USER, ERR_PERM_DIR}; +enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG}; + +struct lang +{ + char* login; + char* password; + char* f1; + char* f2; + char* shell; + char* xinitrc; + char* logout; + char* capslock; + char* numlock; + + // errors + char* err_pam_buf; + char* err_pam_sys; + char* err_pam_auth; + char* err_pam_cred_insufficient; + char* err_pam_authinfo_unavail; + char* err_pam_maxtries; + char* err_pam_user_unknown; + char* err_pam_acct_expired; + char* err_pam_authok_reqd; + char* err_pam_perm_denied; + char* err_pam_cred_err; + char* err_pam_cred_expired; + char* err_pam_cred_unavail; + char* err_pam_session; + char* err_pam_abort; + char* err_perm_group; + char* err_perm_user; + char* err_perm_dir; + char* err_console_dev; +}; + +struct config +{ + u32 bg; + u32 fg; + u16 box_main_w; + u16 box_main_h; + u16 margin_box_main_h; + u16 margin_box_main_v; + u16 input_len; + u16 max_desktop_len; + u16 max_login_len; + u16 max_password_len; + u16 min_refresh_delta; + u16 old_min_refresh_delta; + bool blank_box; + bool force_update; + bool old_force_update; + u16 animate; + char* xsessions; + char* service_name; + u16 tty_id; + char* x_cmd; + char* x_cmd_setup; + char* mcookie_cmd; + char* xauthority; + char* path; + char* shutdown_cmd; + char* console_dev; + u8 tty; + bool save; + bool load; + char* save_file; + bool custom_res; + u16 res_width; + u16 res_height; + bool hide_x; + char* hide_x_save_log; + u8 auth_fails; + char* lang; +}; + +extern struct lang lang; +extern struct config config; + +void config_load(const char* file_config); +void config_config_free(); +void config_lang_free(); +void set_error(enum err error); +void pam_diagnose(int error); + +#endif diff --git a/src/cylgom.h b/src/cylgom.h new file mode 100644 index 0000000..8cb5581 --- /dev/null +++ b/src/cylgom.h @@ -0,0 +1,73 @@ +#ifndef H_CYLGOM +#define H_CYLGOM + +#include +#include + +// typedefs for convenience and optimizations + +// 0 to save ram and optimize for embedded systems +// 1 to gain extra speed by replacing all floats by doubles +// 2 to gain extra speed by using bigger integers depending on arch +// level 2 includes *heavy* optimizations that will definitely eat your ram +#define SPEED 0 + +/////////////////// +// regular stuff // +/////////////////// + +// 100% standard +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +// float and double are not fixed-size by the C standard +// however, the C standard strongly suggests using IEEE 754 +// in IEEE 754, float is 32 bits and double 64 bits +// howevevr, long double is whatever size the compiler prefers +// this is why we redefine float and double but not long double +typedef float f32; +typedef double f64; + +/////////////////////////////// +// black magic optimizations // +/////////////////////////////// + +// the best optimization out there +// doubles are usually slower than floats for various reasons +// on embedded systems though, it is usually the opposite +#if SPEED > 0 +typedef f64 f32; +#endif + +// the following block tries to optimize speed at the cost of ram +// we are testing the architecturee in the most portable way possible +// the following macro is not mandatory, obscure systems might not provide it +// on 16 bits systems, 16-bit integer operations can be the fastest +// on 32 bits systems, 32-bit integer operations can be the fastest +// on 64 bits systems, 64-bit integer operations can be the fastest +#if SPEED > 1 +#if UINTPTR_MAX == 0xffff +typedef uint16_t u8; +typedef int16_t i8; +#elif UINTPTR_MAX == 0xffffffff +typedef uint32_t u8; +typedef int32_t i8; +typedef uint32_t u16; +typedef int32_t i16; +#elif UINTPTR_MAX == 0xffffffffffffffff +typedef uint64_t u8; +typedef int64_t i8; +typedef uint64_t u16; +typedef int64_t i16; +typedef uint64_t u32; +typedef int64_t i32; +#endif +#endif + +#endif diff --git a/src/desktop.c b/src/desktop.c new file mode 100644 index 0000000..6529373 --- /dev/null +++ b/src/desktop.c @@ -0,0 +1,91 @@ +#define _XOPEN_SOURCE 700 +#include "desktop.h" +#include "cylgom.h" +#include "ini.h" +#include "widgets.h" +#include +#include +#include +#include +#include +#include + +char* value_name = NULL; +char* value_exec = NULL; + +int desktop_handler(void* user, const char* section, const char* name, const char* value) +{ + (void)(user); + + if (strcmp(section, "Desktop Entry") == 0) + { + if ((strcmp(name, "Name") == 0) && (value_name == NULL)) + { + value_name = strdup(value); + } + if ((strcmp(name, "Exec") == 0) && (value_exec == NULL)) + { + value_exec = strdup(value); + } + } + + return 1; +} + +enum err desktop_load(struct desktop* target) +{ + DIR* dir; + struct dirent* dir_info; + + #if defined(NAME_MAX) + char path[NAME_MAX]; + #elif defined(_POSIX_PATH_MAX) + char path[_POSIX_PATH_MAX]; + #else + char path[1024]; + #endif + + // checks dir existence + if (access(config.xsessions, F_OK) == -1) + { + return XSESSIONS_MISSING; + } + + // requests read access + dir = opendir(config.xsessions); + + if (dir == NULL) + { + return XSESSIONS_READ; + } + + // reads the content + dir_info = readdir(dir); + + while (dir_info != NULL) + { + // skips the files starting with "." + if ((dir_info->d_name)[0] == '.') + { + dir_info = readdir(dir); + continue; + } + + snprintf(path, (sizeof (path)) - 1, "%s/", config.xsessions); + strncat(path, dir_info->d_name, (sizeof (path)) - 1); + ini_parse(path, desktop_handler, NULL); + + if ((value_name != NULL) && (value_exec != NULL)) + { + widget_desktop_add(target, value_name, value_exec, DS_XORG); + value_name = NULL; + value_exec = NULL; + } + + dir_info = readdir(dir); + } + + closedir(dir); + + return OK; +} diff --git a/src/desktop.h b/src/desktop.h new file mode 100644 index 0000000..6fea88f --- /dev/null +++ b/src/desktop.h @@ -0,0 +1,8 @@ +#ifndef H_DESKTOP + +#include "config.h" +#include "widgets.h" + +enum err desktop_load(struct desktop* target); + +#endif diff --git a/src/draw.c b/src/draw.c new file mode 100644 index 0000000..3a84f7f --- /dev/null +++ b/src/draw.c @@ -0,0 +1,460 @@ +#define _XOPEN_SOURCE 700 +#include "draw.h" +#include "cylgom.h" +#include "termbox.h" +#include "util.h" +#include "config.h" +#include "widgets.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// border chars: ┌ └ ┐ ┘ ─ ─ │ │ +struct box box_main = {0x250c, 0x2514, 0x2510, 0x2518, 0x2500, 0x2500 ,0x2502, 0x2502}; +// alternative border chars: +// struct box box_main = {'+', '+', '+', '+', '-', '-', '|', '|'}; + +u16 width = 0; +u16 height = 0; +u16 box_main_x = 0; +u16 box_main_y = 0; +u16 labels_max_len = 0; + +void draw_init() +{ + if (config.custom_res) + { + width = config.res_width; + height = config.res_height; + } + else + { + width = tb_width(); + height = tb_height(); + } + + if (info_line == NULL) + { + hostname(&info_line); + } +} + +// box outline +void draw_box() +{ + box_main_x = ((width - config.box_main_w) / 2); + box_main_y = ((height - config.box_main_h) / 2); + u16 box_main_x2 = ((width + config.box_main_w) / 2); + u16 box_main_y2 = ((height + config.box_main_h) / 2); + // corners + tb_change_cell(box_main_x - 1, + box_main_y - 1, + box_main.left_up, + config.fg, + config.bg); + tb_change_cell(box_main_x2 + 1, + box_main_y - 1, + box_main.right_up, + config.fg, + config.bg); + tb_change_cell(box_main_x - 1, + box_main_y2 + 1, + box_main.left_down, + config.fg, + config.bg); + tb_change_cell(box_main_x2 + 1, + box_main_y2 + 1, + box_main.right_down, + config.fg, + config.bg); + + // top and bottom + struct tb_cell c1 = {box_main.top, config.fg, config.bg}; + struct tb_cell c2 = {box_main.bot, config.fg, config.bg}; + + for(u8 i = 0; i <= config.box_main_w; ++i) + { + tb_put_cell(box_main_x + i, + box_main_y - 1, + &c1); + tb_put_cell(box_main_x + i, + box_main_y2 + 1, + &c2); + } + + // left and right + c1.ch = box_main.left; + c2.ch = box_main.right; + // blank + struct tb_cell blank = {' ', config.fg, config.bg}; + + for(u8 i = 0; i <= config.box_main_h; ++i) + { + // testing in the height loop takes less cycles + // (I know this is placebo optimization :D) + if (config.blank_box) + { + for (u8 k = 0; k <= config.box_main_w; ++k) + { + tb_put_cell(box_main_x + k, + box_main_y + i, + &blank); + } + } + + tb_put_cell(box_main_x - 1, + box_main_y + i, + &c1); + tb_put_cell(box_main_x2 + 1, + box_main_y + i, + &c2); + } +} + +struct tb_cell* strn_cell(char* s, u16 len) +{ + struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len); + char* s2 = s; + u32 c; + + if (cells != NULL) + { + for (u16 i = 0; i < len; ++i) + { + if ((s2 - s) >= len) + { + break; + } + + s2 += utf8_char_to_unicode(&c, s2); + + cells[i].ch = c; + cells[i].bg = config.bg; + cells[i].fg = config.fg; + } + } + + return cells; +} + +struct tb_cell* str_cell(char* s) +{ + return strn_cell(s, strlen(s)); +} + +// input labels +void draw_labels() +{ + struct tb_cell* login = str_cell(lang.login); + + tb_blit(box_main_x + config.margin_box_main_h, + box_main_y + config.margin_box_main_v + 5, + strlen(lang.login), + 1, + login); + + free(login); + + struct tb_cell* password = str_cell(lang.password); + + tb_blit(box_main_x + config.margin_box_main_h, + box_main_y + config.margin_box_main_v + 7, + strlen(lang.password), + 1, + password); + + free(password); + + labels_max_len = strlen(lang.login); + + if (labels_max_len < strlen(lang.password)) + { + labels_max_len = strlen(lang.password); + } + + if (info_line != NULL) + { + u16 hostname_len = strlen(info_line); + struct tb_cell* info_cell = str_cell(info_line); + + tb_blit(box_main_x + ((config.box_main_w - hostname_len) / 2), + box_main_y + config.margin_box_main_v, + hostname_len, + 1, + info_cell); + free(info_cell); + } +} + +// F1 and F2 labels +void draw_f_commands() +{ + struct tb_cell* f1 = str_cell(lang.f1); + tb_blit(0, 0, strlen(lang.f1), 1, f1); + free(f1); + + struct tb_cell* f2 = str_cell(lang.f2); + tb_blit(strlen(lang.f1) + 1, 0, strlen(lang.f2), 1, f2); + free(f2); +} + +// numlock and capslock info +void draw_lock_state() +{ + FILE* console = fopen(config.console_dev, "w"); + + if (console == NULL) + { + info_line = lang.err_console_dev; + return; + } + + int fd = fileno(console); + char ret; + + ioctl(fd, KDGKBLED, &ret); + fclose(console); + + u16 pos_x = width - strlen(lang.numlock); + + if (((ret >> 1) & 0x01) == 1) + { + struct tb_cell* numlock = str_cell(lang.numlock); + tb_blit(pos_x, 0, strlen(lang.numlock), 1, numlock); + free(numlock); + } + + pos_x -= strlen(lang.capslock) + 1; + + if (((ret >> 2) & 0x01) == 1) + { + struct tb_cell* capslock = str_cell(lang.capslock); + tb_blit(pos_x, 0, strlen(lang.capslock), 1, capslock); + free(capslock); + } +} + +// main box +void draw_desktop(struct desktop* target) +{ + u16 len = strlen(target->list[target->cur]); + + if (len > (target->visible_len - 3)) + { + len = target->visible_len - 3; + } + + tb_change_cell(target->x, + target->y, + '<', + config.fg, + config.bg); + + tb_change_cell(target->x + target->visible_len - 1, + target->y, + '>', + config.fg, + config.bg); + + for (u16 i = 0; i < len; ++i) + { + tb_change_cell(target->x + i + 2, + target->y, + target->list[target->cur][i], + config.fg, + config.bg); + } +} + +// classic input +void draw_input(struct input* input) +{ + u16 len = strlen(input->text); + u16 visible_len = input->visible_len; + struct tb_cell* cells; + + if (len > visible_len) + { + len = visible_len; + } + + cells = strn_cell(input->visible_start, len); + + if (cells != NULL) + { + tb_blit(input->x, input->y, len, 1, cells); + free(cells); + + for (u16 i = input->end - input->visible_start; i < visible_len; ++i) + { + tb_change_cell(input->x + i, + input->y, + ' ', + config.fg, + config.bg); + } + } +} + +// password input (hidden text) +void draw_input_mask(struct input* input) +{ + u16 len = strlen(input->text); + u16 visible_len = input->visible_len; + + if (len > visible_len) + { + len = visible_len; + } + + for (u16 i = 0; i < visible_len; ++i) + { + if (input->visible_start + i < input->end) + { + tb_change_cell(input->x + i, + input->y, + 'o', + config.fg, + config.bg); + } + else + { + tb_change_cell(input->x + i, + input->y, + ' ', + config.fg, + config.bg); + } + } +} + +// configures the inputs accroding to the size of the screen +void position_input(struct desktop* desktop, struct input* login, struct input* password) +{ + i32 len; + u16 x; + + x = box_main_x + config.margin_box_main_h + labels_max_len + 1; + len = box_main_x + config.box_main_w - config.margin_box_main_h - x; + + if (len < 0) + { + return; + } + + desktop->x = x; + desktop->y = box_main_y + config.margin_box_main_v + 3; + desktop->visible_len = len; + + login->x = x; + login->y = box_main_y + config.margin_box_main_v + 5; + login->visible_len = len; + + password->x = x; + password->y = box_main_y + config.margin_box_main_v + 7; + password->visible_len = len; +} + +// background animations +// example implementation +void spiral() +{ + static struct timeval time; + static uint64_t time_present = 0; + static uint64_t time_past = 0; + const struct tb_cell c1 = {'o', config.fg, config.bg}; + static f64 ini = 0; + + gettimeofday(&time, NULL); + time_present = time.tv_usec + ((uint64_t) 1000000) * time.tv_sec; + + ini += 2 * M_PI * ((time_present - time_past) / 2000000.0); + + if (ini > (5 * 2 * M_PI)) + { + ini = 0; + } + + for (f64 t = 0; t < (5 * 2 * M_PI); t += 0.01) + { + f64 y = sin(t + ini) * (height / 2) * 0.3 * (t / (2 * M_PI)) * 1.2; + f64 x = cos(t + ini) * (width / 2) * 0.2 * (t / (2 * M_PI)) * 1.2; + + tb_put_cell((width / 2) + x, (height / 2) + y, &c1); + } + + time_past = time_present; +} + +void animate() +{ + switch(config.animate) + { + case 1: + spiral(); + break; + case 0: + default: + break; + } +} + +// very important ;) +void cascade(u8* fails) +{ + u16 width = tb_width(); + u16 height = tb_height(); + struct tb_cell* buf = tb_cell_buffer(); + char c; + char c_under; + bool changes = false; + + for (int i = height - 2; i >= 0; --i) + { + for (int k = 0; k < width; ++k) + { + c = buf[i * width + k].ch; + + if (isspace(c)) + { + continue; + } + + c_under = buf[(i + 1) * width + k].ch; + + if (!isspace(c_under)) + { + continue; + } + + if (!changes) + { + changes = true; + } + + // omg this is not cryptographically secure + if (((rand() % 10)) > 7) + { + continue; + } + + buf[(i + 1) * width + k] = buf[i * width + k]; + buf[i * width + k].ch = ' '; + } + } + + if (!changes) + { + sleep(7); + config.auth_fails = 0; + config.min_refresh_delta = config.old_min_refresh_delta; + config.force_update = config.old_force_update; + } +} diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..b725abf --- /dev/null +++ b/src/draw.h @@ -0,0 +1,31 @@ +#ifndef H_DRAW +#define H_DRAW + +#include "widgets.h" +#include "cylgom.h" + +struct box +{ + u32 left_up; + u32 left_down; + u32 right_up; + u32 right_down; + u32 top; + u32 bot; + u32 left; + u32 right; +}; + +void draw_init(); +void draw_box(); +void draw_labels(); +void draw_f_commands(); +void draw_lock_state(); +void draw_desktop(struct desktop* target); +void draw_input(struct input* input); +void draw_input_mask(struct input* input); +void position_input(struct desktop* desktop, struct input* login, struct input* password); +void animate(); +void cascade(); + +#endif diff --git a/src/inputs.c b/src/inputs.c new file mode 100644 index 0000000..3ac36cd --- /dev/null +++ b/src/inputs.c @@ -0,0 +1,66 @@ +#include "inputs.h" +#include "termbox.h" +#include "widgets.h" +#include "cylgom.h" +#include + +void handle_desktop(void* input_struct, struct tb_event* event) +{ + struct desktop* target = (struct desktop*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + widget_desktop_move_cur(target, LEFT); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + widget_desktop_move_cur(target, RIGHT); + } + } + + tb_set_cursor(target->x + 2, target->y); +} + +void handle_text(void* input_struct, struct tb_event* event) +{ + struct input* target = (struct input*) input_struct; + + if ((event != NULL) && (event->type == TB_EVENT_KEY)) + { + if (event->key == TB_KEY_ARROW_LEFT) + { + widget_input_move_cur(target, LEFT); + } + else if (event->key == TB_KEY_ARROW_RIGHT) + { + widget_input_move_cur(target, RIGHT); + } + else if (event->key == TB_KEY_DELETE) + { + widget_input_delete(target); + } + else if ((event->key == TB_KEY_BACKSPACE) || (event->key == TB_KEY_BACKSPACE2)) + { + widget_input_backspace(target); + } + else if (((event->ch > 31) && (event->ch < 127)) || (event->key == TB_KEY_SPACE)) + { + char buf[7] = {0}; + + if (event->key == TB_KEY_SPACE) + { + buf[0] = ' '; + } + else + { + utf8_unicode_to_char(buf, event->ch); + } + + widget_input_write(target, buf[0]); + } + } + + tb_set_cursor(target->x + (target->cur - target->visible_start), target->y); +} diff --git a/src/inputs.h b/src/inputs.h new file mode 100644 index 0000000..b5dff07 --- /dev/null +++ b/src/inputs.h @@ -0,0 +1,9 @@ +#ifndef H_INPUTS +#define H_INPUTS + +#include "termbox.h" + +void handle_text(void* input_struct, struct tb_event* event); +void handle_desktop(void* input_struct, struct tb_event* event); + +#endif diff --git a/src/login.c b/src/login.c new file mode 100644 index 0000000..0c5af43 --- /dev/null +++ b/src/login.c @@ -0,0 +1,456 @@ +#define _XOPEN_SOURCE 700 +#define _DEFAULT_SOURCE + +#include "config.h" +#include "login.h" +#include "widgets.h" +#include "desktop.h" +#include "termbox.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef LY_SYSTEMD +#define PAM_SYSTEMD +#endif + +#include +#include + +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 get_free_display() +{ + int i; + char xlock[256]; + + for(i = 0; i < 200; ++i) + { + snprintf(xlock, sizeof(xlock), "/tmp/.X%d-lock", i); + + if(access(xlock, F_OK) == -1) + { + break; + } + } + + return i; +} + +enum err init_env(pam_handle_t* handle, struct passwd* pw) +{ + u16 i; + u16 len; + char tmp[256]; + char** env; + char* termenv; + + 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); + + + if (termenv) + { + setenv("TERM", termenv, 1); + } + else + { + setenv("TERM", "linux", 1); + } + + snprintf(tmp, sizeof(tmp), "%s/%s", pw->pw_dir, config.xauthority); + setenv("XAUTHORITY", tmp, 0); + 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", config.path, 1)) + { + return ERR; + } + + env = pam_getenvlist(handle); + + for (i = 0; env && env[i]; i++) + { + putenv(env[i]); + } + + return OK; +} + +void init_xdg(const char* tty_id, const char* display_name, + enum display_server 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 DS_SHELL: + setenv("XDG_SESSION_TYPE", "tty", 0); + break; + case DS_WAYLAND: + setenv("XDG_SESSION_TYPE", "wayland", 0); + break; + case DS_XINITRC: + case DS_XORG: + setenv("XDG_SESSION_TYPE", "x11", 0); + break; + } +} + +void reset_terminal(struct passwd* pwd) +{ + pid_t pid; + int status; + char cmd[256]; + + pid = fork(); + strncpy(cmd, "exec tput reset", sizeof(cmd)); + + if (pid == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(EXIT_SUCCESS); + } + + waitpid(pid, &status, 0); +} + +void launch_wayland(struct passwd* pwd, pam_handle_t* pam_handle, + const char* de_command) +{ + char cmd[32]; + snprintf(cmd, 32, "exec %s", de_command); + reset_terminal(pwd); + execl(pwd->pw_shell, pwd->pw_shell, "-l", "-c", cmd, NULL); + exit(EXIT_SUCCESS); +} + +void launch_shell(struct passwd* pwd, pam_handle_t* pam_handle) +{ + char* pos; + char args[256 + 2]; // arbitrary + args[0] = '-'; + strncpy(args + 1, ((pos = strrchr(pwd->pw_shell, + '/')) ? pos + 1 : pwd->pw_shell), sizeof(args) - 1); + reset_terminal(pwd); + execl(pwd->pw_shell, args, NULL); + exit(EXIT_SUCCESS); +} + +void launch_xorg(struct passwd* pwd, pam_handle_t* pam_handle, + const char* de_command, const char* display_name, const char* vt, + int xinitrc) +{ + FILE* file; + pid_t child; + int status; + char cmd[256]; + + // updates cookie + snprintf(cmd, + sizeof(cmd), + "exec xauth add %s . `%s`", + display_name, + config.mcookie_cmd); + + file = fopen(getenv("XAUTHORITY"), "ab+"); + fclose(file); + + // generates the cookie + child = fork(); + + if(child == 0) + { + execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); + exit(EXIT_SUCCESS); + } + + waitpid(child, &status, 0); + + // starts x + snprintf(cmd, sizeof(cmd), + "exec xinit %s %s%s -- %s %s %s %s %s %s -auth %s", + config.x_cmd_setup, + xinitrc ? "" : "/usr/bin/", + de_command, config.x_cmd, + config.hide_x ? "-keeptty >" : "", + config.hide_x ? config.hide_x_save_log : "", + config.hide_x ? "2>&1" : "", + display_name, vt, getenv("XAUTHORITY")); + reset_terminal(pwd); + execl(pwd->pw_shell, pwd->pw_shell, "-l", "-c", cmd, NULL); + exit(EXIT_SUCCESS); +} + +enum err login_desktop(struct desktop* desktop, + struct input* login, + struct input* password) +{ + int display_id; + char display_name[3]; + pid_t display_pid; + int display_status; + + const char* creds[2] = {login->text, password->text}; + struct pam_conv conv = {login_conv, creds}; + struct passwd* pwd = NULL; + pam_handle_t* handle; + int pam_result; + + enum display_server display_server = desktop->display_server[desktop->cur]; + char tty_id [3]; + char vt[5]; + + display_id = get_free_display(); + snprintf(display_name, sizeof(display_name), ":%d", display_id); + snprintf(tty_id, sizeof(tty_id), "%d", config.tty_id); + snprintf(vt, sizeof(vt), "vt%d", config.tty_id); + + // starting pam transations + pam_result = pam_start(config.service_name, login->text, &conv, &handle); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + pam_result = pam_authenticate(handle, 0); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + pam_result = pam_acct_mgmt(handle, 0); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + // initializes user groups + struct passwd* pw = getpwnam(login->text); + + if (!pw) + { + pam_end(handle, pam_result); + return ERR; + } + + int grp_result = initgroups(login->text, pw->pw_gid); + + if (grp_result == -1) + { + pam_end(handle, pam_result); + return ERR; + } + + // back to pam transactions + pam_result = pam_setcred(handle, PAM_ESTABLISH_CRED); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + pam_result = pam_open_session(handle, 0); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + // login error + if (handle == NULL) + { + return ERR; + } + + pwd = getpwnam(login->text); + + // clears the password in memory + widget_input_free(password); + widget_input(password, config.max_password_len); + + // launches the DE + display_pid = fork(); + + if (display_pid == 0) + { + // restores regular terminal mode + // doing this here to enable post-return cleanup + tb_clear(); + tb_present(); + tb_shutdown(); + + // initialization + clearenv(); + init_xdg(tty_id, display_name, display_server); + + // downgrades group permissions + if ((pwd == NULL) || (setgid(pwd->pw_gid) < 0)) + { + set_error(ERR_PERM_GROUP); + pam_end(handle, pam_result); + exit(EXIT_FAILURE); + } + + init_env(handle, pwd); + + // downgrades user permissions + if (setuid(pwd->pw_uid) < 0) + { + set_error(ERR_PERM_USER); + pam_end(handle, pam_result); + exit(EXIT_FAILURE); + } + + if (chdir(pwd->pw_dir) < 0) + { + set_error(ERR_PERM_DIR); + pam_end(handle, pam_result); + exit(EXIT_FAILURE); + } + + switch (display_server) + { + case DS_SHELL: + launch_shell(pwd, handle); + break; + case DS_WAYLAND: + launch_wayland(pwd, handle, desktop->cmd[desktop->cur]); + break; + case DS_XORG: + launch_xorg(pwd, handle, desktop->cmd[desktop->cur], + display_name, vt, 0); + break; + case DS_XINITRC: + launch_xorg(pwd, handle, desktop->cmd[desktop->cur], + display_name, vt, 1); + break; + } + + exit(EXIT_SUCCESS); + } + + // waits for the de/shell to exit + waitpid(display_pid, &display_status, 0); + + tb_init(); + tb_select_output_mode(TB_OUTPUT_TRUECOLOR); + + // reloads the desktop environment list on logout + widget_desktop_free(desktop); + widget_desktop(desktop); + desktop_load(desktop); + + info_line = lang.logout; + pam_result = pam_close_session(handle, 0); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + pam_result = pam_setcred(handle, PAM_DELETE_CRED); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + pam_result = pam_end(handle, pam_result); + + if (pam_result != PAM_SUCCESS) + { + pam_diagnose(pam_result); + pam_end(handle, pam_result); + return ERR; + } + + return OK; +} diff --git a/src/login.h b/src/login.h new file mode 100644 index 0000000..ee73aec --- /dev/null +++ b/src/login.h @@ -0,0 +1,9 @@ +#ifndef H_LOGIN +#define H_LOGIN + +#include "config.h" +#include "widgets.h" + +enum err login_desktop(struct desktop* desktop, struct input* login, struct input* password); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..7880597 --- /dev/null +++ b/src/main.c @@ -0,0 +1,179 @@ +#include "cylgom.h" +#include "termbox.h" +#include "draw.h" +#include "desktop.h" +#include "config.h" +#include "inputs.h" +#include "login.h" +#include "util.h" +#include +#include + +enum active_input {INPUT_DESKTOP, INPUT_LOGIN, INPUT_PASSWORD}; +enum shutdown {SHUTDOWN_NO, SHUTDOWN_YES, SHUTDOWN_REBOOT}; + +int main (int argc, char **argv) +{ + struct desktop desktop; + struct input login; + struct input password; + struct tb_event event; + enum shutdown shutdown = SHUTDOWN_NO; + enum active_input active_input; + enum err error = OK; + enum err status; + + void* input_structs[3] = + { + (void*) &desktop, + (void*) &login, + (void*) &password + }; + + void (*input_handles[3]) (void*, struct tb_event*) = + { + handle_desktop, + handle_text, + handle_text + }; + + active_input = INPUT_PASSWORD; + config_load("/etc/ly/config.ini"); + + widget_desktop(&desktop); + widget_input(&login, config.max_login_len); + widget_input(&password, config.max_password_len); + + desktop_load(&desktop); + + tb_init(); + tb_select_output_mode(TB_OUTPUT_TRUECOLOR); + tb_clear(); + + draw_init(); + draw_box(); + draw_labels(); + draw_f_commands(); + draw_lock_state(); + + position_input(&desktop, &login, &password); + load(&desktop, &login); + draw_desktop(&desktop); + draw_input(&login); + draw_input_mask(&password); + + (*input_handles[active_input])(input_structs[active_input], NULL); + + tb_present(); + + switch_tty(); + + bool run = true; + + while (run) + { + error = tb_peek_event(&event, config.min_refresh_delta); + + if (error < 0) + { + continue; + } + + if (event.type == TB_EVENT_KEY) + { + if (event.key == TB_KEY_F1) + { + shutdown = SHUTDOWN_YES; + break; + } + else if (event.key == TB_KEY_F2) + { + shutdown = SHUTDOWN_REBOOT; + break; + } + else if (event.key == TB_KEY_CTRL_C) + { + break; + } + else if ((event.key == TB_KEY_ARROW_UP) && (active_input > 0)) + { + --active_input; + } + else if (((event.key == TB_KEY_ARROW_DOWN) || (event.key == TB_KEY_ENTER)) + && (active_input < 2)) + { + ++active_input; + } + else if (event.key == TB_KEY_ENTER) + { + save(&desktop, &login); + status = login_desktop(&desktop, &login, &password); + load(&desktop, &login); + error = 1; // triggers cursor and screen update + + if (status != OK) + { + ++config.auth_fails; + config.old_min_refresh_delta = config.min_refresh_delta; + config.old_force_update = config.force_update; + config.min_refresh_delta = 10; + config.force_update = true; + } + } + } + + if (error > 0) + { + // calls the apropriate function depending on the active input + (*input_handles[active_input])(input_structs[active_input], &event); + } + + if (config.force_update || (error > 0)) + { + if (config.auth_fails < 10) + { + tb_clear(); + + draw_init(); + animate(); + draw_box(); + draw_labels(); + draw_f_commands(); + draw_lock_state(); + + position_input(&desktop, &login, &password); + draw_desktop(&desktop); + draw_input(&login); + draw_input_mask(&password); + } + else + { + cascade(); + } + } + + tb_present(); + } + + // TODO error + tb_shutdown(); + + widget_desktop_free(&desktop); + widget_input_free(&login); + widget_input_free(&password); + config_lang_free(); + free_hostname(); + + if (shutdown == SHUTDOWN_YES) + { + execl(config.shutdown_cmd, config.shutdown_cmd, "-h", "now", NULL); + } + if (shutdown == SHUTDOWN_REBOOT) + { + execl(config.shutdown_cmd, config.shutdown_cmd, "-r", "now", NULL); + } + + config_config_free(); + + return 0; +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..fa8d9f8 --- /dev/null +++ b/src/util.c @@ -0,0 +1,147 @@ +#define _XOPEN_SOURCE 700 +#include "util.h" +#include "config.h" +#include "widgets.h" +#include "cylgom.h" + +#include +#include + +// hostname +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char* hostname_backup; + +void hostname(char** out) +{ + struct addrinfo hints; + struct addrinfo* info; + char hostname[HOST_NAME_MAX + 1]; + char* dot; + int result; + + hostname[HOST_NAME_MAX] = '\0'; + gethostname(hostname, HOST_NAME_MAX); + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + result = getaddrinfo(hostname, "http", &hints, &info); + + if ((result == 0) && (info != NULL)) + { + dot = strchr(info->ai_canonname, '.'); + *out = strndup(info->ai_canonname, dot - info->ai_canonname); + } + else + { + *out = strdup(""); + } + + hostname_backup = *out; + freeaddrinfo(info); +} + +void free_hostname() +{ + free(hostname_backup); +} + +void switch_tty() +{ + FILE* console = fopen(config.console_dev, "w"); + + if (console == NULL) + { + info_line = lang.err_console_dev; + return; + } + + int fd = fileno(console); + + ioctl(fd, VT_ACTIVATE, config.tty); + ioctl(fd, VT_WAITACTIVE, config.tty); + + fclose(console); +} + +void save(struct desktop* desktop, struct input* login) +{ + if (config.save) + { + FILE* file = fopen(config.save_file, "wb+"); + + if (file != NULL) + { + fprintf(file, "%s\n%d", login->text, desktop->cur); + fclose(file); + } + } +} + +void load(struct desktop* desktop, struct input* login) +{ + if (config.load == 0) + { + return; + } + + FILE* file = fopen(config.save_file, "rb"); + + if (file == NULL) + { + return; + } + + char* line = malloc((config.max_login_len * (sizeof (char))) + 1); + + if (line == NULL) + { + fclose(file); + return; + } + + if (fgets(line, (config.max_login_len * (sizeof (char))) + 1, file)) + { + strncpy(login->text, line, login->len); + + int len = strlen(line); + + if (len == 0) + { + login->end = login->text; + } + else + { + login->end = login->text + len - 1; + login->text[len - 1] = '\0'; + } + } + else + { + fclose(file); + free(line); + return; + } + + if (fgets(line, (config.max_login_len * (sizeof (char))) + 1, file)) + { + int saved_cur = abs(atoi(line)); + + if (saved_cur < desktop->len) + { + desktop->cur = saved_cur; + } + } + + fclose(file); + free(line); +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..8eca64d --- /dev/null +++ b/src/util.h @@ -0,0 +1,12 @@ +#ifndef H_UTIL +#define H_UTIL + +#include "widgets.h" + +void hostname(char** out); +void free_hostname(); +void switch_tty(); +void save(struct desktop* desktop, struct input* login); +void load(struct desktop* desktop, struct input* login); + +#endif diff --git a/src/widgets.c b/src/widgets.c new file mode 100644 index 0000000..04b3109 --- /dev/null +++ b/src/widgets.c @@ -0,0 +1,184 @@ +#define _XOPEN_SOURCE 700 +#include "widgets.h" +#include "cylgom.h" +#include +#include +#include +#include + +enum err widget_desktop(struct desktop* target) +{ + enum err error = OK; + + // one default slot for the shell + target->list = NULL; + target->cmd = NULL; + target->display_server = NULL; + target->cur = 0; + target->len = 0; + + error |= widget_desktop_add(target, strdup(lang.shell), strdup(""), DS_SHELL); + error |= widget_desktop_add(target, strdup(lang.xinitrc), strdup(""), DS_XINITRC); + + return error; +} + +enum err widget_input(struct input* target, u64 len) +{ + enum err error = OK; + int ret; + + target->text = malloc(len + 1); + + if (target->text == NULL) + { + error = ERR; + } + else + { + // lock inputs memory so it won't swap and leak the password + // probably not relevant as most software is insecure as hell, + // but hey are we trying to write good code or not? + ret = mlock(target->text, len + 1); + + if (ret < 0) + { + error = SECURE_RAM; + } + + memset(target->text, 0, len + 1); + } + + target->cur = target->text; + target->end = target->text; + target->visible_start = target->text; + target->len = len; + + return error; +} + +void widget_desktop_free(struct desktop* target) +{ + if (target != NULL) + { + for (u16 i = 0; i < target->len; ++i) + { + if (target->list[i] != NULL) + { + free(target->list[i]); + } + if (target->cmd[i] != NULL) + { + free(target->cmd[i]); + } + } + + free(target->list); + free(target->cmd); + free(target->display_server); + } +} + +void widget_input_free(struct input* target) +{ + // wipes the passord from memory and + // restores the buffer's address as swappable + memset(target->text, 0, target->len); + munlock(target->text, target->len + 1); + free(target->text); +} + +void widget_desktop_move_cur(struct desktop* target, enum direction dest) +{ + if ((dest == RIGHT) && (target->cur < (target->len - 1))) + { + ++(target->cur); + } + + if ((dest == LEFT) && (target->cur > 0)) + { + --(target->cur); + } +} + +enum err widget_desktop_add(struct desktop* target, char* name, char* cmd, enum display_server display_server) +{ + ++(target->len); + target->list = realloc(target->list, target->len * (sizeof (char*))); + target->cmd = realloc(target->cmd, target->len * (sizeof (char*))); + target->display_server = realloc(target->display_server, target->len * (sizeof (enum display_server))); + target->cur = target->len - 1; + + if ((target->list == NULL) + || (target->cmd == NULL) + || (target->display_server == NULL)) + { + return ERR; + } + + target->list[target->cur] = name; + target->cmd[target->cur] = cmd; + target->display_server[target->cur] = display_server; + + return OK; +} + +void widget_input_move_cur(struct input* target, enum direction dest) +{ + if ((dest == RIGHT) && (target->cur < target->end)) + { + ++(target->cur); + + if ((target->cur - target->visible_start) > target->visible_len) + { + ++(target->visible_start); + } + } + + if ((dest == LEFT) && (target->cur > target->text)) + { + --(target->cur); + + if ((target->cur - target->visible_start) < 0) + { + --(target->visible_start); + } + } +} + +void widget_input_write(struct input* target, char ascii) +{ + if (ascii <= 0) + { + return; // unices do not support usernames and passwords other than ascii + } + + if ((target->end - target->text + 1) < target->len) + { + // moves the text on the right to add space for the new ascii char + memcpy(target->cur + 1, target->cur, target->end - target->cur); + ++(target->end); + // adds the new char and moves the cursor to the right + *(target->cur) = ascii; + widget_input_move_cur(target, RIGHT); + } +} + +void widget_input_delete(struct input* target) +{ + if (target->cur < target->end) + { + // moves the text on the right to overwrite the currently pointed char + memcpy(target->cur, target->cur + 1, target->end - target->cur + 1); + --(target->end); + } +} + +void widget_input_backspace(struct input* target) +{ + if (target->cur > target->text) + { + widget_input_move_cur(target, LEFT); + widget_input_delete(target); + } +} diff --git a/src/widgets.h b/src/widgets.h new file mode 100644 index 0000000..bfa614f --- /dev/null +++ b/src/widgets.h @@ -0,0 +1,49 @@ +#ifndef H_WIDGETS +#define H_WIDGETS + +#include "cylgom.h" +#include "config.h" +#include + +enum direction {LEFT, RIGHT}; + +struct input +{ + char* text; + char* end; + u64 len; + + char* cur; + char* visible_start; + u16 visible_len; + + u16 x; + u16 y; +}; + +struct desktop +{ + char** list; + char** cmd; + enum display_server* display_server; + + u16 cur; + u16 len; + u16 visible_len; + + u16 x; + u16 y; +}; + +enum err widget_desktop(struct desktop* target); +enum err widget_input(struct input* target, u64 len); +void widget_desktop_free(struct desktop* target); +void widget_input_free(struct input* target); +void widget_desktop_move_cur(struct desktop* target, enum direction dest); +enum err widget_desktop_add(struct desktop* target, char* name, char* cmd, enum display_server display_server); +void widget_input_move_cur(struct input* target, enum direction dest); +void widget_input_write(struct input* target, char ascii); +void widget_input_delete(struct input* target); +void widget_input_backspace(struct input* target); + +#endif diff --git a/sub/inih b/sub/inih new file mode 160000 index 0000000..0ee2bf2 --- /dev/null +++ b/sub/inih @@ -0,0 +1 @@ +Subproject commit 0ee2bf26abccc63ee0a5a416ed9cdf4d113d8c25 diff --git a/sub/termbox-next b/sub/termbox-next new file mode 160000 index 0000000..467501d --- /dev/null +++ b/sub/termbox-next @@ -0,0 +1 @@ +Subproject commit 467501d0fc3c4bd6889af54a62d2ff7580325444 diff --git a/xsetup.sh b/xsetup.sh new file mode 100755 index 0000000..84b8d54 --- /dev/null +++ b/xsetup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +~/.xsession +exec $@