This commit is contained in:
Jovi Hsu 2021-11-07 23:57:18 +08:00
parent bec3952861
commit 1f4a535110
8 changed files with 337 additions and 133 deletions

View File

@ -15,17 +15,29 @@ repository = "https://www.github.com/HsuJv/webgateway"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
actix = "0.10.0" tokio = {version="1.13.0", feature="io-util"}
tokio-io = "0.1.13"
tokio-core = "0.1.18"
tokio-codec = "0.1.2"
tokio-util = "0.6.9"
actix = "0.10"
actix-session = "0.4" actix-session = "0.4"
actix-web = "3.3.2" actix-web = "3.3.2"
actix-files = "0.5.0" actix-files = "0.5.0"
actix-web-actors = "3.0.0" actix-web-actors = "3.0.0"
actix-codec = "0.4"
urlencoding = "2.1.0" urlencoding = "2.1.0"
bytes = "1.1.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
trust-dns-resolver = "0.20" trust-dns-resolver = "0.20"
rand = "0.8" rand = "0.8"
futures = "0.3.17"
futures-util= "0.3"
# log systems # log systems
femme = "1.3" femme = "1.3"
log = "0.4" log = "0.4"

View File

@ -1,64 +1,198 @@
use actix::{Actor, Addr, Context, Handler, Message, MessageResponse}; use actix::prelude::*;
use actix_codec::{Decoder, Encoder};
use actix_web::web::Bytes; use actix_web::web::Bytes;
use std::net::*; use bytes::BytesMut;
use futures::stream::*;
use std::io;
use std::{collections::HashMap, task::Poll};
use tokio::net::{tcp::OwnedWriteHalf, TcpStream};
use tokio::runtime::{self, Runtime};
use tokio_util::codec::FramedRead;
use log::*;
struct TcpCodec;
impl Encoder<Bytes> for TcpCodec {
type Error = io::Error;
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
info!("encoding: {:?}", item);
Ok(())
}
}
impl Decoder for TcpCodec {
type Item = Bytes;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
info!("recv from server: {:?}", src);
Ok(Some(Bytes::from(src.to_vec())))
}
}
use log::info;
#[derive(MessageResponse)] #[derive(MessageResponse)]
pub enum AgentResp { pub enum AgentResult {
Success, Success,
Failed, Failed,
} }
#[derive(Message)] #[derive(Message)]
#[rtype(result = "AgentResp")] #[rtype(result = "AgentResult")]
pub enum AgentMsg { pub enum AgentMsg {
ConnectServer(SocketAddr), ReadReady,
SendToServer(Bytes), SendToServer(String),
SendToClient(Bytes), SendToClient(Bytes),
} }
pub struct Agent { pub struct Agent {
id: u32, id: u32,
server_info: Option<SocketAddr>, server_info: String,
server_stream: Option<TcpStream>, writer: OwnedWriteHalf,
// client_info: SocketAddr, runtime: Runtime,
} }
impl Actor for Agent { impl Actor for Agent {
type Context = Context<Self>; type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Self::Context) {
info!("Agent {} started", self.id);
// ctx.address().do_send(AgentMsg::ReadReady);
}
} }
impl Handler<AgentMsg> for Agent { impl Handler<AgentMsg> for Agent {
type Result = AgentResp; type Result = AgentResult;
fn handle(&mut self, msg: AgentMsg, _ctx: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: AgentMsg, _ctx: &mut Context<Self>) -> Self::Result {
match msg { match msg {
AgentMsg::ConnectServer(addr) => { AgentMsg::SendToServer(data) => {
info!("connect to server: {}", addr); self.writer.try_write(data.as_bytes()).unwrap();
self.server_info = Some(addr); AgentResult::Success
if let Ok(stream) = TcpStream::connect(addr) { }
stream AgentMsg::SendToClient(_data) => panic!("unexpected message"),
.set_nonblocking(true) _ => panic!("unexpected message"),
.expect("set_nonblocking call failed");
self.server_stream = Some(stream);
AgentResp::Success
} else {
AgentResp::Failed
} }
} }
AgentMsg::SendToServer(_data) => AgentResp::Success,
AgentMsg::SendToClient(_data) => AgentResp::Success,
} }
impl StreamHandler<Bytes> for Agent {
fn handle(&mut self, msg: Bytes, ctx: &mut Context<Self>) {
info!("recv from server: {:?}", msg);
ctx.address().do_send(AgentMsg::SendToClient(msg));
} }
} }
impl Agent { impl Agent {
pub fn new(id: u32) -> Addr<Agent> { pub async fn new(id: u32, target: (String, u16)) -> Option<Addr<Agent>> {
let (host, port) = target;
let server_info = format!("{}:{}", host, port);
info!("connect to server: {}", server_info);
let addr = Agent::create(move |ctx| {
let mut builder = runtime::Builder::new_current_thread();
builder.enable_all();
let runtime = builder.build().unwrap();
let server_stream = runtime.block_on(TcpStream::connect(&server_info));
if server_stream.is_err() {
info!("connect to server failed: {}", server_info);
}
let server_stream = server_stream.unwrap();
let (r, w) = server_stream.into_split();
// let r = FramedRead::new(r, TcpCodec {});
let xx = poll_fn(move |_a| {
let mut buf = [0; 16384];
match r.try_read(&mut buf) {
Ok(n) => {
if n == 0 {
return Poll::Pending;
}
return Poll::Ready(Some(Bytes::from(buf[..n].to_vec())));
}
Err(e) => {
error!("error: {}", e);
if e.kind() == io::ErrorKind::WouldBlock {
return Poll::Pending;
}
return Poll::Ready(None);
}
};
});
Agent::add_stream(xx, ctx);
Self { Self {
id, id,
server_info: None, server_info,
server_stream: None, writer: w,
runtime,
}
});
Some(addr)
}
}
#[derive(MessageResponse)]
pub enum AgentManagerResult {
Success(Addr<Agent>),
Failed,
NoReturn,
}
#[derive(Message)]
#[rtype(result = "AgentManagerResult")]
pub enum AgentManagerMsg {
Add((u32, Addr<Agent>)),
Get(u32),
Del(u32),
}
pub struct AgentManager {
agents: HashMap<u32, Addr<Agent>>,
}
impl AgentManager {
pub fn new() -> Addr<Self> {
Self {
agents: HashMap::new(),
} }
.start() .start()
} }
} }
impl Actor for AgentManager {
type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Context<Self>) {
info!("AgentManager started");
}
fn stopped(&mut self, _ctx: &mut Context<Self>) {
info!("AgentManager stopped");
}
}
impl Handler<AgentManagerMsg> for AgentManager {
type Result = AgentManagerResult;
fn handle(&mut self, msg: AgentManagerMsg, _ctx: &mut Context<Self>) -> Self::Result {
match msg {
AgentManagerMsg::Add(addr) => {
self.agents.insert(addr.0, addr.1);
AgentManagerResult::NoReturn
}
AgentManagerMsg::Get(aid) => {
if let Some(addr) = self.agents.get(&aid) {
AgentManagerResult::Success(addr.clone())
} else {
AgentManagerResult::Failed
}
}
AgentManagerMsg::Del(id) => {
self.agents.remove(&id);
AgentManagerResult::NoReturn
}
}
}
}

