initial commit
This commit is contained in:
commit
e6148f6d50
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "doorlib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anes = "0.1.6"
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
termios = "0.3.3"
|
360
src/lib.rs
Normal file
360
src/lib.rs
Normal file
@ -0,0 +1,360 @@
|
||||
#[cfg(target_family = "unix")]
|
||||
use termios::{cfmakeraw, tcsetattr, Termios, TCSANOW};
|
||||
|
||||
use anes::{esc, ResetAttributes};
|
||||
use std::env;
|
||||
use std::fs::read_to_string;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::process;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
type Result<T> = std::result::Result<T, io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ConnType {
|
||||
Local,
|
||||
Serial,
|
||||
Telnet(i64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct UserInfo {
|
||||
typ: ConnType,
|
||||
bbsid: String,
|
||||
record: i32,
|
||||
realname: String,
|
||||
alias: String,
|
||||
seclevel: i32,
|
||||
timeleft: u64,
|
||||
emulation: i32,
|
||||
node: i32,
|
||||
timeout: Instant,
|
||||
starttime: Instant,
|
||||
}
|
||||
|
||||
pub trait Conn {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()>;
|
||||
|
||||
fn write_str(&mut self, s: &str) -> Result<()> {
|
||||
self.write(s.as_bytes())
|
||||
}
|
||||
|
||||
fn write_ln(&mut self, line: &str) -> Result<()> {
|
||||
self.write_str(line)?;
|
||||
self.write_str("\r\n")
|
||||
}
|
||||
|
||||
fn read_byte(&mut self) -> Result<u8>;
|
||||
|
||||
fn info(&self) -> &UserInfo;
|
||||
}
|
||||
|
||||
struct LocalUser {
|
||||
info: UserInfo,
|
||||
#[cfg(target_family = "unix")]
|
||||
termios: Termios,
|
||||
}
|
||||
|
||||
impl LocalUser {
|
||||
fn new(info: UserInfo) -> LocalUser {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
let termios = Termios::from_fd(0).expect("terminal mode");
|
||||
let mut raw = termios;
|
||||
cfmakeraw(&mut raw);
|
||||
tcsetattr(0, TCSANOW, &raw).expect("terminal raw mode");
|
||||
LocalUser { info, termios }
|
||||
}
|
||||
#[cfg(target_family = "windows")]
|
||||
LocalUser { info }
|
||||
}
|
||||
}
|
||||
|
||||
impl Conn for LocalUser {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()> {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
io::stdout().write_all(data)?;
|
||||
io::stdout().flush()
|
||||
}
|
||||
#[cfg(target_family = "windows")]
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_byte(&mut self) -> Result<u8> {
|
||||
let mut byte = 0u8;
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
io::stdin()
|
||||
.lock()
|
||||
.read_exact(std::slice::from_mut(&mut byte))?;
|
||||
}
|
||||
Ok(byte)
|
||||
}
|
||||
|
||||
fn info(&self) -> &UserInfo {
|
||||
&self.info
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LocalUser {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(target_family = "unix")]
|
||||
tcsetattr(0, TCSANOW, &self.termios).expect("reset terminal");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NetUser {
|
||||
info: UserInfo,
|
||||
stream: TcpStream,
|
||||
}
|
||||
|
||||
impl NetUser {
|
||||
fn read1(&mut self) -> Result<u8> {
|
||||
let mut rx_byte: u8 = 0;
|
||||
loop {
|
||||
match self.stream.read(std::slice::from_mut(&mut rx_byte)) {
|
||||
Ok(_) => {
|
||||
self.info.timeout = Instant::now();
|
||||
return Ok(rx_byte);
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
self.maybe_timeout();
|
||||
self.maybe_out_of_time();
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_timeout(&mut self) {
|
||||
const TIME_OUT: u64 = 300;
|
||||
if self.info.timeout.elapsed().as_secs() >= TIME_OUT {
|
||||
let e = esc!("[1;31m");
|
||||
self.write_ln(format!("\r\n\r\n{e}Timeout!{ResetAttributes}").as_str())
|
||||
.expect("write");
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_out_of_time(&mut self) {
|
||||
let start_time = self.info.starttime;
|
||||
let time_left = self.info.timeleft;
|
||||
if start_time.elapsed().as_secs() >= time_left {
|
||||
let e = esc!("[1;31m");
|
||||
self.write_ln(format!("\r\n\r\n{e}You're out of time!{ResetAttributes}\r\n").as_str())
|
||||
.expect("write");
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Conn for NetUser {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()> {
|
||||
self.stream.write_all(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_byte(&mut self) -> Result<u8> {
|
||||
let mut state = 0;
|
||||
loop {
|
||||
let rx_byte = self.read1()?;
|
||||
state = match (state, rx_byte) {
|
||||
(0, 255) => 1,
|
||||
(0, _) => return Ok(rx_byte),
|
||||
(1, 255) => {
|
||||
self.stream.write_all(std::slice::from_ref(&rx_byte))?;
|
||||
0
|
||||
}
|
||||
(1, 250) => 3,
|
||||
(1, _) => 2,
|
||||
(2, _) => 0,
|
||||
(3, 240) => 0,
|
||||
_ => state,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self) -> &UserInfo {
|
||||
&self.info
|
||||
}
|
||||
}
|
||||
|
||||
pub struct User {
|
||||
typ: bool,
|
||||
netuser: Option<NetUser>,
|
||||
localuser: Option<LocalUser>,
|
||||
}
|
||||
|
||||
impl Conn for User {
|
||||
fn write(&mut self, data: &[u8]) -> Result<()> {
|
||||
match self.typ {
|
||||
true => {
|
||||
self.netuser.as_mut().unwrap().write(data)?;
|
||||
return Ok(());
|
||||
}
|
||||
false => {
|
||||
self.localuser.as_mut().unwrap().write(data)?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn read_byte(&mut self) -> Result<u8> {
|
||||
match self.typ {
|
||||
true => return self.netuser.as_mut().unwrap().read_byte(),
|
||||
false => return self.localuser.as_mut().unwrap().read_byte(),
|
||||
};
|
||||
}
|
||||
|
||||
fn info(&self) -> &UserInfo {
|
||||
match self.typ {
|
||||
true => return &self.netuser.as_ref().unwrap().info,
|
||||
false => return &self.localuser.as_ref().unwrap().info,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_file(user: &mut User, path: &str) -> Result<()> {
|
||||
if let Ok(file) = std::fs::read(path) {
|
||||
user.write(&file)?;
|
||||
user.write_str(ResetAttributes.to_string().as_str())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_string(user: &mut impl Conn, len: usize) -> Result<String> {
|
||||
const BACKSPACE: [u8; 3] = *b"\x08\x20\x08";
|
||||
|
||||
user.write_str(esc!("[s"))?;
|
||||
user.write_str(esc!("[1;37;45m"))?;
|
||||
for _i in 1..len {
|
||||
user.write_str(" ")?;
|
||||
}
|
||||
user.write_str(esc!("[u"))?;
|
||||
|
||||
let mut received: Vec<u8> = vec![];
|
||||
while received.len() != len {
|
||||
let ch = user.read_byte()?;
|
||||
match ch {
|
||||
0 | 10 => {}
|
||||
13 => break,
|
||||
127 | 8 => {
|
||||
if !received.is_empty() {
|
||||
user.write(&BACKSPACE)?;
|
||||
received.truncate(received.len() - 1);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
user.write(std::slice::from_ref(&ch))?;
|
||||
received.extend_from_slice(&[ch]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let s = std::str::from_utf8(&received).unwrap().to_string();
|
||||
user.write_ln(ResetAttributes.to_string().as_str())?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn read_lines(filename: &str) -> Result<Vec<String>> {
|
||||
let s = read_to_string(filename)?;
|
||||
Ok(s.lines().map(str::to_owned).collect())
|
||||
}
|
||||
|
||||
fn read_trimmed_lines(path: &str) -> Result<Vec<String>> {
|
||||
let lines = read_lines(path)?;
|
||||
Ok(lines.into_iter().map(|s| s.trim().to_owned()).collect())
|
||||
}
|
||||
|
||||
fn read_door32(path: &str) -> Result<UserInfo> {
|
||||
let lines = read_trimmed_lines(path)?;
|
||||
let typ = match lines[0].parse::<i32>().expect("i32") {
|
||||
0 => ConnType::Local,
|
||||
1 => ConnType::Serial,
|
||||
2 => ConnType::Telnet(lines[1].parse().expect("door32.sys socket")),
|
||||
_ => ConnType::Local,
|
||||
};
|
||||
let info = UserInfo {
|
||||
typ,
|
||||
bbsid: lines[3].clone(),
|
||||
record: lines[4].parse().expect("door32.sys record"),
|
||||
realname: lines[5].clone(),
|
||||
alias: lines[6].clone(),
|
||||
seclevel: lines[7].parse().expect("door32.sys seclevel"),
|
||||
timeleft: lines[8].parse().expect("door32.sys timeleft"),
|
||||
emulation: lines[9].parse().expect("door32.sys emulation"),
|
||||
node: lines[10].parse().expect("door32.sys node"),
|
||||
timeout: Instant::now(),
|
||||
starttime: Instant::now(),
|
||||
};
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn convert_socket(sock: i64) -> Result<TcpStream> {
|
||||
#[cfg(target_family = "unix")]
|
||||
let stream = unsafe {
|
||||
use std::os::fd::FromRawFd;
|
||||
TcpStream::from_raw_fd(sock as i32)
|
||||
};
|
||||
#[cfg(target_family = "windows")]
|
||||
let stream = unsafe {
|
||||
use std::os::windows::io::FromRawSocket;
|
||||
drop(std::net::TcpListener::bind("255.255.255.255:0")); // hack to get WSAStartup to fire
|
||||
TcpStream::from_raw_socket(sock as u64)
|
||||
};
|
||||
stream.set_nonblocking(true)?;
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
pub fn init() -> Result<User> {
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() < 2 || 3 < args.len() {
|
||||
panic!("Usage: door.exe door32.sys [socket]");
|
||||
}
|
||||
let info = read_door32(&args[1]).expect("read door32.sys");
|
||||
let fd = if args.len() == 3 {
|
||||
let fd = args[2].parse().expect("Socket is an integer");
|
||||
if fd > 0 {
|
||||
fd
|
||||
} else if let ConnType::Telnet(fd) = info.typ {
|
||||
fd
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
if let ConnType::Telnet(fd) = info.typ {
|
||||
fd
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
match info.typ {
|
||||
ConnType::Serial => panic!("UART not supported"),
|
||||
ConnType::Local => {
|
||||
let user = LocalUser::new(info);
|
||||
|
||||
let u = User {
|
||||
typ: false,
|
||||
netuser: None,
|
||||
localuser: Some(user),
|
||||
};
|
||||
return Ok(u);
|
||||
}
|
||||
ConnType::Telnet(_) => {
|
||||
let stream = convert_socket(fd).expect("tcp stream");
|
||||
let user = NetUser { info, stream };
|
||||
let u = User {
|
||||
typ: true,
|
||||
netuser: Some(user),
|
||||
localuser: None,
|
||||
};
|
||||
return Ok(u);
|
||||
}
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user