diff --git a/Makefile b/Makefile index 32625be8..d6196f08 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ games \ init \ kblayout \ kblayout-compiler \ +login \ mbr \ mkinitrd \ regress \ diff --git a/login/.gitignore b/login/.gitignore new file mode 100644 index 00000000..dd1a6026 --- /dev/null +++ b/login/.gitignore @@ -0,0 +1,3 @@ +login +*.o +arrow.inc diff --git a/login/Makefile b/login/Makefile new file mode 100644 index 00000000..6b88aa28 --- /dev/null +++ b/login/Makefile @@ -0,0 +1,41 @@ +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=$(DEFAULT_OPTLEVEL) +CFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\" +CFLAGS:=$(CXXFLAGS) -Wall -Wextra + +BINARY=login + +OBJS=\ +framebuffer.o \ +graphical.o \ +login.o \ +pixel.o \ +vgafont.o \ + +all: $(BINARY) + +.PHONY: all install clean + +$(BINARY): $(OBJS) + $(CC) $(OBJS) -o $(BINARY) $(CXXFLAGS) $(LIBS) + +%.o: %.c arrow.inc + $(CC) -std=gnu11 $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + +arrow.inc: arrow.rgb + carray -cs --identifier=arrow arrow.rgb -o $@ + +install: all + mkdir -p $(DESTDIR)$(SBINDIR) + install $(BINARY) $(DESTDIR)$(SBINDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp login.8 $(DESTDIR)$(MANDIR)/man8/login.8 + +clean: + rm -f $(BINARY) $(OBJS) *.o arrow.inc diff --git a/login/arrow.rgb b/login/arrow.rgb new file mode 100644 index 00000000..6d861ba5 Binary files /dev/null and b/login/arrow.rgb differ diff --git a/login/framebuffer.c b/login/framebuffer.c new file mode 100644 index 00000000..a43ba4ae --- /dev/null +++ b/login/framebuffer.c @@ -0,0 +1,176 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + framebuffer.c + Framebuffer utilities. + +*******************************************************************************/ + +#include +#include +#include +#include + +#include "framebuffer.h" +#include "pixel.h" +#include "vgafont.h" + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height) +{ + // Crop the framebuffer horizontally. + if ( fb.xres < left ) + left = fb.xres; + fb.buffer += left; + fb.xres -= left; + if ( width < fb.xres ) + fb.xres = width; + + // Crop the framebuffer vertically. + if ( fb.yres < top ) + top = fb.yres; + fb.buffer += top * fb.pitch; + fb.yres -= top; + if ( height < fb.yres ) + fb.yres = height; + + return fb; +} + +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + for ( size_t x = 0; x < src.xres; x++ ) + framebuffer_set_pixel(dst, x, y, framebuffer_get_pixel(src, x, y)); +} + +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + { + for ( size_t x = 0; x < src.xres; x++ ) + { + uint32_t bg = framebuffer_get_pixel(dst, x, y); + uint32_t fg = framebuffer_get_pixel(src, x, y); + framebuffer_set_pixel(dst, x, y, blend_pixel(bg, fg)); + } + } +} + +struct framebuffer framebuffer_crop_int(struct framebuffer fb, + int left, + int top, + int width, + int height) +{ + if ( left < 0 ) { width -= -left; left = 0; } + if ( top < 0 ) { top -= -height; top -= 0; } + if ( width < 0 ) { width = 0; } + if ( height < 0 ) { height = 0; } + return framebuffer_crop(fb, left, top, width, height); +} + +struct framebuffer framebuffer_cut_left_x(struct framebuffer fb, int offset) +{ + fb = framebuffer_crop_int(fb, offset, 0, fb.xres - offset, fb.yres); + return fb; +} + +struct framebuffer framebuffer_cut_right_x(struct framebuffer fb, int offset) +{ + fb = framebuffer_crop_int(fb, 0, 0, fb.xres - offset, fb.yres); + return fb; +} + +struct framebuffer framebuffer_cut_top_y(struct framebuffer fb, int offset) +{ + fb = framebuffer_crop_int(fb, 0, offset, fb.xres, fb.yres - offset); + return fb; +} + +struct framebuffer framebuffer_cut_bottom_y(struct framebuffer fb, int offset) +{ + fb = framebuffer_crop_int(fb, 0, 0, fb.xres, fb.yres - offset); + return fb; +} + +struct framebuffer framebuffer_center_x(struct framebuffer fb, int x, int width) +{ + x = x - width / 2; + if ( x < 0 ) { width -= -x; x = 0; } + if ( width < 0 ) { width = 0; } + fb = framebuffer_crop(fb, x, 0, width, fb.yres); + return fb; +} + +struct framebuffer framebuffer_center_y(struct framebuffer fb, int y, int height) +{ + y = y - height / 2; + if ( y < 0 ) { height -= -y; y = 0; } + if ( height < 0 ) { height = 0; } + fb = framebuffer_crop(fb, 0, y, fb.xres, height); + return fb; +} + +struct framebuffer framebuffer_right_x(struct framebuffer fb, int x, int width) +{ + x = x - width; + if ( x < 0 ) { width -= -x; x = 0; } + if ( width < 0 ) { width = 0; } + fb = framebuffer_crop(fb, x, 0, width, fb.yres); + return fb; +} + +struct framebuffer framebuffer_bottom_y(struct framebuffer fb, int y, int height) +{ + y = y - height; + if ( y < 0 ) { height -= -y; y = 0; } + if ( height < 0 ) { height = 0; } + fb = framebuffer_crop(fb, 0, y, fb.xres, height); + return fb; +} + +struct framebuffer framebuffer_center_text_x(struct framebuffer fb, int x, const char* str) +{ + int width = (FONT_WIDTH + 1) * strlen(str); + return framebuffer_center_x(fb, x, width); +} + +struct framebuffer framebuffer_center_text_y(struct framebuffer fb, int y, const char* str) +{ + (void) str; + int height = FONT_HEIGHT; + return framebuffer_center_y(fb, y, height); +} + +struct framebuffer framebuffer_right_text_x(struct framebuffer fb, int x, const char* str) +{ + int width = (FONT_WIDTH + 1) * strlen(str); + return framebuffer_right_x(fb, x, width); +} + +struct framebuffer framebuffer_bottom_text_y(struct framebuffer fb, int y, const char* str) +{ + (void) str; + int height = FONT_HEIGHT; + return framebuffer_bottom_y(fb, y, height); +} diff --git a/login/framebuffer.h b/login/framebuffer.h new file mode 100644 index 00000000..d67800f8 --- /dev/null +++ b/login/framebuffer.h @@ -0,0 +1,83 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + framebuffer.h + Framebuffer utilities. + +*******************************************************************************/ + +#ifndef FRAMEBUFFER_H +#define FRAMEBUFFER_H + +#include +#include + +struct framebuffer +{ + size_t pitch; + uint32_t* buffer; + size_t xres; + size_t yres; +}; + +static inline uint32_t framebuffer_get_pixel(const struct framebuffer fb, + size_t x, + size_t y) +{ + if ( fb.xres <= x || fb.yres <= y ) + return 0; + return fb.buffer[y * fb.pitch + x]; +} + +static inline void framebuffer_set_pixel(const struct framebuffer fb, + size_t x, + size_t y, + uint32_t value) +{ + if ( fb.xres <= x || fb.yres <= y ) + return; + fb.buffer[y * fb.pitch + x] = value; +} + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height); +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src); +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src); +struct framebuffer framebuffer_crop_int(struct framebuffer fb, + int left, + int top, + int width, + int height); +struct framebuffer framebuffer_cut_left_x(struct framebuffer fb, int offset); +struct framebuffer framebuffer_cut_right_x(struct framebuffer fb, int offset); +struct framebuffer framebuffer_cut_top_y(struct framebuffer fb, int offset); +struct framebuffer framebuffer_cut_bottom_y(struct framebuffer fb, int offset); +struct framebuffer framebuffer_center_x(struct framebuffer fb, int x, int width); +struct framebuffer framebuffer_center_y(struct framebuffer fb, int y, int height); +struct framebuffer framebuffer_right_x(struct framebuffer fb, int x, int width); +struct framebuffer framebuffer_bottom_y(struct framebuffer fb, int y, int height); +struct framebuffer framebuffer_center_text_x(struct framebuffer fb, int x, const char* str); +struct framebuffer framebuffer_center_text_y(struct framebuffer fb, int y, const char* str); +struct framebuffer framebuffer_right_text_x(struct framebuffer fb, int x, const char* str); +struct framebuffer framebuffer_bottom_text_y(struct framebuffer fb, int y, const char* str); + +#endif diff --git a/login/graphical.c b/login/graphical.c new file mode 100644 index 00000000..3065fc1b --- /dev/null +++ b/login/graphical.c @@ -0,0 +1,845 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + graphical.c + Graphical login. + +*******************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: The Sortix doesn't expose this at the moment. +#if !defined(HOST_NAME_MAX) && defined(__sortix__) +#include +#endif + +#include "framebuffer.h" +#include "login.h" +#include "pixel.h" +#include "vgafont.h" + +#include "arrow.inc" + +enum stage +{ + STAGE_USERNAME, + STAGE_PASSWORD, + STAGE_CHECKING, +}; + +struct textbox +{ + char text[256]; + size_t used; + size_t offset; + const char* standin; + bool password; +}; + +static uint32_t arrow_buffer[48 * 48]; +static struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 }; + +static inline void arrow_initialize() +{ + static bool done = false; + if ( done ) + return; + memcpy(arrow_buffer, arrow, sizeof(arrow)); + done = true; +} + +static struct textbox textbox_username; +static struct textbox textbox_password; + +struct glogin +{ + struct check chk; + int fd_mouse; + struct dispmsg_crtc_mode mode; + struct framebuffer fade_from_fb; + struct timespec fade_from_begin; + struct timespec fade_from_end; + bool fading_from; + uint32_t* last_fb_buffer; + size_t last_fb_buffer_size; + int pointer_x; + int pointer_y; + size_t mouse_byte_count; + uint8_t mouse_bytes[3]; + enum stage stage; + bool animating; + const char* warning; + bool pointer_working; +}; + +static struct glogin state; + +static bool get_graphical_mode(struct dispmsg_crtc_mode* mode) +{ + struct dispmsg_get_crtc_mode msg; + memset(&msg, 0, sizeof(msg)); + msg.msgid = DISPMSG_GET_CRTC_MODE; + msg.device = 0; // TODO: Multi-screen support! + msg.connector = 0; // TODO: Multi-screen support! + if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) + { + warn("dispmsg_issue: DISPMSG_GET_CRTC_MODE"); + return false; + } + *mode = msg.mode; + return true; +} + +static bool is_graphical_mode(struct dispmsg_crtc_mode* mode) +{ + return (mode->control & DISPMSG_CONTROL_VALID) && + !(mode->control & DISPMSG_CONTROL_VGA) && + mode->fb_format == 32; +} + +static void textbox_initialize(struct textbox* textbox, const char* standin) +{ + memset(textbox, 0, sizeof(*textbox)); + textbox->standin = standin; +} + +static void textbox_reset(struct textbox* textbox) +{ + explicit_bzero(textbox->text, sizeof(textbox->text)); + textbox->used = 0; + textbox->offset = 0; +} + +static void textbox_type_char(struct textbox* textbox, char c) +{ + if ( textbox->used + 1 == sizeof(textbox->text) ) + return; + memmove(textbox->text + textbox->offset + 1, + textbox->text + textbox->offset, + textbox->used - textbox->offset + 1); + textbox->text[textbox->offset++] = c; + textbox->used++; +} + +static void textbox_type_backspace(struct textbox* textbox) +{ + if ( textbox->offset == 0 ) + return; + memmove(textbox->text + textbox->offset - 1, + textbox->text + textbox->offset, + textbox->used - textbox->offset + 1); + textbox->offset--; + textbox->used--; +} + +void render_right_text(struct framebuffer fb, const char* str, uint32_t color) +{ + size_t len = strlen(str); + for ( size_t i = 0; i < len; i++ ) + { + int x = fb.xres - ((int) FONT_WIDTH+1) * ((int) len - (int) i); + render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), str[i], color); + } +} + +void render_right_text_if_needed(struct framebuffer fb, const char* str, uint32_t color) +{ + size_t len = strlen(str); + size_t shown_len = fb.xres / (FONT_WIDTH+1); + if ( len <= shown_len ) + render_text(fb, str, color); + else + render_right_text(fb, str, color); +} + +static void render_background(struct framebuffer fb) +{ + uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3); + for ( size_t y = 0; y < fb.yres; y++ ) + for ( size_t x = 0; x < fb.xres; x++ ) + framebuffer_set_pixel(fb, x, y, bg_color); +} + +static void render_pointer(struct framebuffer fb) +{ + int p_hwidth = arrow_framebuffer.xres / 2; + int p_hheight = arrow_framebuffer.yres / 2; + int p_x = state.pointer_x - p_hwidth; + int p_y = state.pointer_y - p_hheight; + struct framebuffer arrow_render = arrow_framebuffer; + if ( p_x < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, -p_x, 0, arrow_render.xres, arrow_render.yres); + p_x = 0; + } + if ( p_y < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, 0, -p_y, arrow_render.xres, arrow_render.yres); + p_y = 0; + } + struct framebuffer fb_dst = framebuffer_crop(fb, p_x, p_y, fb.xres, fb.yres); + framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render); +} + +static char* brand_line() +{ + char version[64]; + version[0] = '\0'; + kernelinfo("version", version, sizeof(version)); + char* result = NULL; + asprintf(&result, "%s %s - %s", + BRAND_OPERATING_SYSTEM_NAME, + version, + BRAND_DISTRIBUTION_WEBSITE); + return result; +} + +static void render_information(struct framebuffer fb) +{ + struct framebuffer textfb; + + char* brandstr = brand_line(); + if ( brandstr ) + { + textfb = fb; + textfb = framebuffer_center_text_x(textfb, fb.xres/2, brandstr); + textfb = framebuffer_bottom_text_y(textfb, fb.yres, brandstr); + render_text(textfb, brandstr, make_color(255, 255, 255)); + free(brandstr); + } +} + +static void render_textbox(struct framebuffer fb, struct textbox* textbox) +{ + for ( int y = 0; y < (int) fb.yres; y++ ) + { + for ( int x = 0; x < (int) fb.xres; x++ ) + { + uint32_t color; + if ( x == 0 || x == (int) fb.xres - 1 || + y == 0 || y == (int) fb.yres - 1 ) + color = make_color(32, 32, 32); + else + color = make_color(255, 255, 255); + framebuffer_set_pixel(fb, x, y, color); + } + } + + fb = framebuffer_cut_left_x(fb, 6); + fb = framebuffer_cut_right_x(fb, 6); + fb = framebuffer_cut_top_y(fb, 6); + fb = framebuffer_cut_bottom_y(fb, 6); + if ( !textbox->used ) + render_right_text_if_needed(fb, textbox->standin, make_color(160, 160, 160)); + else if ( textbox->password ) + { + int x = 0; + while ( x + (FONT_WIDTH+1) <= (int) fb.xres ) + { + render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), '*', + make_color(200, 200, 200)); + x += (FONT_WIDTH+1); + } + } + else + render_right_text_if_needed(fb, textbox->text, make_color(0, 0, 0)); +} + +static void render_form(struct framebuffer fb) +{ + int typearea_width = (FONT_WIDTH + 1) * 25; + int typearea_height = FONT_HEIGHT; + int textbox_margin = 6; + int textbox_width = typearea_width + 2 * textbox_margin; + int textbox_height = typearea_height + 2 * textbox_margin; + int form_margin = 10; + int form_width = textbox_width + 2 * form_margin; + int form_height = textbox_height + 2 * form_margin; + int BORDER_WIDTH = 8; + int TITLE_HEIGHT = 28; + int b0 = 0; + int b1 = 1; + int b2 = 2; + int b3 = BORDER_WIDTH; + int t0 = TITLE_HEIGHT; + int window_width = BORDER_WIDTH + form_width + BORDER_WIDTH; + int window_height = TITLE_HEIGHT + form_height + BORDER_WIDTH; + + if ( state.warning ) + { + struct framebuffer warnfb = fb; + int y = (fb.yres - 50 - window_height) / 2 - 2 * FONT_HEIGHT; + warnfb = framebuffer_cut_top_y(warnfb, y); + int w = strlen(state.warning) * (FONT_WIDTH+1); + warnfb = framebuffer_center_x(warnfb, fb.xres / 2, w); + render_text(warnfb, state.warning, make_color(255, 0, 0)); + } + + fb = framebuffer_center_x(fb, fb.xres / 2, window_width); + fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, window_height); + + uint32_t glass_color = make_color_a(200, 200, 255, 192); + uint32_t title_color = make_color_a(16, 16, 16, 240); + + for ( int y = 0; y < (int) fb.yres; y++ ) + { + for ( int x = 0; x < (int) fb.xres; x++ ) + { + uint32_t color; + if ( x == b0 || x == (int) fb.xres - (b0+1) || + y == b0 || y == (int) fb.yres - (b0+1) ) + color = make_color_a(0, 0, 0, 32); + else if ( x == b1 || x == (int) fb.xres - (b1+1) || + y == b1 || y == (int) fb.yres - (b1+1) ) + color = make_color_a(0, 0, 0, 64); + else if ( x == b2 || x == (int) fb.xres - (b2+1) || + y == b2 || y == (int) fb.yres - (b2+1) ) + color = make_color(240, 240, 250); + else if ( x < (b3-1) || x > (int) fb.xres - (b3+1-1) || + y < (t0-1) || y > (int) fb.yres - (b3+1-1) ) + color = glass_color; + else if ( x == (b3-1) || x == (int) fb.xres - (b3+1-1) || + y == (t0-1) || y == (int) fb.yres - (b3+1-1) ) + color = make_color(64, 64, 64); + else + continue; + uint32_t bg = framebuffer_get_pixel(fb, x, y); + framebuffer_set_pixel(fb, x, y, blend_pixel(bg, color)); + } + } + + fb = framebuffer_cut_left_x(fb, BORDER_WIDTH); + fb = framebuffer_cut_right_x(fb, BORDER_WIDTH); + + char hostname[HOST_NAME_MAX + 1]; + hostname[0] = '\0'; + gethostname(hostname, sizeof(hostname)); + const char* tt = hostname; + size_t tt_length = strlen(tt); + size_t tt_max_width = fb.xres; + size_t tt_desired_width = tt_length * (FONT_WIDTH+1); + size_t tt_width = tt_desired_width < tt_max_width ? tt_desired_width : tt_max_width; + size_t tt_height = FONT_HEIGHT; + size_t tt_pos_x = BORDER_WIDTH + (tt_max_width - tt_width) / 2; + size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2; + uint32_t tt_color = title_color; + render_text(framebuffer_crop(fb, tt_pos_x, tt_pos_y, tt_width, tt_height), tt, tt_color); + + fb = framebuffer_cut_top_y(fb, TITLE_HEIGHT); + fb = framebuffer_cut_bottom_y(fb, BORDER_WIDTH); + + for ( int y = 0; y < (int) fb.yres; y++ ) + { + for ( int x = 0; x < (int) fb.xres; x++ ) + { + framebuffer_set_pixel(fb, x, y, make_color(214, 214, 214)); + } + } + + struct framebuffer boxfb = fb; + boxfb = framebuffer_cut_left_x(boxfb, form_margin); + boxfb = framebuffer_cut_right_x(boxfb, form_margin); + boxfb = framebuffer_cut_top_y(boxfb, form_margin); + boxfb = framebuffer_cut_bottom_y(boxfb, form_margin); + switch ( state.stage ) + { + case STAGE_USERNAME: render_textbox(boxfb, &textbox_username); break; + case STAGE_PASSWORD: render_textbox(boxfb, &textbox_password); break; + default: break; + } +} + +static void render_progress(struct framebuffer fb) +{ + state.animating = true; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + float time = (float) now.tv_sec + (float) now.tv_nsec * 10E-9; + float rotslow_cos = cos(-time / 30.0f * M_PI * 2.0); + float rotslow_sin = sin(-time / 30.0f * M_PI * 2.0); + int size = 32; + int width = 4; + float widthf = ((float) width / (float) size) * 2.0f; + float innersq = (1.0f - widthf) * (1.0f - widthf); + float outersq = (1.0f) * (1.0f); + fb = framebuffer_center_x(fb, fb.xres / 2, size); + fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, size); + for ( size_t y = 0; y < fb.yres; y++ ) + { + float yfi = ((float) y / (float) size) * 2.0f - 1.0f; + for ( size_t x = 0; x < fb.xres; x++ ) + { + float xfi = ((float) x / (float) size) * 2.0f - 1.0f; + float distsq = xfi * xfi + yfi * yfi; + if ( distsq < innersq ) + continue; + if ( distsq > outersq ) + continue; + float af = fabs((distsq - innersq) / (outersq - innersq) * 2.0f - 1.0f); + af = 1.0 - af * af; + uint8_t a = (uint8_t) (af * 255.0f); + float xf = xfi; + float yf = yfi; + xf = rotslow_cos * xf + rotslow_sin * yf; + yf = -rotslow_sin * xf + rotslow_cos * yf; + if ( -widthf < yf && yf < widthf ) + continue; + uint8_t r = 0; + uint8_t g = 127.5 + 127.5 * xf; + uint8_t b = 255; + uint32_t bg = framebuffer_get_pixel(fb, x, y); + uint32_t fg = make_color_a(r, g, b, a); + framebuffer_set_pixel(fb, x, y, blend_pixel(bg, fg)); + } + } +} + +static void render_login(struct framebuffer fb) +{ + render_background(fb); + if ( false ) + render_information(fb); + switch ( state.stage ) + { + case STAGE_USERNAME: render_form(fb); break; + case STAGE_PASSWORD: render_form(fb); break; + case STAGE_CHECKING: render_progress(fb); break; + } + if ( state.pointer_working ) + render_pointer(fb); +} + +static void glogin_fade_from_end(struct glogin* state) +{ + state->fading_from = false; + free(state->fade_from_fb.buffer); +} + +static uint32_t* glogin_malloc_fb_buffer(struct glogin* state, + size_t size) +{ + if ( state->last_fb_buffer ) + { + if ( state->last_fb_buffer_size == size ) + { + uint32_t* result = state->last_fb_buffer; + state->last_fb_buffer = NULL; + return result; + } + free(state->last_fb_buffer); + state->last_fb_buffer = NULL; + } + uint32_t* result = (uint32_t*) malloc(size); + if ( !result ) + { + glogin_fade_from_end(state); + result = (uint32_t*) malloc(size); + } + return result; +} + +static void glogin_free_fb_buffer(struct glogin* state, + uint32_t* buffer, + size_t size) +{ + if ( state->last_fb_buffer ) + free(state->last_fb_buffer); + state->last_fb_buffer = buffer; + state->last_fb_buffer_size = size; +} + +static bool screen_capture(struct glogin* state, struct framebuffer* fb) +{ + fb->xres = state->mode.view_xres; + fb->yres = state->mode.view_yres; + fb->pitch = state->mode.view_xres; + size_t size = sizeof(uint32_t) * fb->xres * fb->yres; + fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size); + if ( !fb->buffer ) + return false; + struct dispmsg_write_memory msg; + memset(&msg, 0, sizeof(msg)); + msg.msgid = DISPMSG_READ_MEMORY; + msg.device = 0; // TODO: Multi-screen support! + msg.offset = 0; // TODO: mode.fb_location! + msg.size = fb->xres * fb->yres * sizeof(fb->buffer[0]); + msg.src = (uint8_t*) fb->buffer; + if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) + { + warn("dispmsg_issue: DISPMSG_READ_MEMORY"); + return false; + } + return true; +} + +static bool begin_render(struct glogin* state, struct framebuffer* fb) +{ + if ( !get_graphical_mode(&state->mode) ) + return false; + fb->xres = state->mode.view_xres; + fb->yres = state->mode.view_yres; + fb->pitch = state->mode.view_xres; + size_t size = sizeof(uint32_t) * fb->xres * fb->yres; + fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size); + if ( !fb->buffer ) + { + warn("malloc"); + return false; + } + return true; +} + +static bool finish_render(struct glogin* state, struct framebuffer* fb) +{ + struct dispmsg_write_memory msg; + memset(&msg, 0, sizeof(msg)); + msg.msgid = DISPMSG_WRITE_MEMORY; + msg.device = 0; // TODO: Multi-screen support! + msg.offset = 0; // TODO: mode.fb_location! + msg.size = sizeof(uint32_t) * fb->xres * fb->yres; + msg.src = (uint8_t*) fb->buffer; + if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) + { + warn("dispmsg_issue: DISPMSG_WRITE_MEMORY"); + free(fb->buffer); + return false; + } + glogin_free_fb_buffer(state, fb->buffer, msg.size); + return true; +} + +static bool render(struct glogin* state) +{ + state->animating = false; + struct framebuffer fb; + if ( !begin_render(state, &fb) ) + return false; + render_login(fb); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if ( state->fading_from && timespec_lt(now, state->fade_from_end) ) + { + struct timespec duration_ts = + timespec_sub(state->fade_from_end, state->fade_from_begin); + struct timespec elapsed_ts = timespec_sub(now, state->fade_from_begin); + float duration = (float) duration_ts.tv_sec + (float) duration_ts.tv_nsec * 10E-9; + float elapsed = (float) elapsed_ts.tv_sec + (float) elapsed_ts.tv_nsec * 10E-9; + float fade_from_alpha_f = 255.0 * elapsed / duration; + if ( fade_from_alpha_f < 0.0f ) fade_from_alpha_f = 0.0f; + if ( fade_from_alpha_f > 255.0f ) fade_from_alpha_f = 255.0f; + uint8_t fade_from_alpha = (uint8_t) fade_from_alpha_f; + uint32_t and_mask = ~make_color(0, 0, 0); + uint32_t or_mask = make_color_a(0, 0, 0, 255 - fade_from_alpha); + for ( int y = 0; y < (int) state->fade_from_fb.yres; y++ ) + { + for ( int x = 0; x < (int) state->fade_from_fb.xres; x++ ) + { + uint32_t color = framebuffer_get_pixel(state->fade_from_fb, x, y); + color = (color & and_mask) | or_mask; + framebuffer_set_pixel(state->fade_from_fb, x, y, color); + } + } + framebuffer_copy_to_framebuffer_blend(fb, state->fade_from_fb); + state->animating = true; + } + + else if ( state->fading_from ) + glogin_fade_from_end(state); + if ( !finish_render(state, &fb) ) + return false; + return true; +} + +static void think(struct glogin* state) +{ + if ( state->stage == STAGE_CHECKING ) + { + bool result; + if ( !check_end(&state->chk, &result, true) ) + { + sched_yield(); + return; + } + if ( result ) + { + if ( !login(textbox_username.text) ) + state->warning = strerror(errno); + state->stage = STAGE_USERNAME; + textbox_reset(&textbox_username); + } + else + { + state->stage = STAGE_USERNAME; + textbox_reset(&textbox_username); + if ( errno == EACCES ) + state->warning = "Invalid username/password"; + else + state->warning = strerror(errno); + } + } +} + +static void keyboard_event(struct glogin* state, uint32_t codepoint) +{ + if ( codepoint == '\n' ) + { + state->warning = NULL; + switch ( state->stage ) + { + case STAGE_USERNAME: + if ( !strcmp(textbox_username.text, "exit") ) + exit(0); + if ( !strcmp(textbox_username.text, "poweroff") ) + exit(0); + if ( !strcmp(textbox_username.text, "reboot") ) + exit(1); + state->stage = STAGE_PASSWORD; + textbox_reset(&textbox_password); + break; + case STAGE_PASSWORD: + state->stage = STAGE_CHECKING; + if ( !check_begin(&state->chk, textbox_username.text, + textbox_password.text, true) ) + { + state->stage = STAGE_USERNAME; + state->warning = strerror(errno); + } + break; + case STAGE_CHECKING: + break; + } + return; + } + struct textbox* textbox = NULL; + switch ( state->stage ) + { + case STAGE_USERNAME: textbox = &textbox_username; break; + case STAGE_PASSWORD: textbox = &textbox_password; break; + case STAGE_CHECKING: break; + } + if ( textbox && codepoint < 128 ) + { + if ( codepoint == '\b' || codepoint == 127 ) + textbox_type_backspace(textbox); + else + textbox_type_char(textbox, (char) codepoint); + } +} + +static void mouse_event(struct glogin* state, unsigned char byte) +{ + state->pointer_working = true; + if ( state->mouse_byte_count == 0 && !(byte & 1 << 3) ) + return; + if ( state->mouse_byte_count < 3 ) + state->mouse_bytes[state->mouse_byte_count++] = byte; + if ( state->mouse_byte_count < 3 ) + return; + state->mouse_byte_count = 0; + unsigned char* bytes = state->mouse_bytes; + int xm = bytes[1]; + int ym = bytes[2]; + if ( xm && bytes[0] & (1 << 4) ) + xm = xm - 256; + if ( ym && bytes[0] & (1 << 5) ) + ym = ym - 256; + if ( (bytes[0] & (1 << 6)) || (bytes[0] & (1 << 7)) ) + { + xm = 0; + ym = 0; + } + ym = -ym; + int old_pointer_x = state->pointer_x; + int old_pointer_y = state->pointer_y; + if ( xm*xm + ym*ym >= 2*2 ) + { + xm *= 2; + ym *= 2; + } + else if ( xm*xm + ym*ym >= 5*5 ) + { + xm *= 3; + ym *= 3; + } + state->pointer_x += xm; + state->pointer_y += ym; + if ( state->pointer_x < 0 ) + state->pointer_x = 0; + if ( state->pointer_y < 0 ) + state->pointer_y = 0; + if ( state->mode.view_xres <= (size_t) state->pointer_x ) + state->pointer_x = state->mode.view_xres; + if ( state->mode.view_yres <= (size_t) state->pointer_y ) + state->pointer_y = state->mode.view_yres; + xm = state->pointer_x - old_pointer_x; + ym = state->pointer_y - old_pointer_y; + if ( (bytes[0] & 1 << 0) ) + { + (void) xm; + (void) ym; + } +} + +void glogin_destroy(struct glogin* state) +{ + if ( 0 <= state->fd_mouse ) + close(state->fd_mouse); + if ( state->fading_from ) + free(state->fade_from_fb.buffer); +} + +bool glogin_init(struct glogin* state) +{ + memset(state, 0, sizeof(*state)); + state->fd_mouse = -1; + + if ( !get_graphical_mode(&state->mode) ) + { + warn("dispmsg_issue"); + glogin_destroy(state); + return false; + } + if ( !is_graphical_mode(&state->mode) || + state->mode.view_xres < 128 || + state->mode.view_yres < 128 ) + { + glogin_destroy(state); + return false; + } + if ( !load_font() ) + { + warn("/dev/vgafont"); + glogin_destroy(state); + return false; + } + state->fd_mouse = open("/dev/mouse", O_RDONLY | O_CLOEXEC); + if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK) < 0 ) + { + warn("settermmode"); + return false; + } + fsync(0); + arrow_initialize(); + textbox_initialize(&textbox_username, "Username"); + textbox_initialize(&textbox_password, "Password"); + textbox_password.password = true; + state->pointer_x = state->mode.view_xres / 2; + state->pointer_y = state->mode.view_yres / 2; + if ( screen_capture(state, &state->fade_from_fb) ) + { + state->fading_from = true; + clock_gettime(CLOCK_MONOTONIC, &state->fade_from_begin); + struct timespec duration = timespec_make(0, 150*1000*1000); + state->fade_from_end = timespec_add(state->fade_from_begin, duration); + } + return true; +} + +int glogin_main(struct glogin* state) +{ + while ( true ) + { + think(state); + if ( !render(state) ) + break; + struct pollfd pfds[2]; + memset(pfds, 0, sizeof(pfds)); + pfds[0].fd = -1; + pfds[1].fd = -1; + if ( state->stage != STAGE_CHECKING ) + { + pfds[0].fd = 0; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + } + if ( 0 <= state->fd_mouse ) + { + pfds[1].fd = state->fd_mouse; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + } + nfds_t nfds = 2; + struct timespec wake_now_ts = timespec_make(0, 0); + struct timespec* wake = state->animating ? &wake_now_ts : NULL; + int num_events = ppoll(pfds, nfds, wake, NULL); + if ( num_events < 0 ) + { + warn("poll"); + break; + } + for ( nfds_t i = 0; i < nfds; i++ ) + { + if ( pfds[i].fd == -1 ) + continue; + if ( (pfds[i].revents & POLLERR) || + (pfds[i].revents & POLLHUP) || + (pfds[i].revents & POLLNVAL) ) + { + warnx("poll failure on %s", i == 0 ? "keyboard" : "mouse"); + break; + } + } + if ( pfds[0].fd != -1 && pfds[0].revents ) + { + uint32_t codepoint; + while ( read(0, &codepoint, sizeof(codepoint)) == sizeof(codepoint) ) + keyboard_event(state, codepoint); + } + if ( pfds[1].fd != -1 && pfds[1].revents ) + { + unsigned char events[64]; + ssize_t amount = read(state->fd_mouse, events, sizeof(events)); + for ( ssize_t i = 0; i < amount; i++ ) + mouse_event(state, events[i]); + } + } + return -1; +} + +int graphical(void) +{ + if ( access("/etc/login.conf.textual", F_OK) == 0 ) + return -1; + if ( !glogin_init(&state) ) + return -1; + int result = glogin_main(&state); + glogin_destroy(&state); + return result; +} diff --git a/login/login.8 b/login/login.8 new file mode 100644 index 00000000..ab3545a1 --- /dev/null +++ b/login/login.8 @@ -0,0 +1,74 @@ +.Dd $Mdocdate: October 6 2015 $ +.Dt LOGIN 8 +.Os +.Sh NAME +.Nm login +.Nd authenticate users and run personal session +.Sh SYNOPSIS +.Nm login +.Sh DESCRIPTION +.Nm login +interactively authenticates users by asking them to enter their username and +password. The passwords are checked against the password hashes in +.Pa /etc/passwd +as described in +.Xr passwd 5 . +.Nm login +creates a session as the requested user upon successful authentication. +.Pp +.Nm login +has a graphical interface if the display is graphical and uses a textual +interface otherwise. The textual interface is forced if +.Pa /etc/login.conf.textual +exists. The process remains running in the background and takes +over again when the user session exits. +.Pp +Type a special username to perform special options: +.Pp +.Bl -tag -width "poweroff" -compact -offset indent +.It exit +alias for poweroff +.It poweroff +exit asking for powering off the computer +.It reboot +exit asking for rebooting the computer +.El +.Sh SECURITY +There is currently no method to confirm the login screen is in fact real other +than witnessing a pristine boot. Local users can log in and show a counterfeit +login screen that look and behave like the real +.Nm login +program and trick the next user into revealing their password. +.Sh ENVIRONMENT +.Nm login +sets the following environment variables to match the authenticated user: +.Bl -tag -width "LOGNAME" +.It Ev HOME +home directory +.It Ev LOGNAME +username +.It Ev SHELL +shell +.It Ev USER +username +.El +.Sh FILES +.Bl -tag -width "/etc/passwd" -compact +.It Pa /etc/passwd +user database (see +.Xr passwd 5 ) +.It Pa /etc/login.conf.textual +textual interface is forced if this file exists +.El +.Sh EXIT STATUS +.Nm login +exits 0 if the computer should power off, exits 1 if the computer should +reboot, or exits 2 on fatal failure and the boot should halt. +.Sh SEE ALSO +.Xr crypt_checkpass 3 , +.Xr passwd 5 , +.Xr init 8 , +.Xr login 8 +.Sh BUGS +.Nm login +only supports a single monitor. The mouse code is less than perfect. diff --git a/login/login.c b/login/login.c new file mode 100644 index 00000000..375d2eb1 --- /dev/null +++ b/login/login.c @@ -0,0 +1,438 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + login.c + Authenticates users. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: The Sortix doesn't expose this at the moment. +#if !defined(HOST_NAME_MAX) && defined(__sortix__) +#include +#endif + +#include "login.h" + +static void on_interrupt_signal(int signum) +{ + if ( signum == SIGINT ) + dprintf(1, "^C"); + if ( signum == SIGQUIT ) + dprintf(1, "^\\"); +} + +bool check_real(const char* username, const char* password) +{ + char fakehashbuf[128]; + char goodhashbuf[128]; + size_t fakematch = 0; + size_t goodmatch = 0; + const char* fakehash = NULL; + const char* goodhash = NULL; + setpwent(); + struct passwd* pwd; + while ( (errno = 0, pwd = getpwent()) ) + { + if ( !strcmp(username, pwd->pw_name) ) + { + strlcpy(goodhashbuf, pwd->pw_passwd, sizeof(goodhashbuf)); + goodhash = goodhashbuf; + goodmatch++; + } + else + { + strlcpy(fakehashbuf, pwd->pw_passwd, sizeof(fakehashbuf)); + fakehash = fakehashbuf; + fakematch++; + } + } + int errnum = errno; + endpwent(); + if ( errnum != 0 ) + return errno = errnum, false; + if ( 1 < goodmatch ) + return errno = EACCES, false; + errno = 0; + (void) fakehash; + return crypt_checkpass(password, goodhash) == 0; +} + +bool check_begin(struct check* chk, + const char* username, + const char* password, + bool restrict_termmode) +{ + memset(chk, 0, sizeof(*chk)); + if ( tcgetattr(0, &chk->tio) ) + return false; + int pipe_fds[2]; + if ( pipe2(pipe_fds, O_CLOEXEC) < 0 ) + return false; + sigset_t sigttou; + sigemptyset(&sigttou); + sigaddset(&sigttou, SIGTTOU); + sigprocmask(SIG_BLOCK, &sigttou, &chk->oldset); + if ( (chk->pid = fork()) < 0 ) + return close(pipe_fds[0]), close(pipe_fds[1]), false; + int success = -2; + if ( chk->pid == 0 ) + { + sigdelset(&chk->oldset, SIGINT); + sigdelset(&chk->oldset, SIGQUIT); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL | + TERMMODE_UTF8 | TERMMODE_LINEBUFFER | + TERMMODE_ECHO; + if ( restrict_termmode ) + termmode = TERMMODE_SIGNAL; + if ( setpgid(0, 0) < 0 || + close(pipe_fds[0]) < 0 || + tcsetpgrp(0, getpgid(0)) || + sigprocmask(SIG_SETMASK, &chk->oldset, NULL) < 0 || + settermmode(0, termmode) < 0 || + !check_real(username, password) || + write(pipe_fds[1], &success, sizeof(success)) < 0 ) + { + assert(1 <= errno); + write(pipe_fds[1], &errno, sizeof(errno)); + } + _exit(0); + } + close(pipe_fds[1]); + chk->pipe = pipe_fds[0]; + return true; +} + +bool check_end(struct check* chk, bool* result, bool try) +{ + if ( try && !chk->pipe_nonblock ) + { + fcntl(chk->pipe, F_SETFL, fcntl(chk->pipe, F_GETFL) | O_NONBLOCK); + chk->pipe_nonblock = true; + } + while ( chk->errnum_done < sizeof(chk->errnum_bytes) ) + { + ssize_t amount = read(chk->pipe, chk->errnum_bytes + chk->errnum_done, + sizeof(chk->errnum_bytes) - chk->errnum_done); + if ( amount <= 0 ) + { + if ( amount == 0 ) + errno = EOF; + break; + } + chk->errnum_done += amount; + } + int code; + pid_t wait_ret = waitpid(chk->pid, &code, try ? WNOHANG : 0); + if ( try && wait_ret == 0 ) + return false; + tcsetattr(0, TCSAFLUSH, &chk->tio); + tcsetpgrp(0, getpgid(0)); + sigprocmask(SIG_SETMASK, &chk->oldset, NULL); + if ( wait_ret < 0 ) + return *result = false, true; + if ( chk->errnum_done < sizeof(chk->errnum_bytes) ) + chk->errnum = EEOF; + if ( WIFSIGNALED(code) ) + chk->errnum = EINTR; + else if ( !(WIFEXITED(code) && WEXITSTATUS(code) == 0) ) + chk->errnum = EINVAL; + int success = -2; + if ( chk->errnum < 1 && chk->errnum != success ) + chk->errnum = EINVAL; + if ( chk->errnum != success ) + return errno = chk->errnum, *result = false, true; + return *result = true, true; +} + +bool check(const char* username, const char* password) +{ + struct check chk; + if ( !check_begin(&chk, username, password, false) ) + return false; + bool result; + check_end(&chk, &result, false); + return result; +} + +static int setcloexecfrom(int from) +{ + int fd = from - 1; + while ( (fd = fcntl(fd, F_NEXTFD)) != -1 ) + { + int flags = fcntl(fd, F_GETFD); + if ( flags < 0 ) + return -1; + if ( !(flags & FD_CLOEXEC) && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0 ) + return -1; + } + return 0; +} + +bool login(const char* username) +{ + char login_pid[sizeof(pid_t) * 3]; + snprintf(login_pid, sizeof(login_pid), "%" PRIiPID, getpid()); + struct passwd* pwd = getpwnam(username); + if ( !pwd ) + return false; + struct termios tio; + if ( tcgetattr(0, &tio) ) + return false; + int pipe_fds[2]; + if ( pipe2(pipe_fds, O_CLOEXEC) < 0 ) + return false; + sigset_t oldset, sigttou; + sigemptyset(&sigttou); + sigaddset(&sigttou, SIGTTOU); + sigprocmask(SIG_BLOCK, &sigttou, &oldset); + pid_t child_pid = fork(); + if ( child_pid < 0 ) + return close(pipe_fds[0]), close(pipe_fds[1]), false; + if ( child_pid == 0 ) + { + sigdelset(&oldset, SIGINT); + sigdelset(&oldset, SIGQUIT); + sigdelset(&oldset, SIGTSTP); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + (void) ( + setpgid(0, 0) < 0 || + close(pipe_fds[0]) < 0 || + setgid(pwd->pw_gid) < 0 || + setuid(pwd->pw_uid) < 0 || + setenv("LOGIN_PID", login_pid, 1) < 0 || + setenv("LOGNAME", pwd->pw_name, 1) < 0 || + setenv("USER", pwd->pw_name, 1) < 0 || + chdir(pwd->pw_dir) < 0 || + setenv("HOME", pwd->pw_dir, 1) < 0 || + setenv("SHELL", pwd->pw_shell, 1) < 0 || + close(0) < 0 || + close(1) < 0 || + close(2) < 0 || + open("/dev/tty", O_RDONLY) != 0 || + open("/dev/tty", O_WRONLY) != 1 || + open("/dev/tty", O_WRONLY) != 2 || + setcloexecfrom(3) < 0 || + tcsetpgrp(0, getpgid(0)) || + sigprocmask(SIG_SETMASK, &oldset, NULL) < 0 || + settermmode(0, TERMMODE_NORMAL) < 0 || + execlp(pwd->pw_shell, pwd->pw_shell, (const char*) NULL)); + write(pipe_fds[1], &errno, sizeof(errno)); + _exit(127); + } + close(pipe_fds[1]); + int errnum; + if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) ) + errnum = 0; + close(pipe_fds[0]); + int child_status; + if ( waitpid(child_pid, &child_status, 0) < 0 ) + errnum = errno; + tcsetattr(0, TCSAFLUSH, &tio); + tcsetpgrp(0, getpgid(0)); + sigprocmask(SIG_SETMASK, &oldset, NULL); + dprintf(1, "\e[H\e[2J"); + fsync(1); + if ( errnum != 0 ) + return errno = errnum, false; + return true; +} + +static bool read_terminal_line(char* buffer, size_t size) +{ + assert(size); + size--; + sigset_t intset; + sigemptyset(&intset); + sigaddset(&intset, SIGINT); + sigaddset(&intset, SIGQUIT); + bool newline = false; + size_t sofar = 0; + while ( !newline && sofar < size ) + { + sigset_t oldset; + sigprocmask(SIG_UNBLOCK, &intset, &oldset); + ssize_t amount = read(0, buffer + sofar, size - sofar); + sigprocmask(SIG_SETMASK, &oldset, NULL); + if ( amount <= 0 ) + return false; + for ( ssize_t i = 0; i < amount; i++ ) + { + if ( buffer[sofar + i] != '\n' ) + continue; + newline = true; + amount = i; + break; + } + sofar += amount; + } + while ( !newline ) + { + char c; + if ( read(0, &c, 1) <= 0 ) + return false; + newline = c == '\n'; + } + buffer[sofar] = '\0'; + return true; +} + +int textual(void) +{ + unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 | + TERMMODE_LINEBUFFER | TERMMODE_ECHO; + if ( settermmode(0, termmode) < 0 ) + err(2, "settermmode"); + unsigned int pw_termmode = termmode & ~(TERMMODE_ECHO); + + while ( true ) + { + char hostname[HOST_NAME_MAX + 1]; + hostname[0] = '\0'; + gethostname(hostname, sizeof(hostname)); + printf("%s login: ", hostname); + fflush(stdout); + char username[256]; + errno = 0; + if ( !read_terminal_line(username, sizeof(username)) ) + { + printf("\n"); + if ( errno && errno != EINTR ) + { + warn("fgets"); + sleep(1); + } + printf("\n"); + continue; + } + + if ( !strcmp(username, "exit") ) + exit(0); + if ( !strcmp(username, "poweroff") ) + exit(0); + if ( !strcmp(username, "reboot") ) + exit(1); + + if ( settermmode(0, pw_termmode) < 0 ) + err(2, "settermmode"); + printf("Password (will not echo): "); + fflush(stdout); + char password[256]; + errno = 0; + bool password_success = read_terminal_line(password, sizeof(password)); + printf("\n"); + if ( settermmode(0, termmode) < 0 ) + err(2, "settermmode"); + if ( !password_success ) + { + if ( errno && errno != EINTR ) + { + warn("fgets"); + sleep(1); + } + printf("\n"); + continue; + } + + bool result = check(username, password); + explicit_bzero(password, sizeof(password)); + if ( !result ) + { + const char* msg = "Invalid username/password"; + if ( errno != EACCES ) + msg = strerror(errno); + printf("%s\n", msg); + printf("\n"); + continue; + } + + if ( !login(username) ) + { + warn("logging in as %s", username); + printf("\n"); + continue; + } + } + + return 0; +} + +int main(void) +{ + setlocale(LC_ALL, ""); + if ( getuid() != 0 ) + errx(2, "must be user root"); + if ( getgid() != 0 ) + errx(2, "must be group root"); + if ( !isatty(0) ) + { + close(0); + if ( open("/dev/tty", O_RDONLY) != 0 ) + err(2, "/dev/tty"); + } + if ( !isatty(1) ) + { + close(1); + if ( open("/dev/tty", O_WRONLY) != 0 ) + err(2, "/dev/tty"); + } + if ( !isatty(2) ) + { + if ( dup2(1, 2) < 0 ) + err(2, "dup2"); + } + if ( tcgetpgrp(0) != getpgid(0) ) + errx(2, "must be in foreground process group"); + if ( getpgid(0) != getpid() ) + errx(2, "must be progress group leader"); + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = on_interrupt_signal; + sigaddset(&sa.sa_mask, SIGINT); + sigaddset(&sa.sa_mask, SIGQUIT); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaddset(&sa.sa_mask, SIGTSTP); + sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL); + int result = -1; + if ( result == -1 ) + result = graphical(); + if ( result == -1 ) + result = textual(); + return result; +} diff --git a/login/login.h b/login/login.h new file mode 100644 index 00000000..a745a4af --- /dev/null +++ b/login/login.h @@ -0,0 +1,52 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + login.h + Authenticates users. + +*******************************************************************************/ + +#ifndef LOGIN_H +#define LOGIN_H + +struct check +{ + sigset_t oldset; + struct termios tio; + pid_t pid; + int pipe; + union + { + int errnum; + unsigned char errnum_bytes[sizeof(int)]; + }; + unsigned int errnum_done; + bool pipe_nonblock; +}; + +bool login(const char* username); +bool check_real(const char* username, const char* password); +bool check_begin(struct check* chk, + const char* username, + const char* password, + bool restrict_termmode); +bool check_end(struct check* chk, bool* result, bool try); +bool check(const char* username, const char* password); +int graphical(void); +int textual(void); + +#endif diff --git a/login/pixel.c b/login/pixel.c new file mode 100644 index 00000000..8bc838e0 --- /dev/null +++ b/login/pixel.c @@ -0,0 +1,41 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + pixel.c + Pixel utilities. + +*******************************************************************************/ + +#include + +#include "pixel.h" + +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value) +{ + union color_rgba8 fg; fg.value = fg_value; + union color_rgba8 bg; bg.value = bg_value; + if ( fg.a == 255 ) + return fg.value; + if ( fg.a == 0 ) + return bg.value; + union color_rgba8 ret; + ret.a = 255; + ret.r = ((255-fg.a)*bg.r + fg.a*fg.r) / 256; + ret.g = ((255-fg.a)*bg.g + fg.a*fg.g) / 256; + ret.b = ((255-fg.a)*bg.b + fg.a*fg.b) / 256; + return ret.value; +} diff --git a/login/pixel.h b/login/pixel.h new file mode 100644 index 00000000..a963b316 --- /dev/null +++ b/login/pixel.h @@ -0,0 +1,60 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + pixel.h + Pixel utilities. + +*******************************************************************************/ + +#ifndef PIXEL_H +#define PIXEL_H + +#include + +// TODO: This isn't the only pixel format in the world! +union color_rgba8 +{ + struct + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + uint32_t value; +}; + +__attribute__((used)) +static inline uint32_t make_color_a(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + union color_rgba8 color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color.value; +} + +__attribute__((used)) +static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) +{ + return make_color_a(r, g, b, 255); +} + +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value); + +#endif diff --git a/login/vgafont.c b/login/vgafont.c new file mode 100644 index 00000000..300e5ca4 --- /dev/null +++ b/login/vgafont.c @@ -0,0 +1,75 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + vgafont.c + VGA font. + +*******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "framebuffer.h" +#include "vgafont.h" + +unsigned char font[FONT_CHARSIZE * FONT_NUMCHARS]; + +bool load_font() +{ + static bool done = false; + if ( done ) + return true; + int fd = open("/dev/vgafont", O_RDONLY); + if ( fd < 0 ) + return false; + if ( readall(fd, font, sizeof(font)) != sizeof(font) ) + return false; + close(fd); + return done = true; +} + +void render_char(struct framebuffer fb, char c, uint32_t color) +{ + unsigned char uc = (unsigned char) c; + + uint32_t buffer[FONT_HEIGHT * (FONT_WIDTH+1)]; + for ( size_t y = 0; y < FONT_HEIGHT; y++ ) + { + unsigned char line_bitmap = font[uc * FONT_CHARSIZE + y]; + for ( size_t x = 0; x < FONT_WIDTH; x++ ) + buffer[y * (FONT_WIDTH+1) + x] = line_bitmap & 1U << (7 - x) ? color : 0; + buffer[y * (FONT_WIDTH+1) + 8] = 0; //line_bitmap & 1U << 0 ? color : 0; + } + + struct framebuffer character_fb; + character_fb.xres = FONT_WIDTH + 1; + character_fb.yres = FONT_HEIGHT; + character_fb.pitch = character_fb.xres; + character_fb.buffer = buffer; + + framebuffer_copy_to_framebuffer_blend(fb, character_fb); +} + +void render_text(struct framebuffer fb, const char* str, uint32_t color) +{ + for ( size_t i = 0; str[i]; i++ ) + render_char(framebuffer_crop(fb, (FONT_WIDTH+1) * i, 0, fb.xres, fb.yres), + str[i], color); +} diff --git a/login/vgafont.h b/login/vgafont.h new file mode 100644 index 00000000..2acb7bff --- /dev/null +++ b/login/vgafont.h @@ -0,0 +1,41 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2014, 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + vgafont.h + VGA font. + +*******************************************************************************/ + +#ifndef VGAFONT_H +#define VGAFONT_H + +#include + +#include "framebuffer.h" + +#define FONT_WIDTH 8 +#define FONT_HEIGHT 16 +#define FONT_NUMCHARS 256 +#define FONT_CHARSIZE ((FONT_WIDTH * FONT_HEIGHT) / 8) + +extern uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +bool load_font(); +void render_char(struct framebuffer fb, char c, uint32_t color); +void render_text(struct framebuffer fb, const char* str, uint32_t color); + +#endif diff --git a/share/man/man7/user-guide.7 b/share/man/man7/user-guide.7 index de129f74..4215e8d7 100644 --- a/share/man/man7/user-guide.7 +++ b/share/man/man7/user-guide.7 @@ -13,9 +13,16 @@ You will be presented a with standard Unix command line environment upon booting the live environment. .Ss Shutdown .Xr init 8 -spawns a session after boot. This is a root shell if booted in +spawns a session after boot. This is +.Xr login 8 +if the system is booted in multi-user mode. This is a root shell if booted in single-user mode. .Pp +To power off from the login screen, login as user +.Sy poweroff . +To reboot, login as user +.Sy reboot . +.Pp To power off from a single-user boot root shell, run .Sy exit 0 in the shell. To reboot, run diff --git a/trianglix/trianglix.cpp b/trianglix/trianglix.cpp index 14bbdcbd..ec7d97d8 100644 --- a/trianglix/trianglix.cpp +++ b/trianglix/trianglix.cpp @@ -780,7 +780,9 @@ class action** administration::list_actions(size_t* num_actions) { class action** actions = new action*[4 + 1]; size_t index = 0; +#if 0 // TODO: Until crypt_newhash is used for the password. actions[index++] = new action("Create user", new create_user()); +#endif actions[index++] = new action("Enable Runes", new decide_runes(true)); actions[index++] = new action("Disable Runes", new decide_runes(false)); actions[index++] = new action("Trinit Core", new core()); @@ -788,6 +790,23 @@ class action** administration::list_actions(size_t* num_actions) return *num_actions = index, actions; } +class exiter : public object +{ +public: + exiter() { } + virtual ~exiter() { } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +}; + +void exiter::invoke() +{ + exit(0); +} + class desktop : public object { public: @@ -811,7 +830,7 @@ class action** desktop::list_actions(size_t* num_actions) actions[3] = new action("Shell", new path_program("sh")); actions[4] = new action("Development", new development()); actions[5] = new action("Administration", new administration()); - actions[6] = new action("Logout", parent_object); + actions[6] = new action("Logout", new exiter()); return actions; } @@ -830,109 +849,6 @@ class object* log_user_in(struct passwd* user) return new desktop(); } -class poweroff : public object -{ -public: - poweroff() { } - virtual ~poweroff() { } - -public: - virtual enum object_type type() { return TYPE_FILE; } - virtual void invoke(); - -}; - -void poweroff::invoke() -{ - exit(0); -} - -class login : public object -{ -public: - login(const char* username) : username(strdup(username)) { } - virtual ~login() { } - -public: - virtual enum object_type type() { return TYPE_DIRECTORY; } - virtual class object* factory(); - virtual const char* title() { return "Authentication required "; } - virtual const char* prompt() { return "Enter Password:"; } - virtual bool is_password_prompt() { return true; } - virtual class action** list_actions(size_t* num_actions); - virtual class object* command_line(const char* command); - -private: - char* username; - -}; - -class user_selection : public object -{ -public: - user_selection() { } - virtual ~user_selection() { } - -public: - virtual enum object_type type() { return TYPE_DIRECTORY; } - virtual const char* title() { return "User Selection"; } - virtual class action** list_actions(size_t* num_actions); - virtual class object* command_line(const char* command); - -}; - -class object* login::factory() -{ - if ( struct passwd* user = getpwnam(username) ) - if ( !user->pw_passwd[0] ) - return log_user_in(user); - return NULL; -} - -class action** login::list_actions(size_t* num_actions) -{ - return *num_actions = 0, new class action*[0]; -} - -class object* login::command_line(const char* password) -{ - error_string = ""; - if ( struct passwd* user = getpwnam(username) ) - { - if ( !strcmp(user->pw_passwd, password) ) - return log_user_in(user); - else - return error_string = "Invalid password", (class object*) NULL; - } - return error_string = "No such user", (class object*) NULL; -} - -class action** user_selection::list_actions(size_t* num_actions) -{ - size_t num_users = 0; - FILE* fp = openpw(); - while ( fgetpwent(fp) ) - num_users++; - fseeko(fp, 0, SEEK_SET); - action** actions = new class action*[num_users + 1]; - size_t which_user = 0; - while ( struct passwd* user = fgetpwent(fp) ) - actions[which_user++] = - new action(user->pw_gecos ? user->pw_gecos : user->pw_name, - new login(user->pw_name)); - fclose(fp); - actions[num_users] = new action("Poweroff", new poweroff); - return *num_actions = num_users + 1, actions; -} - -class object* user_selection::command_line(const char* command) -{ - error_string = ""; - if ( getpwnam(command) ) - return new login(command); - return error_string = "No such user", (class object*) NULL; -} - class FrameBufferInfo; struct Desktop; struct RenderInfo; @@ -1764,7 +1680,7 @@ static void InitializeDesktop(struct Desktop* desktop) desktop->rshift = false; desktop->actions = NULL; desktop->num_actions = 0; - desktop->object = new user_selection(); + desktop->object = new class desktop(); UpdateActionList(desktop); } diff --git a/utils/command-not-found.cpp b/utils/command-not-found.cpp index e5b96ffc..d81f114e 100644 --- a/utils/command-not-found.cpp +++ b/utils/command-not-found.cpp @@ -23,6 +23,7 @@ *******************************************************************************/ #include +#include #include void suggest_editor(const char* filename) @@ -49,6 +50,40 @@ void suggest_unmount(const char* filename) fprintf(stderr, " Command 'unmount' from package 'utils'\n"); } +void suggest_logout(const char* filename) +{ + fprintf(stderr, "No command '%s' found, did you mean:\n", filename); + fprintf(stderr, " Exiting your shell normally to logout.\n"); +} + +void suggest_poweroff(const char* filename) +{ + fprintf(stderr, "No command '%s' found, did you mean:\n", filename); + if ( getenv("LOGIN_PID") ) + { + fprintf(stderr, " Exiting your shell normally to logout.\n"); + fprintf(stderr, " Login as user 'poweroff' to power off computer.\n"); + } + else + { + fprintf(stderr, " Exiting your shell normally to poweroff.\n"); + } +} + +void suggest_reboot(const char* filename) +{ + fprintf(stderr, "No command '%s' found, did you mean:\n", filename); + if ( getenv("LOGIN_PID") ) + { + fprintf(stderr, " Exiting your shell normally to logout.\n"); + fprintf(stderr, " Login as user 'reboot' to reboot computer.\n"); + } + else + { + fprintf(stderr, " Exiting your shell with 'exit 1' to reboot.\n"); + } +} + int main(int argc, char* argv[]) { const char* filename = 2 <= argc ? argv[1] : argv[0]; @@ -65,6 +100,15 @@ int main(int argc, char* argv[]) suggest_extfs(filename); else if ( !strcmp(filename, "umount") ) suggest_unmount(filename); + else if ( !strcmp(filename, "logout") || + !strcmp(filename, "logoff") ) + suggest_logout(filename); + else if ( !strcmp(filename, "poweroff") || + !strcmp(filename, "halt") || + !strcmp(filename, "shutdown") ) + suggest_poweroff(filename); + else if ( !strcmp(filename, "reboot") ) + suggest_reboot(filename); fprintf(stderr, "%s: command not found\n", filename); return 127; }