Add login(8).

This commit is contained in:
Jonas 'Sortie' Termansen 2015-12-25 03:24:51 +01:00
parent 02d5dddc5b
commit 9a1786f688
17 changed files with 2003 additions and 106 deletions

View File

@ -18,6 +18,7 @@ games \
init \
kblayout \
kblayout-compiler \
login \
mbr \
mkinitrd \
regress \

3
login/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
login
*.o
arrow.inc

41
login/Makefile Normal file
View File

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

BIN
login/arrow.rgb Normal file

Binary file not shown.

176
login/framebuffer.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
framebuffer.c
Framebuffer utilities.
*******************************************************************************/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#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);
}

83
login/framebuffer.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
framebuffer.h
Framebuffer utilities.
*******************************************************************************/
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
#include <stddef.h>
#include <stdint.h>
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

845
login/graphical.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
graphical.c
Graphical login.
*******************************************************************************/
#include <sys/display.h>
#include <sys/display.h>
#include <sys/kernelinfo.h>
#include <sys/termmode.h>
#include <assert.h>
#include <brand.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <sched.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <timespec.h>
#include <unistd.h>
// TODO: The Sortix <limits.h> doesn't expose this at the moment.
#if !defined(HOST_NAME_MAX) && defined(__sortix__)
#include <sortix/limits.h>
#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;
}

74
login/login.8 Normal file
View File

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

438
login/login.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
login.c
Authenticates users.
*******************************************************************************/
#include <sys/termmode.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ioleast.h>
#include <limits.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
// TODO: The Sortix <limits.h> doesn't expose this at the moment.
#if !defined(HOST_NAME_MAX) && defined(__sortix__)
#include <sortix/limits.h>
#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;
}

52
login/login.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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

41
login/pixel.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
pixel.c
Pixel utilities.
*******************************************************************************/
#include <stdint.h>
#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;
}

60
login/pixel.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
pixel.h
Pixel utilities.
*******************************************************************************/
#ifndef PIXEL_H
#define PIXEL_H
#include <stdint.h>
// 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

75
login/vgafont.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
vgafont.c
VGA font.
*******************************************************************************/
#include <fcntl.h>
#include <ioleast.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#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);
}

41
login/vgafont.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
vgafont.h
VGA font.
*******************************************************************************/
#ifndef VGAFONT_H
#define VGAFONT_H
#include <stdint.h>
#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

View File

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

View File

@ -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);
}

View File

@ -23,6 +23,7 @@
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}