it works!

This commit is contained in:
~keith 2022-05-20 05:31:14 +00:00
parent ac49127e85
commit ad66313883
Signed by: keith
GPG Key ID: 5BEBEEAB2C73D520
3 changed files with 751 additions and 74 deletions

560
hlogin.c
View File

@ -17,6 +17,8 @@
* You should have received a copy of the GNU General Public License
* along with hlogin. If not, see <https://www.gnu.org/licenses/>.
*/
#define __USE_GNU
#define _GNU_SOURCE
#include "config.h"
#include <stdio.h>
@ -26,6 +28,10 @@
#include <locale.h>
#include "gettext.h"
#define VAR_(x) #x
#define VAR(x) VAR_(x)
#pragma message "LOCALEDIR=" VAR(LOCALEDIR)
#include <unistd.h>
#include <sys/types.h>
#include <security/_pam_types.h>
@ -33,13 +39,16 @@
#include <security/pam_misc.h>
#include <syslog.h>
#include <pwd.h>
#include <grp.h>
#include <utmpx.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include "ui.h"
#define VAR_(x) #x
#define VAR(x) VAR_(x)
#pragma message "LOCALEDIR=" VAR(LOCALEDIR)
int pam_conv_handler(int msgc, const struct pam_message *msgv[], struct pam_response **resp, void *appdata_ptr)
{
if (msgc <= 0)
@ -49,12 +58,21 @@ int pam_conv_handler(int msgc, const struct pam_message *msgv[], struct pam_resp
if (!result)
return PAM_CONV_ERR;
char *info_concat = NULL;
size_t info_len;
for (int i = 0; i < msgc; i++) {
int r;
char *text;
switch (msgv[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
if (info_concat) {
ui_setup_message(info_concat);
ui_message_run();
free(info_concat);
info_concat = NULL;
}
ui_setup_dialog((char *) msgv[i]->msg, msgv[i]->msg_style == PAM_PROMPT_ECHO_OFF);
r = ui_run();
text = ui_get_text();
@ -68,12 +86,40 @@ int pam_conv_handler(int msgc, const struct pam_message *msgv[], struct pam_resp
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
goto FAILURE; // TODO implement info/error dialog
if (!info_concat) {
info_len = strlen((char *) msgv[i]->msg);
info_concat = realloc(info_concat, sizeof(char) * (info_len + 1));
strcpy(info_concat, (char *) msgv[i]->msg);
info_concat[info_len] = '\0';
} else {
info_concat[info_len] = '\n';
size_t pos = info_len + 1;
info_len = pos + strlen((char *) msgv[i]->msg);
info_concat = realloc(info_concat, sizeof(char) * (info_len + 1));
strcpy(info_concat + pos, (char *) msgv[i]->msg);
info_concat[info_len] = '\0';
}
result[i].resp_retcode = 0;
result[i].resp = NULL;
break;
default:
if (info_concat) {
ui_setup_message(info_concat);
ui_message_run();
free(info_concat);
info_concat = NULL;
}
goto FAILURE;
}
}
if (info_concat) {
ui_setup_message(info_concat);
ui_message_run();
free(info_concat);
info_concat = NULL;
}
*resp = result;
return PAM_SUCCESS;
@ -91,8 +137,167 @@ FAILURE:
return PAM_CONV_ERR;
}
/** number of seconds to hang after a login failure or error **/
/** number of seconds to hang after a login failure or error */
unsigned int fail_delay = 3;
/** maximum number of login attempts before a failure */
unsigned int max_tries = 5;
char *origin = "test_origin";
char *tty_path = NULL;
char *tty_name = NULL;
char *tty_id = NULL;
char *remote_host = NULL;
pid_t login_pid;
void log_utmp(char *login_name)
{
struct utmpx ut;
memset(&ut, 0, sizeof(ut));
struct utmpx *ent;
setutxent();
while ((ent = getutxent()))
if (ent->ut_pid == login_pid && ent->ut_type >= INIT_PROCESS && ent->ut_type <= DEAD_PROCESS)
break;
// try by tty_name
if (ent == NULL && tty_name) {
setutxent();
ut.ut_type = LOGIN_PROCESS;
strncpy(ut.ut_line, tty_name, sizeof(ut.ut_line));
ent = getutxline(&ut);
}
// try by tty_id
if (ent == NULL && tty_id) {
setutxent();
ut.ut_type = DEAD_PROCESS;
strncpy(ut.ut_id, tty_id, sizeof(ut.ut_id));
ent = getutxid(&ut);
}
// copy or create
if (ent)
memcpy(&ut, ent, sizeof(ut));
else
memset(&ut, 0, sizeof(ut));
// update utmp entry
strncpy(ut.ut_user, login_name, sizeof(ut.ut_user));
ut.ut_type = USER_PROCESS;
ut.ut_pid = login_pid;
if (tty_id && ut.ut_id[0] == 0)
strncpy(ut.ut_id, tty_id, sizeof(ut.ut_id));
if (tty_name)
strncpy(ut.ut_line, tty_name, sizeof(ut.ut_line));
if (remote_host)
strncpy(ut.ut_host, remote_host, sizeof(ut.ut_host));
// TODO copy remote_addr
// set time
struct timeval tv;
gettimeofday(&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;
pututxline(&ut);
endutxent();
updwtmpx(_PATH_WTMP, &ut);
}
const char *CANCEL_SENTINEL = "\0CANCEL\0";
/** try_login - prompt for login name & password and attempt to log in
* returns login_name on success or NULL on failure
*/
char *try_login(pam_handle_t *pam, unsigned int try_count)
{
int result;
// prompt for username
ui_setup_dialog(_("Login name:"), false);
result = ui_run();
char *login_name = ui_get_text();
if (result != 0)
return CANCEL_SENTINEL;
result = pam_set_item(pam, PAM_USER, login_name);
if (result != PAM_SUCCESS) {
syslog(LOG_NOTICE, _("Couldn't set PAM_USER: %s"), pam_strerror(pam, result));
ui_setup_message(_("Failed to begin authentication."));
ui_message_run();
return NULL;
}
result = pam_authenticate(pam, 0);
if (result != PAM_SUCCESS) {
// log to syslog
if (result == PAM_MAXTRIES || try_count >= max_tries)
syslog(LOG_NOTICE, _("TOO MANY LOGIN TRIES (%u) FROM '%s' FOR '%s', %s"),
try_count, origin, login_name, pam_strerror(pam, result));
else
syslog(LOG_NOTICE, _("FAILED LOGIN (%u) FROM '%s' FOR '%s', %s"),
try_count, origin, login_name, pam_strerror(pam, result));
// display error message
ui_setup_message(_("Authentication failed."));
ui_message_run();
return NULL;
}
return login_name;
}
int get_pam_login_name(pam_handle_t *pam, char **login_name)
{
const void *item = (const void *) *login_name;
int result = pam_get_item(pam, PAM_USER, &item);
*login_name = (char *) item;
return result;
}
void init_env(bool no_destroy, pam_handle_t *pam, struct passwd *pwd)
{
char *buf = getenv("TERM");
if (buf)
buf = strdup(buf);
if (!no_destroy)
environ = calloc(1, sizeof(char *));
setenv("HOME", pwd->pw_dir, 0);
setenv("USER", pwd->pw_name, 1);
setenv("LOGNAME", pwd->pw_name, 1);
setenv("SHELL", pwd->pw_shell, 1);
setenv("TERM", buf ? buf : "dumb", 1);
free(buf);
// TODO use ENV_PATH and ENV_SUPATH from logindefs
setenv("PATH", _PATH_DEFPATH, 0);
// apparently mailx is a bitch without this
asprintf(&buf, "%s/%s", _PATH_MAILDIR, pwd->pw_name);
setenv("MAIL", buf, 0);
free(buf);
char **pam_env = pam_getenvlist(pam);
for (int i = 0; pam_env && pam_env[i]; i++)
putenv(pam_env[i]);
}
static pid_t child_pid;
static volatile sig_atomic_t got_sig = 0;
static void handle_sig(int signal)
{
if (child_pid)
kill(-child_pid, signal);
else
got_sig = 1;
if (signal == SIGTERM)
kill(-child_pid, SIGHUP);
}
int main(int argc, char* argv[])
{
@ -104,10 +309,40 @@ int main(int argc, char* argv[])
openlog("hlogin", LOG_ODELAY, LOG_AUTHPRIV);
login_pid = getpid();
if (isatty(STDIN_FILENO))
tty_path = ttyname(STDIN_FILENO);
else if (isatty(STDOUT_FILENO))
tty_path = ttyname(STDOUT_FILENO);
if (tty_path) {
if (strncmp(tty_path, "/dev/", 5) == 0)
tty_name = tty_path + 5;
else
tty_name = tty_path;
for (char *x = tty_name; x && *x; x++) {
if ((*x) >= '0' && (*x) <= '9') {
tty_id = x;
break;
}
}
}
// TODO remote login stuff
if (remote_host)
origin = remote_host;
else if (tty_name)
origin = tty_name;
else
syslog(LOG_NOTICE, _("Couldn't determine origin: neither remote host nor TTY"));
pam_handle_t *pam = NULL;
struct pam_conv conv = { .conv = pam_conv_handler, .appdata_ptr = NULL };
result = pam_start("login", NULL, &conv, &pam);
result = pam_start(remote_host ? "remote" : "login", NULL, &conv, &pam);
if (result != PAM_SUCCESS) {
syslog(LOG_ERR, _("Couldn't initialize PAM: %s"), pam_strerror(pam, result));
fprintf(stderr, _("pam_start error: %s. Abort.\n"), pam_strerror(pam, result));
@ -115,30 +350,311 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
result = pam_set_item(pam, PAM_RHOST, remote_host);
if (result != PAM_SUCCESS) {
syslog(LOG_ERR, _("Couldn't set PAM_RHOST: %s"), pam_strerror(pam, result));
fprintf(stderr, _("pam_set_item error: %s. Abort.\n"), pam_strerror(pam, result));
sleep(fail_delay);
return EXIT_FAILURE;
}
if (tty_path) {
result = pam_set_item(pam, PAM_TTY, tty_path);
if (result != PAM_SUCCESS) {
syslog(LOG_ERR, _("Couldn't set PAM_TTY: %s"), pam_strerror(pam, result));
fprintf(stderr, _("pam_set_item error: %s. Abort.\n"), pam_strerror(pam, result));
sleep(fail_delay);
return EXIT_FAILURE;
}
}
ui_init();
// prompt for username
ui_setup_dialog(_("Login name:"), false);
result = ui_run();
char *login_name = ui_get_text();
if (result != 0)
goto END;
char *login_name;
for (unsigned int try = 1; try <= max_tries; try++) {
login_name = try_login(pam, try);
if (login_name)
break;
}
result = pam_set_item(pam, PAM_USER, login_name);
if (result != PAM_SUCCESS) {
endwin();
printf("pam_set_item error: %s\n", pam_strerror(pam, result));
if (login_name == CANCEL_SENTINEL) {
syslog(LOG_NOTICE, _("Login from '%s' aborted"), origin);
ui_end();
sleep(fail_delay);
return EXIT_SUCCESS;
}
if (!login_name) {
char *msg;
// TRANSLATORS: default value is 5
asprintf(&msg, _("Exceeded maximum login attempts (%u).\n"), max_tries);
ui_setup_message(msg);
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
result = pam_authenticate(pam, 0);
// Is user account valid?
result = pam_acct_mgmt(pam, 0);
if (result == PAM_NEW_AUTHTOK_REQD)
result = pam_chauthtok(pam, PAM_CHANGE_EXPIRED_AUTHTOK);
if (result != PAM_SUCCESS) {
endwin();
printf("pam_authenticate error: %s\n", pam_strerror(pam, result));
syslog(LOG_ERR, _("User validation failed for '%s': %s"), login_name, pam_strerror(pam, result));
switch (result) {
/*
case PAM_ACCT_EXPIRED:
asprintf(&msg, _("The user account '%s' has expired and cannot log in."), login_name);
break;
*/
case PAM_AUTHTOK_ERR:
case PAM_AUTHTOK_RECOVERY_ERR:
case PAM_AUTHTOK_LOCK_BUSY:
case PAM_AUTHTOK_DISABLE_AGING:
// asprintf(&msg, _("Failed to update login credentials for '%s'."), login_name);
ui_setup_message(_("Failed to update login credentials."));
break;
default:
// asprintf(&msg, _("Cannot log in as '%s'. Is the user account valid?"), login_name);
ui_setup_message(_("Cannot log in."));
}
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
// ensure we have the correct login_name
result = get_pam_login_name(pam, &login_name);
if (result != PAM_SUCCESS) {
syslog(LOG_ERR, _("Couldn't get PAM_USER: %s"), pam_strerror(pam, result));
ui_setup_message(_("Cannot log in. The user account data is missing or invalid."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
if ((!login_name) || login_name[0] == '\0') {
syslog(LOG_ERR, _("NULL user name. Abort."));
ui_setup_message(_("Cannot log in. The user account data is missing or invalid."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
END:
endwin();
struct passwd *pwd = getpwnam(login_name);
if (!pwd) {
syslog(LOG_ERR, _("No passwd entry for '%s'. Abort."), login_name);
ui_setup_message(_("Cannot log in. The user account data is missing or invalid."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
result = (pwd->pw_uid != 0)
? initgroups(login_name, pwd->pw_gid)
: setgroups(0, NULL);
if (result < 0) {
syslog(LOG_ERR, _("groups initialization failed: %m"));
ui_setup_message(_("Cannot log in. Unable to establish a session."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
// begin PAM session
result = pam_setcred(pam, PAM_ESTABLISH_CRED);
if (result != PAM_SUCCESS) {
syslog(LOG_ERR, _("Couldn't init PAM session: %s"), pam_strerror(pam, result));
ui_setup_message(_("Cannot log in. Unable to establish a session."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
result = pam_open_session(pam, 0);
if (result != PAM_SUCCESS) {
pam_setcred(pam, PAM_DELETE_CRED);
syslog(LOG_ERR, _("Couldn't init PAM session: %s"), pam_strerror(pam, result));
ui_setup_message(_("Cannot log in. Unable to establish a session."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
result = pam_setcred(pam, PAM_REINITIALIZE_CRED);
if (result != PAM_SUCCESS) {
pam_close_session(pam, 0);
syslog(LOG_ERR, _("Couldn't init PAM session: %s"), pam_strerror(pam, result));
ui_setup_message(_("Cannot log in. Unable to establish a session."));
ui_message_run();
sleep(fail_delay);
ui_end();
return EXIT_FAILURE;
}
endpwent();
ui_end();
log_utmp(login_name);
// TODO log audit and lastlog
syslog(LOG_INFO, _("LOGIN FROM '%s' BY '%s'"), origin, login_name);
// chown TTY
if (tty_path) {
// TODO set group as well
if (chown(tty_path, pwd->pw_uid, -1) != 0)
syslog(LOG_ERR, _("failed to chown TTY '%s': %m"), tty_path);
}
if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) {
syslog(LOG_ALERT, _("setgid() failed: %m"));
fprintf(stderr, _("error applying user GID: %m. Abort.\n"));
sleep(fail_delay);
return EXIT_FAILURE;
}
if ((!pwd->pw_shell) || pwd->pw_shell[0] == '\0')
pwd->pw_shell = _PATH_BSHELL;
init_env(false, pam, pwd);
// Get ready to fork!
// signal handlers - i think this is in case something comes in while we're forking?
struct sigaction sa, prev_hup, prev_term;
memset(&sa, 0, sizeof(sa));
signal(SIGALRM, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTSTP, SIG_IGN);
sa.sa_handler = SIG_IGN;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGHUP, &sa, &prev_hup);
if (tty_path)
ioctl(0, TIOCNOTTY, NULL);
sa.sa_handler = handle_sig;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGTERM, &sa, &prev_term);
closelog();
child_pid = fork();
if (child_pid < 0) {
fprintf(stderr, _("error forking: %m. Abort.\n"));
openlog("hlogin", LOG_ODELAY, LOG_AUTHPRIV);
syslog(LOG_ALERT, _("fork() failed: %m"));
pam_setcred(pam, PAM_DELETE_CRED);
pam_end(pam, pam_close_session(pam, 0));
sleep(fail_delay);
return EXIT_FAILURE;
}
if (child_pid) { // we're in the parent
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
sa.sa_handler = SIG_IGN;
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
while (wait(NULL) == -1 && errno == EINTR);
openlog("hlogin", LOG_ODELAY, LOG_AUTHPRIV);
pam_setcred(pam, PAM_DELETE_CRED);
pam_end(pam, pam_close_session(pam, 0));
return EXIT_SUCCESS;
}
// child
// reset signal handlers
sigaction(SIGHUP, &prev_hup, NULL);
sigaction(SIGTERM, &prev_term, NULL);
if (got_sig)
return EXIT_FAILURE;
setsid();
// reopen tty
if (tty_path) {
int fd = open(tty_path, O_RDWR | O_NONBLOCK);
if (fd == -1) {
syslog(LOG_ERR, _("can't reopen tty: %m"));
return EXIT_FAILURE;
}
if (!isatty(fd)) {
close(fd);
syslog(LOG_ERR, _("%s is not a tty"), tty_path);
return EXIT_FAILURE;
}
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
// set tty as stdin, stdout, and stderr
for (int i = 0; i < fd; i++)
close(i);
for (int i = 0; i < 3; i++)
if (fd != i)
dup2(fd, i);
if (fd >= 3)
close(fd);
}
openlog("hlogin", LOG_ODELAY, LOG_AUTHPRIV);
if (ioctl(0, TIOCSCTTY, 1) != 0)
syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
signal(SIGINT, SIG_DFL);
// continue becoming the user
if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) {
syslog(LOG_ALERT, _("setuid() failed: %m"));
fprintf(stderr, _("error applying user UID: %m. Abort.\n"));
sleep(fail_delay);
return EXIT_FAILURE;
}
if (chdir(pwd->pw_dir) < 0) {
syslog(LOG_ALERT, _("failed to chdir to home (%s): %m"), pwd->pw_dir);
fprintf(stderr, _("error changing to home directory: %m. Abort.\n"));
sleep(fail_delay);
return EXIT_FAILURE;
}
pam_end(pam, PAM_SUCCESS | PAM_DATA_SILENT);
// execute login shell
char **shell_argv = calloc(2, sizeof(char *));
char *shell_base = strrchr(pwd->pw_shell, '/');
if (shell_base)
shell_base++;
else
shell_base = pwd->pw_shell;
shell_argv[0] = malloc(sizeof(char) * (strlen(shell_base) + 2));
shell_argv[0][0] = '-';
strcpy(shell_argv[0] + 1, shell_base);
execvp(pwd->pw_shell, shell_argv);
return EXIT_SUCCESS;
}

258
ui.c
View File

@ -55,6 +55,9 @@ PANEL *error_shadow_p;
const int login_rows = 8;
const int login_cols = 48;
int error_rows, error_cols;
int error_curx;
char *input_buf;
size_t input_buflen;
int input_pos;
@ -120,6 +123,22 @@ FAILURE:
return unknown_str;
}
void safe_free(void *ptr, size_t size)
{
memset(ptr, (int)'\0', size);
free(ptr);
}
void *safe_realloc(void *ptr, size_t size, size_t new_size)
{
void *new_ptr = malloc(new_size);
if (new_ptr == NULL)
return NULL;
memcpy(new_ptr, ptr, size);
safe_free(ptr, size);
return new_ptr;
}
/*
char *xgetnisdomainname()
{
@ -148,14 +167,20 @@ char erase_ch;
void ui_setup_dialog(char *prompt, bool password)
{
input_buf[0] = '\0';
memset(input_buf, (int)'\0', sizeof(char) * input_buflen);
input_pos = 0;
input_scroll = 0;
btn_focus = 0;
btn_count = 2;
msg = prompt;
hide_input = password;
hide_panel(error_p);
hide_panel(error_shadow_p);
show_panel(login_shadow_p);
top_panel(login_p);
wclear(login_w);
draw_outline(login_w, login_rows, login_cols, 0, 0, false);
paint_login();
@ -163,12 +188,56 @@ void ui_setup_dialog(char *prompt, bool password)
void ui_setup_message(char *text)
{
msg = text;
// TODO insert word wrap logic here
int msg_len = strlen(text);
btn_focus = 0;
btn_count = 1;
int tcols = login_cols - 4;
int trows = 1;
char **msg_lines = word_wrap(text, tcols, tcols / 2, &trows);
if (trows == 1)
tcols = strlen(msg_lines[0]);
if (tcols < 16)
tcols = 16;
error_rows = trows + 4;
error_cols = tcols + 4;
hide_panel(login_shadow_p);
wresize(error_w, error_rows, error_cols);
wresize(error_shadow_w, error_rows, error_cols);
replace_panel(error_shadow_p, error_shadow_w);
show_panel(error_shadow_p);
move_panel(error_shadow_p, ((term_rows - error_rows) / 2) + 1, ((term_cols - error_cols) / 2) + 1);
replace_panel(error_p, error_w);
show_panel(error_p);
move_panel(error_p, (term_rows - error_rows) / 2, (term_cols - error_cols) / 2);
wbkgd(error_shadow_w, ' ' | COLOR_PAIR(4));
wclear(error_shadow_w);
touchwin(error_shadow_w);
wclear(error_w);
draw_outline(error_w, error_rows, error_cols, 0, 0, false);
// draw text
wattron(error_w, COLOR_PAIR(2));
for (int i = 0; i < trows; i++) {
wmove(error_w, i + 1, 2);
waddstr(error_w, msg_lines[i]);
free(msg_lines[i]);
}
free(msg_lines);
// draw button
wmove(error_w, error_rows - 2, 2);
wattron(error_w, A_REVERSE);
wprintw(error_w, "< %s >", _("OK"));
error_curx = error_w->_curx - 2;
wattroff(error_w, A_REVERSE);
}
/** ui_init - initialize the UI subsystem
@ -201,7 +270,7 @@ void ui_init()
init_pair(1, COLOR_WHITE, COLOR_BLUE);
init_pair(2, COLOR_BLACK, COLOR_WHITE);
init_pair(3, COLOR_WHITE, COLOR_WHITE);
init_pair(4, COLOR_RED, COLOR_WHITE);
init_pair(4, COLOR_BLACK, COLOR_BLACK);
// set up background window
back_w = newwin(term_rows, term_cols, 0, 0);
@ -214,10 +283,10 @@ void ui_init()
// set up login shadow window
login_shadow_w = newwin(login_rows, login_cols, ((term_rows - login_rows) / 2) + 1, ((term_cols - login_cols) / 2) + 1);
login_shadow_p = new_panel(login_shadow_w);
wbkgd(login_shadow_w, ' ' | COLOR_PAIR(0));
wbkgd(login_shadow_w, ' ' | COLOR_PAIR(4));
wclear(login_shadow_w);
// set up dialog window
// set up login window
login_w = newwin(login_rows, login_cols, (term_rows - login_rows) / 2, (term_cols - login_cols) / 2);
login_p = new_panel(login_w);
wattron(login_w, COLOR_PAIR(2));
@ -226,8 +295,42 @@ void ui_init()
draw_outline(login_w, login_rows, login_cols, 0, 0, false);
paint_login();
// set up error shadow window
error_shadow_w = newwin(2, 2, 1, 1);
error_shadow_p = new_panel(error_shadow_w);
wbkgd(error_shadow_w, ' ' | COLOR_PAIR(4));
wclear(error_shadow_w);
hide_panel(error_shadow_p);
// set up error window
error_w = newwin(1, 1, 0, 0);
error_p = new_panel(error_w);
wbkgd(error_w, ' ' | COLOR_PAIR(2));
wclear(error_w);
hide_panel(error_p);
// enable keypad mode
keypad(login_w, true);
keypad(error_w, true);
}
void ui_end()
{
safe_free(input_buf, input_buflen);
del_panel(back_p);
del_panel(login_p);
del_panel(login_shadow_p);
del_panel(error_p);
del_panel(error_shadow_p);
delwin(back_w);
delwin(login_w);
delwin(login_shadow_w);
delwin(error_w);
delwin(error_shadow_w);
endwin();
}
/** paint_back - redraw info displayed on background
@ -327,10 +430,55 @@ int ui_update()
return -1;
}
int ui_error_update()
{
paint_back();
wmove(error_w, error_rows - 2, error_curx);
update_panels();
doupdate();
int ch = wgetch(error_w);
if (ch == ERR)
return -1;
if (ch == 0x1B)
return -2;
else if (ch == KEY_ENTER || ch == '\r' || ch == '\n')
return btn_focus;
return -1;
}
int ui_run()
{
int result;
while ((result = ui_update()) == -1);
btn_focus = -1;
paint_back();
paint_login();
update_panels();
doupdate();
return result;
}
int ui_message_run()
{
int result;
while ((result = ui_error_update()) == -1);
btn_focus = -1;
paint_back();
wmove(error_w, error_rows - 2, 2);
wattron(error_w, COLOR_PAIR(2));
wprintw(error_w, "< %s >", _("OK"));
wmove(error_w, error_rows - 2, error_curx);
update_panels();
doupdate();
hide_panel(error_p);
hide_panel(error_shadow_p);
show_panel(login_shadow_p);
top_panel(login_p);
clear();
return result;
}
@ -339,6 +487,7 @@ char *ui_get_text()
unsigned int len = strlen(input_buf) + 1;
char *buf = malloc(len * sizeof(char));
strcpy(buf, input_buf);
memset(input_buf, (int)'\0', input_buflen * sizeof(char));
return buf;
}
@ -346,8 +495,8 @@ void input_add_char(char ch)
{
// resize if necessary
if ((strlen(input_buf) + 2) > input_buflen) {
input_buf = safe_realloc(input_buf, input_buflen * sizeof(char), input_buflen * 2 * sizeof(char));
input_buflen *= 2;
input_buf = realloc(input_buf, input_buflen * sizeof(char));
}
// insert new char & shift everything over
for (int i = (input_pos++); i < input_buflen; i++) {
@ -412,46 +561,37 @@ void draw_outline(WINDOW *win, int height, int width, int y, int x, bool inset)
/** output_word - used in word_wrap
*/
void output_word(char *word, size_t len,
char **out, size_t *out_size, int *out_pos,
int *line_pos, int *lines, int width)
char ***out, int *line_pos, int *lines, int width)
{
int i = 0;
while (i < len) {
int space = width - (*line_pos);
int remain = len - i;
if (space > remain) { // can fit on line
if ((*out_size) < (*out_pos) + remain) {
*out_size *= 2;
*out = realloc(*out, sizeof(char) * (*out_size));
}
// copy word
memcpy((*out) + (*out_pos), word + i, sizeof(char) * remain);
memcpy((*out)[(*lines) - 1] + (*line_pos), word + i, sizeof(char) * remain);
i += remain;
*out_pos += remain;
*line_pos += remain;
} else {
if ((*out_size) < (*out_pos) + space + 1) {
*out_size *= 2;
*out = realloc(*out, sizeof(char) * (*out_size));
}
// copy part of word
memcpy((*out) + (*out_pos), word + i, sizeof(char) * space);
memcpy((*out)[(*lines) - 1] + (*line_pos), word + i, sizeof(char) * space);
i += space;
*out_pos += space;
*line_pos += space;
// add newline
(*out)[(*out_pos)++] = '\n';
(*out)[(*lines) - 1][(*line_pos)] = '\0';
*line_pos = 0;
(*lines)++;
*out = realloc(*out, sizeof(char *) * (*lines));
(*out)[(*lines) - 1] = malloc(sizeof(char) * (width + 1));
}
}
}
char *word_wrap(char *text, int width, int max_word, int *lines)
char **word_wrap(char *text, int width, int max_word, int *lines)
{
// output buffer
size_t out_size = 256;
char *out = malloc(sizeof(char) * out_size);
int out_pos = 0;
// output lines
*lines = 1;
char **out = malloc(sizeof(char *) * (*lines));
out[0] = malloc(sizeof(char) * (width + 1));
// word buffer
char *word = malloc(sizeof(char) * max_word);
int word_len = 0;
@ -464,31 +604,28 @@ char *word_wrap(char *text, int width, int max_word, int *lines)
case ' ':
if (word[0] == '\0')
word_len = 0;
// ensure buffer is large enough
if (out_size < out_pos + word_len + 2) {
out_size *= 2;
out = realloc(out, sizeof(char) * out_size);
}
if (word_len > 0) {
// do we have to wrap to a new line?
if (line_pos + word_len > width) {
out[out_pos++] = '\n';
out[(*lines) - 1][line_pos] = '\0';
line_pos = 0;
(*lines)++;
out = realloc(out, sizeof(char *) * (*lines));
out[(*lines) - 1] = malloc(sizeof(char) * (width + 1));
}
memcpy(out + out_pos, word, sizeof(char) * word_len);
word_len = 0;
out_pos += word_len;
memcpy(out[(*lines) - 1] + line_pos, word, sizeof(char) * word_len);
line_pos += word_len;
word_len = 0;
}
// output \n
if (ch == '\n' || line_pos >= width) {
out[out_pos++] = '\n';
out[(*lines) - 1][line_pos] = '\0';
line_pos = 0;
(*lines)++;
out = realloc(out, sizeof(char *) * (*lines));
out[(*lines) - 1] = malloc(sizeof(char) * (width + 1));
} else {
out[out_pos++] = ' ';
line_pos++;
out[(*lines) - 1][line_pos++] = ' ';
}
break;
default:
@ -497,26 +634,45 @@ char *word_wrap(char *text, int width, int max_word, int *lines)
if (word[0] != '\0') {
// write what we have
output_word(word, word_len,
&out, &out_size, &out_pos,
&line_pos, lines, width);
&out, &line_pos, lines, width);
word[0] = '\0';
}
// ensure buffer is large enough
if (out_size < out_pos + 2) {
out_size *= 2;
out = realloc(out, sizeof(char) * out_size);
}
// write char and maybe newline
out[out_pos++] = ch;
if ((++line_pos) >= width) {
out[out_pos++] = '\n';
out[(*lines) - 1][line_pos++] = ch;
if (line_pos >= width) {
out[(*lines) - 1][line_pos] = '\0';
line_pos = 0;
(*lines)++;
out = realloc(out, sizeof(char *) * (*lines));
out[(*lines) - 1] = malloc(sizeof(char) * (width + 1));
}
} else
word[word_len++] = ch;
}
}
if (word_len > 0 && word[0] != '\0') {
// do we have to wrap to a new line?
if (line_pos + word_len > width) {
out[(*lines) - 1][line_pos] = '\0';
line_pos = 0;
(*lines)++;
out = realloc(out, sizeof(char *) * (*lines));
out[(*lines) - 1] = malloc(sizeof(char) * (width + 1));
}
memcpy(out[(*lines) - 1] + line_pos, word, sizeof(char) * word_len);
line_pos += word_len;
word_len = 0;
}
free(word);
if (line_pos == 0)
// remove last line
free(out[--(*lines)]);
else
// non-empty line, add null terminator
out[(*lines) - 1][line_pos] = '\0';
return out;
}

7
ui.h
View File

@ -27,7 +27,11 @@
extern int term_rows, term_cols;
void *safe_realloc(void *ptr, size_t size, size_t new_size);
void safe_free(void *ptr, size_t size);
void ui_init();
void ui_end();
void paint_back();
void paint_login();
@ -37,6 +41,7 @@ void ui_setup_message(char *text);
int ui_update();
int ui_run();
int ui_message_run();
char *ui_get_text();
void input_add_char(char ch);
@ -45,6 +50,6 @@ void input_del_char();
void draw_outline(WINDOW *win, int height, int width, int y, int x, bool inset);
char *word_wrap(char *text, int width, int max_word, int *lines);
char **word_wrap(char *text, int width, int max_word, int *lines);
#endif // __UI_H