View File

@ -6,7 +6,6 @@ use serde_json::json;
use log::info; use log::info;
use rand::Rng; use rand::Rng;
use crate::agent::resolver::*;
use crate::AppData; use crate::AppData;
use super::agent; use super::agent;
@ -28,10 +27,10 @@ pub async fn target_validate(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let remote = params.into_inner(); let remote = params.into_inner();
info!("{:?}", remote); info!("{:?}", remote);
let resolved = data.resolver.send(ResolveMsg::Resolve(remote.host)).await; // let resolved = data.resolver.send(ResolveMsg::Resolve(remote.host)).await;
match resolved.unwrap() { match data.resolver.lockup(remote.host).await {
ResolveResp::Success(ipaddr) => { Some(ipaddr) => {
let json = json!({ let json = json!({
"status": "success", "status": "success",
"ip": ipaddr "ip": ipaddr
@ -48,6 +47,7 @@ pub async fn target_validate(
} }
} }
const SSH_VER: &str = "SSH-2.0-OpenSSH_7.4p1 Ubuntu-4ubuntu2.1";
#[post("/target/ssh")] #[post("/target/ssh")]
pub async fn target_ssh( pub async fn target_ssh(
session: Session, session: Session,
@ -56,17 +56,18 @@ pub async fn target_ssh(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let aid = rand::thread_rng().gen::<u32>(); let aid = rand::thread_rng().gen::<u32>();
let remote = params.into_inner(); let remote = params.into_inner();
let agent = agent::Agent::new(aid); let agent = agent::Agent::new(aid, (remote.ip, remote.port)).await;
match agent match agent {
.send(agent::AgentMsg::ConnectServer( Some(addr) => {
format!("{}:{}", remote.ip, remote.port).parse().unwrap(), let _ = addr
)) .send(agent::AgentMsg::SendToServer(SSH_VER.to_string()))
.await .await;
{
Ok(agent::AgentResp::Success) => {
// add to agent list // add to agent list
data.agents.write().unwrap().insert(aid, agent); let _ = data
.agents
.send(agent::AgentManagerMsg::Add((aid, addr)))
.await;
// add session, so that the websocket can send message to the agent // add session, so that the websocket can send message to the agent
let _ = session.set::<u32>("aid", aid); let _ = session.set::<u32>("aid", aid);

View File

@ -1,75 +1,108 @@
use actix::{Actor, Addr, Context, Handler, Message, MessageResponse}; // use actix::{Actor, Addr, Context, Handler, Message, MessageResponse};
use std::net::*; // use std::net::*;
use trust_dns_resolver::config::*; // use trust_dns_resolver::config::*;
use trust_dns_resolver::Resolver; // use trust_dns_resolver::Resolver;
use log::info; // use log::info;
#[derive(MessageResponse)] // #[derive(MessageResponse)]
pub enum ResolveResp { // pub enum ResolveResp {
Success(IpAddr), // Success(IpAddr),
Failed, // Failed,
} // }
#[derive(Message)] // #[derive(Message)]
#[rtype(result = "ResolveResp")] // #[rtype(result = "ResolveResp")]
pub enum ResolveMsg { // pub enum ResolveMsg {
Resolve(String), // Resolve(String),
} // }
// pub struct DnsResolver {
// resolver: Resolver,
// }
// impl Actor for DnsResolver {
// type Context = Context<Self>;
// }
// impl Handler<ResolveMsg> for DnsResolver {
// type Result = ResolveResp;
// fn handle(&mut self, msg: ResolveMsg, _: &mut Context<Self>) -> Self::Result {
// match msg {
// ResolveMsg::Resolve(name) => {
// if let Ok(response) = self.resolver.lookup_ip(name.clone()) {
// if let Some(address) = response.iter().next() {
// info!("Resolved {} to {}", name, address);
// ResolveResp::Success(address)
// } else {
// ResolveResp::Failed
// }
// } else {
// info!("Failed to resolve {}", name);
// ResolveResp::Failed
// }
// }
// }
// }
// }
// impl DnsResolver {
// pub fn new() -> Addr<Self> {
// let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
// DnsResolver { resolver }.start()
// }
// }
use std::net::IpAddr;
use tokio::runtime::{self, Runtime};
use trust_dns_resolver::{
config::*,
name_server::{GenericConnection, GenericConnectionProvider, TokioRuntime},
};
use trust_dns_resolver::{AsyncResolver, TokioHandle};
use log::*;
pub struct DnsResolver { pub struct DnsResolver {
resolver: Resolver, resolver: AsyncResolver<GenericConnection, GenericConnectionProvider<TokioRuntime>>,
} runtime: Runtime,
impl Actor for DnsResolver {
type Context = Context<Self>;
}
impl Handler<ResolveMsg> for DnsResolver {
type Result = ResolveResp;
fn handle(&mut self, msg: ResolveMsg, _: &mut Context<Self>) -> Self::Result {
match msg {
ResolveMsg::Resolve(name) => {
if let Ok(response) = self.resolver.lookup_ip(name.clone()) {
if let Some(address) = response.iter().next() {
info!("Resolved {} to {}", name, address);
ResolveResp::Success(address)
} else {
ResolveResp::Failed
}
} else {
info!("Failed to resolve {}", name);
ResolveResp::Failed
}
}
}
}
} }
impl DnsResolver { impl DnsResolver {
pub fn new() -> Addr<Self> { pub fn new() -> Self {
let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap(); let resolver = AsyncResolver::new(
ResolverConfig::default(),
ResolverOpts::default(),
TokioHandle,
)
.unwrap();
DnsResolver { resolver }.start() let mut builder = runtime::Builder::new_current_thread();
} builder.enable_all();
let runtime = builder.build().unwrap();
Self { resolver, runtime }
} }
// Construct a new Resolver with default configuration options pub async fn lockup(&self, name: String) -> Option<IpAddr> {
// let mut resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap(); let lookup = self.resolver.lookup_ip(name.clone());
// On Unix/Posix systems, this will read the /etc/resolv.conf // todo!("work out how to run it async");
// let mut resolver = Resolver::from_system_conf().unwrap(); if let Ok(response) = self.runtime.block_on(lookup) {
if let Some(address) = response.iter().next() {
// Lookup the IP addresses associated with a name. info!("Resolved {} to {}", name, address);
// let mut response = resolver.lookup_ip("www.example.com.").unwrap(); Some(address)
} else {
// There can be many addresses associated with the name, info!("Failed to resolve {}", name);
// this can return IPv4 and/or IPv6 addresses None
// let address = response.iter().next().expect("no addresses returned!"); }
// if address.is_ipv4() { } else {
// assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34))); info!("Failed to resolve {}", name);
// } else { None
// assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946))); }
// } }
}

View File

@ -7,9 +7,9 @@ use log::*;
use crate::AppData; use crate::AppData;
use super::agent::Agent; use super::agent::{Agent, AgentManagerMsg, AgentManagerResult};
/// Define HTTP actor /// Define Websocket actor
struct WsSession { struct WsSession {
agent: Addr<Agent>, agent: Addr<Agent>,
} }
@ -38,8 +38,13 @@ pub async fn ws_index(
stream: web::Payload, stream: web::Payload,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let aid = session.get::<u32>("aid").unwrap_or(Some(0)).unwrap(); let aid = session.get::<u32>("aid").unwrap_or(Some(0)).unwrap();
let agent = data.agents.read().unwrap().get(&aid).unwrap().clone();
let resp = ws::start(WsSession { agent }, &req, stream); let resp = match data.agents.send(AgentManagerMsg::Get(aid)).await.unwrap() {
AgentManagerResult::Success(agent) => ws::start(WsSession { agent }, &req, stream),
_ => Err(Error::from(actix_web::error::ErrorInternalServerError(
"Agent not found",
))),
};
match &resp { match &resp {
Ok(resp) => info!("{:?}", resp), Ok(resp) => info!("{:?}", resp),

View File

@ -1,14 +1,13 @@
use std::{collections::HashMap, sync::RwLock};
use actix::Addr; use actix::Addr;
use actix_files as fs; use actix_files as fs;
use actix_session::CookieSession; use actix_session::CookieSession;
use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::*; use actix_web::*;
use agent::{agent::Agent, resolver::DnsResolver}; use agent::{agent::AgentManager, resolver::DnsResolver};
use log::info; use log::info;
use rand::Rng; use rand::Rng;
use user::auth::Authenticator;
mod agent; mod agent;
mod user; mod user;
@ -19,15 +18,17 @@ const PAGE_NOT_FOUND: &str = "./static/p404.html";
pub struct AppData { pub struct AppData {
// session: CookieSession, // session: CookieSession,
resolver: Addr<DnsResolver>, resolver: DnsResolver,
agents: RwLock<HashMap<u32, Addr<Agent>>>, authenticator: Addr<Authenticator>,
agents: Addr<AgentManager>,
} }
impl AppData { impl AppData {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
resolver: DnsResolver::new(), resolver: DnsResolver::new(),
agents: RwLock::new(HashMap::new()), authenticator: Authenticator::new(),
agents: AgentManager::new(),
} }
} }
} }

View File

@ -1,22 +1,23 @@
use actix::{Actor, Context, Handler, Message, MessageResponse}; use actix::{Actor, Addr, Context, Handler, Message, MessageResponse};
use actix_session::Session;
use actix_web::*; use actix_web::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use log::info; use log::info;
use crate::AppData;
#[derive(MessageResponse)] #[derive(MessageResponse)]
#[allow(dead_code)] #[allow(dead_code)]
enum AuthResp { enum AuthResult {
AuthSuccess, AuthSuccess,
AuthFailure, AuthFailure,
} }
#[derive(Message)] #[derive(Message)]
#[rtype(result = "AuthResp")] #[rtype(result = "AuthResult")]
enum AuthMsg { enum AuthMsg {
DoAuth, DoAuth(AuthInfo),
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -25,12 +26,19 @@ pub struct AuthInfo {
password: String, password: String,
} }
impl Actor for AuthInfo { pub struct Authenticator;
impl Authenticator {
pub fn new() -> Addr<Self> {
Self {}.start()
}
}
impl Actor for Authenticator {
type Context = Context<Self>; type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Self::Context) { fn started(&mut self, _ctx: &mut Self::Context) {
info!("AuthInfo started"); info!("AuthInfo started");
info!("{:?}", self);
} }
fn stopped(&mut self, _ctx: &mut Self::Context) { fn stopped(&mut self, _ctx: &mut Self::Context) {
@ -38,23 +46,32 @@ impl Actor for AuthInfo {
} }
} }
impl Handler<AuthMsg> for AuthInfo { impl Handler<AuthMsg> for Authenticator {
type Result = AuthResp; type Result = AuthResult;
fn handle(&mut self, _msg: AuthMsg, _ctx: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: AuthMsg, _ctx: &mut Context<Self>) -> Self::Result {
info!("AuthInfo handle"); match msg {
AuthResp::AuthSuccess AuthMsg::DoAuth(auth_info) => {
if auth_info.username == "admin" && auth_info.password == "admin" {
AuthResult::AuthSuccess
} else {
AuthResult::AuthFailure
}
}
}
} }
} }
#[post("/auth")] #[post("/auth")]
pub async fn auth(params: web::Json<AuthInfo>) -> Result<HttpResponse, Error> { pub async fn auth(
let auth = params.into_inner(); params: web::Json<AuthInfo>,
let auth_addr = auth.start(); data: web::Data<AppData>,
let res = auth_addr.send(AuthMsg::DoAuth).await; ) -> Result<HttpResponse, Error> {
let auth_info = params.into_inner();
let res = data.authenticator.send(AuthMsg::DoAuth(auth_info)).await;
match res { match res {
Ok(AuthResp::AuthSuccess) => Ok(HttpResponse::Ok().json(json!({ Ok(AuthResult::AuthSuccess) => Ok(HttpResponse::Ok().json(json!({
"status": "success", "status": "success",
}))), }))),
_ => Ok(HttpResponse::Ok().json(json!({ _ => Ok(HttpResponse::Ok().json(json!({

View File

@ -131,6 +131,7 @@ impl Component for Host {
<label for="hostname">{"Hostname: "}</label> <label for="hostname">{"Hostname: "}</label>
<input id="hostname" type="text" placeholder="hostname" onchange={updatehost} /> <input id="hostname" type="text" placeholder="hostname" onchange={updatehost} />
<br /> <br />
<label for="port">{" Port: "}</label>
<input id="port" type="text" placeholder="port" onchange={updateport}/> <input id="port" type="text" placeholder="port" onchange={updateport}/>
<br /> <br />
<button onclick={connecthost}>{"Connect"}</button> <button onclick={connecthost}>{"Connect"}</button>