diff --git a/hlogin.c b/hlogin.c
index 7aabfc2..d547d81 100644
--- a/hlogin.c
+++ b/hlogin.c
@@ -17,6 +17,8 @@
* You should have received a copy of the GNU General Public License
* along with hlogin. If not, see .
*/
+#define __USE_GNU
+#define _GNU_SOURCE
#include "config.h"
#include
@@ -26,6 +28,10 @@
#include
#include "gettext.h"
+#define VAR_(x) #x
+#define VAR(x) VAR_(x)
+#pragma message "LOCALEDIR=" VAR(LOCALEDIR)
+
#include
#include
#include
@@ -33,13 +39,16 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#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;
}
diff --git a/ui.c b/ui.c
index 179e90e..23f0ff5 100644
--- a/ui.c
+++ b/ui.c
@@ -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;
}
diff --git a/ui.h b/ui.h
index d76e8d4..678d891 100644
--- a/ui.h
+++ b/ui.h
@@ -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