diff --git a/Makefile b/Makefile index 5622ef9..254e418 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,9 @@ initrd: --target=../etc/$(ARCH)-osdev5.json \ -Z build-std=core,alloc,compiler_builtins \ $(CARGO_COMMON_OPTS) - mkdir -p $(O)/rootfs/bin $(O)/rootfs/sbin $(O)/rootfs/dev + mkdir -p $(O)/rootfs/bin $(O)/rootfs/sbin $(O)/rootfs/dev $(O)/rootfs/etc + cp etc/initrd/passwd $(O)/rootfs/etc + cp etc/initrd/shadow $(O)/rootfs/etc touch $(O)/rootfs/dev/.do_no_remove cp target/$(ARCH)-osdev5/$(PROFILE)/init $(O)/rootfs/init cp target/$(ARCH)-osdev5/$(PROFILE)/shell $(O)/rootfs/bin diff --git a/etc/initrd/passwd b/etc/initrd/passwd new file mode 100644 index 0000000..c672cc4 --- /dev/null +++ b/etc/initrd/passwd @@ -0,0 +1,2 @@ +root:0:0:root:/:/bin/shell +alnyan:1000:1000:alnyan:/:/bin/shell diff --git a/etc/initrd/shadow b/etc/initrd/shadow new file mode 100644 index 0000000..76b0bc3 --- /dev/null +++ b/etc/initrd/shadow @@ -0,0 +1,2 @@ +root:toor +alnyan: diff --git a/libsys/src/lib.rs b/libsys/src/lib.rs index ddafbff..2990d35 100644 --- a/libsys/src/lib.rs +++ b/libsys/src/lib.rs @@ -24,6 +24,55 @@ pub struct ProgramArgs { pub size: usize } +// TODO utils +use core::fmt; + +#[derive(Clone, Copy)] +pub struct FixedStr { + len: usize, + data: [u8; N], +} + +impl FixedStr { + pub const fn empty() -> Self { + Self { + len: 0, + data: [0; N] + } + } + + pub fn copy_from_str(&mut self, src: &str) { + if src.len() > self.data.len() { + panic!("copy_from_str: src len > data len"); + } + self.len = src.len(); + self.data[..self.len].copy_from_slice(src.as_bytes()); + } + + pub fn as_str(&self) -> &str { + unsafe { + core::str::from_utf8_unchecked(&self.data[..self.len]) + } + } +} + +impl fmt::Debug for FixedStr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "\"")?; + fmt::Display::fmt(self, f)?; + write!(f, "\"") + } +} + +impl fmt::Display for FixedStr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for &byte in &self.data[..self.len] { + write!(f, "{}", byte as char)?; + } + Ok(()) + } +} + #[cfg(feature = "user")] pub mod calls; #[cfg(feature = "user")] diff --git a/libusr/src/env.rs b/libusr/src/env/mod.rs similarity index 81% rename from libusr/src/env.rs rename to libusr/src/env/mod.rs index 41a2ed6..beb989c 100644 --- a/libusr/src/env.rs +++ b/libusr/src/env/mod.rs @@ -1,6 +1,14 @@ -use libsys::{debug::TraceLevel, ProgramArgs}; -use alloc::vec::Vec; use crate::trace; +use alloc::vec::Vec; +use libsys::{ + debug::TraceLevel, + ProgramArgs, +}; + +mod passwd; +pub use passwd::UserInfo; +mod shadow; +pub use shadow::UserShadow; static mut PROGRAM_ARGS: Vec<&'static str> = Vec::new(); diff --git a/libusr/src/env/passwd.rs b/libusr/src/env/passwd.rs new file mode 100644 index 0000000..92f50c3 --- /dev/null +++ b/libusr/src/env/passwd.rs @@ -0,0 +1,99 @@ +use crate::io::{Read, read_line}; +use core::str::FromStr; +use core::fmt; +use crate::trace_debug; +use crate::file::File; +use libsys::{FixedStr, stat::{UserId, GroupId}}; + +#[derive(Debug, Clone, Copy)] +pub struct UserInfo { + name: FixedStr<32>, + uid: UserId, + gid: GroupId, + home: FixedStr<64>, + shell: FixedStr<64>, +} + +impl UserInfo { + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn home(&self) -> &str { + self.home.as_str() + } + + pub fn shell(&self) -> &str { + self.shell.as_str() + } + + pub fn uid(&self) -> UserId { + self.uid + } + + pub fn gid(&self) -> GroupId { + self.gid + } + + pub fn find bool>(pred: F) -> Result { + let mut file = File::open("/etc/passwd").map_err(|_| ())?; + let mut buf = [0; 128]; + loop { + let line = read_line(&mut file, &mut buf).map_err(|_| ())?; + if let Some(line) = line { + let ent = UserInfo::from_str(line)?; + if pred(&ent) { + return Ok(ent); + } + } else { + break; + } + } + Err(()) + } + + pub fn by_name(name: &str) -> Result { + Self::find(|ent| ent.name() == name) + } +} + +impl FromStr for UserInfo { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut iter = s.split(":"); + + let name = iter.next().ok_or(())?; + let uid = iter + .next() + .ok_or(()) + .and_then(|e| u32::from_str(e).map_err(|_| ())) + .map(UserId::from)?; + let gid = iter + .next() + .ok_or(()) + .and_then(|e| u32::from_str(e).map_err(|_| ())) + .map(GroupId::from)?; + let comment = iter.next().ok_or(())?; + let home = iter.next().ok_or(())?; + let shell = iter.next().ok_or(())?; + + if iter.next().is_some() { + return Err(()); + } + + let mut res = Self { + uid, + gid, + name: FixedStr::empty(), + home: FixedStr::empty(), + shell: FixedStr::empty(), + }; + + res.name.copy_from_str(&name); + res.home.copy_from_str(&home); + res.shell.copy_from_str(&shell); + + Ok(res) + } +} diff --git a/libusr/src/env/shadow.rs b/libusr/src/env/shadow.rs new file mode 100644 index 0000000..4691090 --- /dev/null +++ b/libusr/src/env/shadow.rs @@ -0,0 +1,67 @@ +use crate::file::File; +use crate::io::{Read, read_line}; +use core::str::FromStr; +use libsys::FixedStr; + +#[derive(Debug, Clone, Copy)] +pub struct UserShadow { + name: FixedStr<32>, + password: FixedStr<64>, +} + +impl UserShadow { + pub fn name(&self) -> &str { + self.name.as_str() + } + + pub fn password(&self) -> &str { + self.password.as_str() + } + + + pub fn find bool>(pred: F) -> Result { + let mut file = File::open("/etc/shadow").map_err(|_| ())?; + let mut buf = [0; 128]; + loop { + let line = read_line(&mut file, &mut buf).map_err(|_| ())?; + if let Some(line) = line { + let ent = UserShadow::from_str(line)?; + if pred(&ent) { + return Ok(ent); + } + } else { + break; + } + } + Err(()) + } + + pub fn by_name(name: &str) -> Result { + Self::find(|ent| ent.name() == name) + } +} + +impl FromStr for UserShadow { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut iter = s.split(':'); + + let name = iter.next().ok_or(())?; + let password = iter.next().ok_or(())?; + + if iter.next().is_some() { + return Err(()); + } + + let mut res = Self { + name: FixedStr::empty(), + password: FixedStr::empty(), + }; + + res.name.copy_from_str(name); + res.password.copy_from_str(password); + + Ok(res) + } +} diff --git a/libusr/src/io/mod.rs b/libusr/src/io/mod.rs index d0c97d5..907f2ef 100644 --- a/libusr/src/io/mod.rs +++ b/libusr/src/io/mod.rs @@ -42,3 +42,27 @@ pub fn stat(pathname: &str) -> Result { sys_fstatat(None, pathname, &mut buf, 0).unwrap(); Ok(buf) } + +// TODO use BufRead instead once it's implemented +pub(crate) fn read_line<'a, F: Read>(f: &mut F, buf: &'a mut [u8]) -> Result, ()> { + let mut pos = 0; + loop { + if pos == buf.len() { + return Err(()); + } + + let count = f.read(&mut buf[pos..=pos]).map_err(|_| ())?; + if count == 0 { + if pos == 0 { + return Ok(None); + } + break; + } + if buf[pos] == b'\n' { + break; + } + + pos += 1; + } + core::str::from_utf8(&buf[..pos]).map_err(|_| ()).map(Some) +} diff --git a/user/src/sbin/login.rs b/user/src/sbin/login.rs index 40733f4..2f973ff 100644 --- a/user/src/sbin/login.rs +++ b/user/src/sbin/login.rs @@ -14,7 +14,8 @@ use libsys::{ stat::{FileDescriptor, FileMode, GroupId, OpenFlags, UserId}, termios::{Termios, TermiosLflag}, }; -use libusr::{env, io}; +use libusr::{env::{self, UserInfo, UserShadow}, io}; +use core::str::FromStr; struct HiddenInput { fd: FileDescriptor, @@ -73,7 +74,7 @@ fn readline(fd: FileDescriptor, buf: &mut [u8]) -> Result<&str, Errno> { } } -fn login_as(uid: UserId, gid: GroupId, shell: &str) -> Result<(), Errno> { +fn login(uid: UserId, gid: GroupId, shell: &str) -> Result<(), Errno> { if let Some(pid) = unsafe { sys_fork() }? { let mut status = 0; sys_waitpid(pid, &mut status).ok(); @@ -90,6 +91,11 @@ fn login_as(uid: UserId, gid: GroupId, shell: &str) -> Result<(), Errno> { } } +fn login_as(name: &str) -> Result<(), Errno> { + let ent = UserInfo::by_name(name).map_err(|_| Errno::DoesNotExist)?; + login(ent.uid(), ent.gid(), ent.shell()) +} + // TODO baud rate and misc port settings #[no_mangle] fn main() -> i32 { @@ -132,15 +138,26 @@ fn main() -> i32 { loop { print!("login: "); let username = readline(FileDescriptor::STDIN, &mut user_buf).expect("Login read failed"); - print!("password: "); - let password = { - let mut input = HiddenInput::open(FileDescriptor::STDIN).unwrap(); - input.readline(&mut password_buf) - } - .expect("Password read failed"); - if username == "root" && password == "toor" { - login_as(UserId::from(0), GroupId::from(0), "/bin/shell").unwrap(); + let shadow = match UserShadow::by_name(username) { + Ok(e) => e, + Err(_) => continue + }; + + if !shadow.password().is_empty() { + print!("password: "); + let password = { + let mut input = HiddenInput::open(FileDescriptor::STDIN).unwrap(); + input.readline(&mut password_buf) + } + .expect("Password read failed"); + + if password != shadow.password() { + eprintln!("Incorrect password"); + continue; + } } + + login_as(username); } }