the server will now save the password that is provided or generated for
it to the systems keyring, and attempt to load it at run time. I also added the beginnings for ingesting shodan results for hosts into the tool. The attack client can now save servers, load them from the folder, and save/load the passwords to/from the system keyring.
This commit is contained in:
+426
-336
@@ -88,6 +88,7 @@ pub fn run_tui(
|
||||
let (event_tx, event_rx) = channel::<AppEvent>();
|
||||
let input_tx = event_tx.clone();
|
||||
let mut project_list_state = ListState::default();
|
||||
let mut host_list_state = ListState::default();
|
||||
let mut system = System::new_all();
|
||||
let system_timer = Duration::from_secs(1);
|
||||
let mut last_print = Instant::now();
|
||||
@@ -97,6 +98,7 @@ pub fn run_tui(
|
||||
project_list_state.select(Some(0));
|
||||
state.selected_project = 0;
|
||||
}
|
||||
host_list_state.select(Some(0));
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
if !state.server_broker_running && !state.app_state_running {
|
||||
@@ -143,245 +145,293 @@ pub fn run_tui(
|
||||
last_print = Instant::now();
|
||||
}
|
||||
let mut text_area_width = 0;
|
||||
terminal.draw(|f| {
|
||||
let main_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Max(3)])
|
||||
.split(f.area());
|
||||
let top_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(45),
|
||||
Constraint::Percentage(35),
|
||||
])
|
||||
.split(main_chunks[0]);
|
||||
text_area_width = top_chunks[1].width.saturating_sub(2);
|
||||
let projects: Vec<ListItem> = state
|
||||
.projects
|
||||
.iter()
|
||||
.map(|p| ListItem::new(format!(" {} | {}", p.org_name, p.name)))
|
||||
.collect();
|
||||
let projects_list = List::new(projects)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Projects (ctrl + arrow keys to select) "),
|
||||
)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol(">> ");
|
||||
f.render_stateful_widget(projects_list, top_chunks[0], &mut project_list_state);
|
||||
let mut info_lines: Vec<Line> = Vec::new();
|
||||
let project = state.projects[state.selected_project].clone();
|
||||
info_lines.push(Line::from(format!(
|
||||
"|CPU: {}% | RAM: {}GB / {}GB |",
|
||||
system.global_cpu_usage().floor(),
|
||||
system.used_memory() / BYTES_PER_GB,
|
||||
system.total_memory() / BYTES_PER_GB,
|
||||
)));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
"--- PROJECT INFORMATION ---",
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
info_lines.push(Line::from(format!("ORG: {}", project.org_name)));
|
||||
info_lines.push(Line::from(format!("NAME: {}", project.name)));
|
||||
info_lines.push(Line::from(format!("NOTES: {}", project.notes.display())));
|
||||
info_lines.push(Line::from(format!("Files: {}", project.files.display())));
|
||||
if let Some(db) = project.db {
|
||||
info_lines.push(Line::from(format!("DISTROBOX: {}", db.name)));
|
||||
}
|
||||
if project.current {
|
||||
info_lines.push(Line::from("STATUS: CURRENT"));
|
||||
if state.selecting_host {
|
||||
if !state.projects[state.selected_project].hosts.is_empty() {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Max(3)])
|
||||
.split(f.area());
|
||||
let project_name = Paragraph::new(format!(
|
||||
"{} {}",
|
||||
state.projects[state.selected_project].org_name,
|
||||
state.projects[state.selected_project].name
|
||||
));
|
||||
let hosts: Vec<ListItem> = state.projects[state.selected_project]
|
||||
.hosts
|
||||
.iter()
|
||||
.map(|h| ListItem::new(format!("{}: {}", h.ip, h.hostname)))
|
||||
.collect();
|
||||
let hosts_list = List::new(hosts)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Select a host."),
|
||||
)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol(">> ");
|
||||
f.render_stateful_widget(hosts_list, chunks[0], &mut host_list_state);
|
||||
f.render_widget(project_name, chunks[1]);
|
||||
})?;
|
||||
} else {
|
||||
info_lines.push(Line::from("STATUS: UPCOMING"));
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
"No hosts in the current project to select!".to_string(),
|
||||
)));
|
||||
state.selecting_host = false;
|
||||
}
|
||||
if !project.hosts.is_empty() {
|
||||
} else {
|
||||
terminal.draw(|f| {
|
||||
let main_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Max(3)])
|
||||
.split(f.area());
|
||||
let top_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Percentage(45),
|
||||
Constraint::Percentage(35),
|
||||
])
|
||||
.split(main_chunks[0]);
|
||||
text_area_width = top_chunks[1].width.saturating_sub(2);
|
||||
let projects: Vec<ListItem> = state
|
||||
.projects
|
||||
.iter()
|
||||
.map(|p| ListItem::new(format!(" {} | {}", p.org_name, p.name)))
|
||||
.collect();
|
||||
let projects_list = List::new(projects)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Projects (ctrl + arrow keys to select) "),
|
||||
)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Blue)
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol(">> ");
|
||||
f.render_stateful_widget(projects_list, top_chunks[0], &mut project_list_state);
|
||||
let mut info_lines: Vec<Line> = Vec::new();
|
||||
let project = state.projects[state.selected_project].clone();
|
||||
info_lines.push(Line::from(format!(
|
||||
"|CPU: {}% | RAM: {}GB / {}GB |",
|
||||
system.global_cpu_usage().floor(),
|
||||
system.used_memory() / BYTES_PER_GB,
|
||||
system.total_memory() / BYTES_PER_GB,
|
||||
)));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
"--- PROJECT HOST INFORMATION ---",
|
||||
"--- PROJECT INFORMATION ---",
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
for host in &project.hosts {
|
||||
info_lines.push(Line::from(format!("- {}:{}", host.hostname, host.ip)));
|
||||
info_lines.push(Line::from(format!(" - PWNED: {}", host.pwned)));
|
||||
info_lines.push(Line::from(format!(
|
||||
" - CONTROL PORT: {}",
|
||||
host.control_port
|
||||
info_lines.push(Line::from(format!("ORG: {}", project.org_name)));
|
||||
info_lines.push(Line::from(format!("NAME: {}", project.name)));
|
||||
info_lines.push(Line::from(format!("NOTES: {}", project.notes.display())));
|
||||
info_lines.push(Line::from(format!("Files: {}", project.files.display())));
|
||||
if let Some(db) = project.db {
|
||||
info_lines.push(Line::from(format!("DISTROBOX: {}", db.name)));
|
||||
}
|
||||
if project.current {
|
||||
info_lines.push(Line::from("STATUS: CURRENT"));
|
||||
} else {
|
||||
info_lines.push(Line::from("STATUS: UPCOMING"));
|
||||
}
|
||||
if !project.hosts.is_empty() {
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
"--- PROJECT HOST INFORMATION ---",
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
if !host.open_ports.is_empty() {
|
||||
info_lines.push(Line::from(format!(" - PORTS:")));
|
||||
for port in &host.open_ports {
|
||||
info_lines.push(Line::from(format!(" - {}", port)));
|
||||
for host in &project.hosts {
|
||||
info_lines.push(Line::from(format!("- {}:{}", host.hostname, host.ip)));
|
||||
info_lines.push(Line::from(format!(" - PWNED: {}", host.pwned)));
|
||||
info_lines.push(Line::from(format!(
|
||||
" - CONTROL PORT: {}",
|
||||
host.control_port
|
||||
)));
|
||||
if !host.open_ports.is_empty() {
|
||||
info_lines.push(Line::from(format!(" - PORTS:")));
|
||||
for port in &host.open_ports {
|
||||
info_lines.push(Line::from(format!(" - {}", port)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !host.users.is_empty() {
|
||||
info_lines.push(Line::from(format!(" - USERS:")));
|
||||
for user in &host.users {
|
||||
info_lines.push(Line::from(format!(
|
||||
" - {} - PWNED: {}",
|
||||
user.name, user.compromised
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let spacer = format!("{}", "+".repeat(top_chunks[2].width as usize - 2));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
&spacer,
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
"--- Tool Information ---",
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
info_lines.push(Line::from(format!(
|
||||
"CONFIG FILE: {}",
|
||||
state.config_file.display()
|
||||
)));
|
||||
for setting in state.config.keys() {
|
||||
info_lines.push(Line::from(format!(
|
||||
"{}: {}",
|
||||
setting.to_uppercase(),
|
||||
state.config.get(setting).unwrap()
|
||||
)));
|
||||
}
|
||||
if state.progress_bars.is_empty() {
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize > info_lines.len().saturating_sub(info_area_height) {
|
||||
state.info_scroll = info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, top_chunks[2]);
|
||||
} else {
|
||||
let progress_section = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
|
||||
.split(top_chunks[2]);
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize > info_lines.len().saturating_sub(info_area_height) {
|
||||
state.info_scroll = info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, progress_section[0]);
|
||||
let mut progress_lines = Vec::new();
|
||||
let mut color_differnt = false;
|
||||
state.progress_bars.iter().for_each(|progress| {
|
||||
if let Ok(status) = progress.read() {
|
||||
let percentage = status.complete / status.total * 100;
|
||||
if percentage == 100 {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
} else {
|
||||
match color_differnt {
|
||||
true => {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Cyan),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Cyan),
|
||||
)));
|
||||
color_differnt = false;
|
||||
}
|
||||
false => {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Magenta),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Magenta),
|
||||
)));
|
||||
color_differnt = true;
|
||||
}
|
||||
if !host.users.is_empty() {
|
||||
info_lines.push(Line::from(format!(" - USERS:")));
|
||||
for user in &host.users {
|
||||
info_lines.push(Line::from(format!(
|
||||
" - {} - PWNED: {}",
|
||||
user.name, user.compromised
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let spacer = format!("{}", "+".repeat(top_chunks[2].width as usize - 2));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
&spacer,
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
info_lines.push(Line::from(Span::styled(
|
||||
"--- Tool Information ---",
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
info_lines.push(Line::from(format!(
|
||||
"CONFIG FILE: {}",
|
||||
state.config_file.display()
|
||||
)));
|
||||
for setting in state.config.keys() {
|
||||
info_lines.push(Line::from(format!(
|
||||
"{}: {}",
|
||||
setting.to_uppercase(),
|
||||
state.config.get(setting).unwrap()
|
||||
)));
|
||||
}
|
||||
if state.progress_bars.is_empty() {
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize
|
||||
> info_lines.len().saturating_sub(info_area_height)
|
||||
{
|
||||
state.info_scroll =
|
||||
info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, top_chunks[2]);
|
||||
} else {
|
||||
let progress_section = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
|
||||
.split(top_chunks[2]);
|
||||
let info_area_height = top_chunks[2].height.saturating_sub(2) as usize;
|
||||
if state.info_scroll as usize
|
||||
> info_lines.len().saturating_sub(info_area_height)
|
||||
{
|
||||
state.info_scroll =
|
||||
info_lines.len().saturating_sub(info_area_height) as u16;
|
||||
}
|
||||
let info_paragraph = Paragraph::new(info_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Selected Project Information"),
|
||||
)
|
||||
.scroll((state.info_scroll, 0))
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
f.render_widget(info_paragraph, progress_section[0]);
|
||||
let mut progress_lines = Vec::new();
|
||||
let mut color_differnt = false;
|
||||
state.progress_bars.iter().for_each(|progress| {
|
||||
if let Ok(status) = progress.read() {
|
||||
let percentage = status.complete / status.total * 100;
|
||||
if percentage == 100 {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Green),
|
||||
)));
|
||||
} else {
|
||||
match color_differnt {
|
||||
true => {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Cyan),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Cyan),
|
||||
)));
|
||||
color_differnt = false;
|
||||
}
|
||||
false => {
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!("{}:)", status.name),
|
||||
Style::default().fg(Color::Magenta),
|
||||
)));
|
||||
progress_lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" {}/{} ({}%)",
|
||||
status.complete, status.total, percentage
|
||||
),
|
||||
Style::default().fg(Color::Magenta),
|
||||
)));
|
||||
color_differnt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let progress_paragraph = Paragraph::new(progress_lines).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Operations in progress"),
|
||||
);
|
||||
f.render_widget(progress_paragraph, progress_section[1]);
|
||||
}
|
||||
let mut output_lines = Vec::new();
|
||||
state.output.iter().for_each(|text| {
|
||||
let line = Line::from(Span::raw(text));
|
||||
if text.to_lowercase().contains("error") {
|
||||
let line = line.style(Style::default().fg(Color::Red));
|
||||
output_lines.push(line);
|
||||
} else if text.to_lowercase().contains("success") {
|
||||
let line = line.style(Style::default().fg(Color::Green));
|
||||
output_lines.push(line);
|
||||
} else {
|
||||
output_lines.push(line);
|
||||
}
|
||||
});
|
||||
let progress_paragraph = Paragraph::new(progress_lines).block(
|
||||
let text_area_height = top_chunks[1].height.saturating_sub(2) as usize;
|
||||
let max_scroll = state.output.len().saturating_sub(text_area_height);
|
||||
if state.output_follow {
|
||||
state.output_scroll = max_scroll as u16;
|
||||
}
|
||||
state.output_scroll = state.output_scroll.min(max_scroll as u16);
|
||||
if state.output_scroll == u16::MAX {
|
||||
if state.output.len() > text_area_height {
|
||||
state.output_scroll = (state.output.len() - text_area_height) as u16;
|
||||
} else {
|
||||
state.output_scroll = 0;
|
||||
}
|
||||
}
|
||||
let output_paragraph = Paragraph::new(output_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Script Engine Output "),
|
||||
)
|
||||
.scroll((state.output_scroll, 0));
|
||||
|
||||
f.render_widget(output_paragraph, top_chunks[1]);
|
||||
let input_paragraph = Paragraph::new(state.curent_intput.as_str()).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Operations in progress"),
|
||||
.title(" What is thy bidding, my master? "),
|
||||
);
|
||||
f.render_widget(progress_paragraph, progress_section[1]);
|
||||
}
|
||||
let mut output_lines = Vec::new();
|
||||
state.output.iter().for_each(|text| {
|
||||
let line = Line::from(Span::raw(text));
|
||||
if text.to_lowercase().contains("error") {
|
||||
let line = line.style(Style::default().fg(Color::Red));
|
||||
output_lines.push(line);
|
||||
} else if text.to_lowercase().contains("success") {
|
||||
let line = line.style(Style::default().fg(Color::Green));
|
||||
output_lines.push(line);
|
||||
} else {
|
||||
output_lines.push(line);
|
||||
}
|
||||
});
|
||||
let text_area_height = top_chunks[1].height.saturating_sub(2) as usize;
|
||||
let max_scroll = state.output.len().saturating_sub(text_area_height);
|
||||
if state.output_follow {
|
||||
state.output_scroll = max_scroll as u16;
|
||||
}
|
||||
state.output_scroll = state.output_scroll.min(max_scroll as u16);
|
||||
if state.output_scroll == u16::MAX {
|
||||
if state.output.len() > text_area_height {
|
||||
state.output_scroll = (state.output.len() - text_area_height) as u16;
|
||||
} else {
|
||||
state.output_scroll = 0;
|
||||
}
|
||||
}
|
||||
let output_paragraph = Paragraph::new(output_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Script Engine Output "),
|
||||
)
|
||||
.scroll((state.output_scroll, 0));
|
||||
|
||||
f.render_widget(output_paragraph, top_chunks[1]);
|
||||
let input_paragraph = Paragraph::new(state.curent_intput.as_str()).block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" What is thy bidding, my master? "),
|
||||
);
|
||||
|
||||
f.render_widget(input_paragraph, main_chunks[1]);
|
||||
})?;
|
||||
f.render_widget(input_paragraph, main_chunks[1]);
|
||||
})?;
|
||||
}
|
||||
if let Ok(event) = event_rx.recv_timeout(Duration::from_millis(100)) {
|
||||
match event {
|
||||
AppEvent::Worker(msg) => match msg {
|
||||
@@ -697,134 +747,152 @@ pub fn run_tui(
|
||||
break;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let trimmed = state.curent_intput.trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
state.history.push(trimmed.clone());
|
||||
let _ = state
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((0, "\n".to_string())));
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("[user input] > {}", trimmed),
|
||||
)));
|
||||
let _ = state
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((0, "\n".to_string())));
|
||||
let prompt = state.prompt.clone();
|
||||
if prompt.action.is_some() {
|
||||
state.prompt.responses.push(PromptResponse {
|
||||
query: prompt.last_prompted.clone().unwrap(),
|
||||
response: trimmed.clone(),
|
||||
});
|
||||
if state.prompt.responses.len() == state.prompt.num_responses {
|
||||
state.execute_command(
|
||||
prompt.execute_command.as_str(),
|
||||
None,
|
||||
0,
|
||||
)?;
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("{}", prompt.execute_command),
|
||||
)));
|
||||
} else {
|
||||
state.execute_command("prompt", None, 0)?;
|
||||
}
|
||||
} else {
|
||||
let (command, args) =
|
||||
trimmed.split_once(' ').unwrap_or((&trimmed, ""));
|
||||
match command {
|
||||
"exit" | "quit" => break,
|
||||
"reload-modules" => {
|
||||
state.initialize_modules();
|
||||
if state.selecting_host {
|
||||
state.selecting_host = false;
|
||||
} else {
|
||||
let trimmed = state.curent_intput.trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
state.history.push(trimmed.clone());
|
||||
let _ = state
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((0, "\n".to_string())));
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("[user input] > {}", trimmed),
|
||||
)));
|
||||
let _ = state
|
||||
.main_tx
|
||||
.send(ToolMessage::Output((0, "\n".to_string())));
|
||||
let prompt = state.prompt.clone();
|
||||
if prompt.action.is_some() {
|
||||
state.prompt.responses.push(PromptResponse {
|
||||
query: prompt.last_prompted.clone().unwrap(),
|
||||
response: trimmed.clone(),
|
||||
});
|
||||
if state.prompt.responses.len()
|
||||
== state.prompt.num_responses
|
||||
{
|
||||
state.execute_command(
|
||||
prompt.execute_command.as_str(),
|
||||
None,
|
||||
0,
|
||||
)?;
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
"Reloading Module Paths...".to_string(),
|
||||
format!("{}", prompt.execute_command),
|
||||
)));
|
||||
} else {
|
||||
state.execute_command("prompt", None, 0)?;
|
||||
}
|
||||
"help" => {
|
||||
let help_text = state.help.clone().join("\n");
|
||||
for line in help_text.lines() {
|
||||
} else {
|
||||
let (command, args) =
|
||||
trimmed.split_once(' ').unwrap_or((&trimmed, ""));
|
||||
match command {
|
||||
"exit" | "quit" => break,
|
||||
"reload-modules" => {
|
||||
state.initialize_modules();
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
line.to_string(),
|
||||
"Reloading Module Paths...".to_string(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
"new_project" | "np" => {
|
||||
if args.split_once(' ').is_some() {
|
||||
let _ = state.execute_command(
|
||||
command,
|
||||
Some(args.to_string()),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
state.output.push(
|
||||
"[error]: USAGE -> np <org> <name>".into(),
|
||||
);
|
||||
"help" => {
|
||||
let help_text = state.help.clone().join("\n");
|
||||
for line in help_text.lines() {
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((0, line.to_string())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"switch_config" => {
|
||||
switch_config = true;
|
||||
state.curent_intput.clear();
|
||||
break;
|
||||
}
|
||||
command_name => {
|
||||
if state.module_loader.commands.contains_key(command) {
|
||||
let _ = state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!(
|
||||
"[Worker] Executing script {}...",
|
||||
command_name
|
||||
),
|
||||
)));
|
||||
if let Err(e) = state.execute_command(
|
||||
command,
|
||||
Some(args.to_string()),
|
||||
0,
|
||||
) {
|
||||
"new_project" | "np" => {
|
||||
if args.split_once(' ').is_some() {
|
||||
let _ = state.execute_command(
|
||||
command,
|
||||
Some(args.to_string()),
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
state.output.push(
|
||||
"[error]: USAGE -> np <org> <name>".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
"switch_config" => {
|
||||
switch_config = true;
|
||||
state.curent_intput.clear();
|
||||
break;
|
||||
}
|
||||
"select_host" => {
|
||||
state.selecting_host = true;
|
||||
}
|
||||
command_name => {
|
||||
if state
|
||||
.module_loader
|
||||
.commands
|
||||
.contains_key(command)
|
||||
{
|
||||
let _ =
|
||||
state.main_tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("[error] Pipeline faile: {e}"),
|
||||
format!(
|
||||
"[Worker] Executing script {}...",
|
||||
command_name
|
||||
),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
if args == "" {
|
||||
match state.execute_command(command, None, 0) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((
|
||||
0,
|
||||
e.to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match state.execute_command(
|
||||
if let Err(e) = state.execute_command(
|
||||
command,
|
||||
Some(args.to_string()),
|
||||
0,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((
|
||||
0,
|
||||
e.to_string(),
|
||||
)),
|
||||
);
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((
|
||||
0,
|
||||
format!(
|
||||
"[error] Pipeline faile: {e}"
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if args == "" {
|
||||
match state
|
||||
.execute_command(command, None, 0)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((
|
||||
0,
|
||||
e.to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match state.execute_command(
|
||||
command,
|
||||
Some(args.to_string()),
|
||||
0,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let _ = state.main_tx.send(
|
||||
ToolMessage::Output((
|
||||
0,
|
||||
e.to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state.curent_intput.clear();
|
||||
}
|
||||
state.curent_intput.clear();
|
||||
history_index = state.history.len();
|
||||
}
|
||||
history_index = state.history.len();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
state.curent_intput.push(c);
|
||||
@@ -833,18 +901,40 @@ pub fn run_tui(
|
||||
state.curent_intput.pop();
|
||||
}
|
||||
KeyCode::Up if key.modifiers.is_empty() => {
|
||||
if !state.history.is_empty() && history_index > 0 {
|
||||
history_index -= 1;
|
||||
state.curent_intput = state.history[history_index].clone();
|
||||
if state.selecting_host {
|
||||
if let Some(selected) = host_list_state.selected() {
|
||||
if selected > 0 {
|
||||
let new_index = selected - 1;
|
||||
host_list_state.select(Some(new_index));
|
||||
state.selected_host = new_index;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !state.history.is_empty() && history_index > 0 {
|
||||
history_index -= 1;
|
||||
state.curent_intput = state.history[history_index].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Down if key.modifiers.is_empty() => {
|
||||
if history_index < state.history.len() {
|
||||
history_index += 1;
|
||||
if history_index == state.history.len() {
|
||||
state.curent_intput.clear(); // Clear back to a fresh prompt
|
||||
} else {
|
||||
state.curent_intput = state.history[history_index].clone();
|
||||
if state.selecting_host {
|
||||
if let Some(selected) = host_list_state.selected() {
|
||||
if selected + 1
|
||||
< state.projects[state.selected_project].hosts.len()
|
||||
{
|
||||
let new_index = selected + 1;
|
||||
host_list_state.select(Some(new_index));
|
||||
state.selected_host = new_index;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if history_index < state.history.len() {
|
||||
history_index += 1;
|
||||
if history_index == state.history.len() {
|
||||
state.curent_intput.clear(); // Clear back to a fresh prompt
|
||||
} else {
|
||||
state.curent_intput = state.history[history_index].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+234
-1
@@ -9,6 +9,9 @@ use rayon::prelude::*;
|
||||
use rhai::{AST, Dynamic, Engine, Scope};
|
||||
use rust_tools::{BusterTarget, rustbuster};
|
||||
use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, pki_types::ServerName};
|
||||
use serde_derive::Deserialize;
|
||||
use serde_json;
|
||||
use shodan_rust::ShodanClient;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
@@ -23,6 +26,7 @@ use std::process::{Command, Stdio};
|
||||
use std::sync::RwLock;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::{self, Instant};
|
||||
use walkdir::WalkDir;
|
||||
@@ -91,6 +95,7 @@ pub struct AppState {
|
||||
pub output: Vec<String>,
|
||||
pub selected_server: usize,
|
||||
pub selected_project: usize,
|
||||
pub selected_host: usize,
|
||||
pub curent_intput: String,
|
||||
pub module_loader: ModuleLoader,
|
||||
pub output_scroll: u16,
|
||||
@@ -103,6 +108,7 @@ pub struct AppState {
|
||||
pub help: Vec<String>,
|
||||
pub name: String,
|
||||
pub progress_bars: Vec<Arc<RwLock<ToolProgress>>>,
|
||||
pub selecting_host: bool,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -124,6 +130,7 @@ impl AppState {
|
||||
output: Vec::new(),
|
||||
selected_server: 0,
|
||||
selected_project: 0,
|
||||
selected_host: 0,
|
||||
curent_intput: String::new(),
|
||||
module_loader: ModuleLoader::new(),
|
||||
output_scroll: 0,
|
||||
@@ -143,6 +150,7 @@ impl AppState {
|
||||
help: Vec::new(),
|
||||
name: String::new(),
|
||||
progress_bars: Vec::new(),
|
||||
selecting_host: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +231,47 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut server_folder_path = self.config_file.clone();
|
||||
server_folder_path.pop();
|
||||
server_folder_path.push("client_servers");
|
||||
if server_folder_path.exists() {
|
||||
let server_confs = read_dir(server_folder_path)?;
|
||||
server_confs.into_iter().for_each(|entry| {
|
||||
if let Ok(entry) = entry {
|
||||
if let Ok(text) = read_to_string(entry.path()) {
|
||||
let mut name = String::new();
|
||||
let mut address = String::new();
|
||||
let mut cert = String::new();
|
||||
text.lines().into_iter().for_each(|line| {
|
||||
if let Some((setting, data)) = line.split_once(": ") {
|
||||
match setting.trim() {
|
||||
"name" => name = data.trim().to_string(),
|
||||
"address" => address = data.trim().to_string(),
|
||||
"cert" => cert = data.trim().to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
let new_server = Server {
|
||||
address,
|
||||
connected: false,
|
||||
timer: Duration::from_secs(90),
|
||||
config: None,
|
||||
last_check: Instant::now(),
|
||||
message_que: Vec::new(),
|
||||
action_que: Vec::new(),
|
||||
client_id: 0,
|
||||
selected_client: 0,
|
||||
logged_in: false,
|
||||
password: String::new(),
|
||||
name,
|
||||
cert_text: cert,
|
||||
};
|
||||
self.servers.push(Arc::new(Mutex::new(new_server)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
self.help.push("help\nThat's MEEEEEEE\n".to_string());
|
||||
self.help.push("new_project\nadd a project to the upcoming project pool and create default notes based on templates\nusage:\nnew_project organization_name, project_name\n".to_string());
|
||||
self.help.push(
|
||||
@@ -260,6 +309,20 @@ impl AppState {
|
||||
self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist\n".to_string());
|
||||
self.help
|
||||
.push("clear_operations\nClear the completed operation statuses.\n".to_string());
|
||||
self.help
|
||||
.push("shodan\nGet the shodan info for the currently selected host\n".to_string());
|
||||
self.help
|
||||
.push("select_host\nSelect a host in the current project.\n".to_string());
|
||||
self.help.push(
|
||||
"shodan_all\nGet the shodan info for all hosts in the current project.\n".to_string(),
|
||||
);
|
||||
self.help.push(
|
||||
"set_shodan_key\nSet your shodan api key, and save it to your system's keyring.\n"
|
||||
.to_string(),
|
||||
);
|
||||
self.help.push(
|
||||
"save_server\nsave the server and password to your system's keyring.\n".to_string(),
|
||||
);
|
||||
self.help.push("exit\nquit the tool\n".to_string());
|
||||
self.initialize_modules();
|
||||
return Ok(());
|
||||
@@ -1362,6 +1425,107 @@ impl AppState {
|
||||
!lock.finished
|
||||
});
|
||||
}
|
||||
"shodan" => {
|
||||
if let Some(project) = self.projects.get(self.selected_project) {
|
||||
if let Some(host) = project.hosts.get(self.selected_host) {
|
||||
if let Ok(entry) = Entry::new("tetanus", "shodan_key") {
|
||||
if let Ok(key) = entry.get_password() {
|
||||
let mut host_clone = host.clone();
|
||||
let tx_clone = self.main_tx.clone();
|
||||
thread::spawn(move || host_clone.get_shodan(key, tx_clone));
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't get shodan key from keyring!"),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't find shodan key in keyring!"),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"shodan_all" => {
|
||||
if let Some(project) = self.projects.get(self.selected_project) {
|
||||
if !project.hosts.is_empty() {
|
||||
if let Ok(entry) = Entry::new("tetanus", "shodan_key") {
|
||||
if let Ok(key) = entry.get_password() {
|
||||
project.hosts.iter().for_each(|h| {
|
||||
let mut host_clone = h.clone();
|
||||
let tx_clone = self.main_tx.clone();
|
||||
let key_clone = key.clone();
|
||||
thread::spawn(move || {
|
||||
host_clone.get_shodan(key_clone, tx_clone)
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't get shodan key from keyring!"),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't find shodan key in keyring!"),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"set_shodan_key" => {
|
||||
if let Some(args) = command_args {
|
||||
if let Ok(_) = Entry::store_status() {
|
||||
if let Ok(entry) = Entry::new("tetanus", "shodan_key") {
|
||||
if let Err(e) = entry.set_password(args.trim()) {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't set shodan apikey!: {e}"),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] couldn't create new entry!"),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] store status bad!"),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
"save_server" => {
|
||||
if let Some(server) = self.servers.get(self.selected_server) {
|
||||
let mut path = self.config_file.clone();
|
||||
path.pop();
|
||||
path.push("client_servers");
|
||||
if !path.exists() {
|
||||
create_dir_all(&path).unwrap();
|
||||
}
|
||||
if let Ok(mut lock) = server.lock() {
|
||||
match lock.save(path) {
|
||||
Ok(_) => {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[success] {} saved!", lock.address),
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = self.main_tx.send(ToolMessage::Output((
|
||||
rid,
|
||||
format!("[error] saving server: {e}"),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"exit" => {
|
||||
self.save_all()?;
|
||||
let _ = self.main_tx.send(ToolMessage::AppStateExit);
|
||||
@@ -2352,6 +2516,10 @@ impl Server {
|
||||
}
|
||||
self.connected = true;
|
||||
self.last_check = Instant::now();
|
||||
let entry = Entry::new("tetanus", &self.address)?;
|
||||
if let Ok(password) = entry.get_password() {
|
||||
self.login(password)?;
|
||||
}
|
||||
return Ok(self.client_id);
|
||||
}
|
||||
|
||||
@@ -2433,6 +2601,27 @@ impl Server {
|
||||
self.connected = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save(&mut self, mut path: PathBuf) -> Result<(), Box<dyn Error>> {
|
||||
path.push(format!("{}.conf", self.address));
|
||||
let mut conf_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
let outdata = format!(
|
||||
"
|
||||
name: {}
|
||||
address: {}
|
||||
cert: {}
|
||||
",
|
||||
self.name, self.address, self.cert_text
|
||||
);
|
||||
write!(conf_file, "{}", outdata)?;
|
||||
let entry = Entry::new("tetanus", &self.address)?;
|
||||
entry.set_password(&self.password)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -2698,6 +2887,7 @@ pub struct Host {
|
||||
pub pwned: bool,
|
||||
pub id: usize,
|
||||
pub findings: Vec<String>,
|
||||
pub shodan_data: Option<ShodanData>,
|
||||
}
|
||||
|
||||
impl Host {
|
||||
@@ -2711,6 +2901,7 @@ impl Host {
|
||||
pwned: false,
|
||||
id: 0,
|
||||
findings: Vec::new(),
|
||||
shodan_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2812,6 +3003,41 @@ id: {}",
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub fn get_shodan(&mut self, key: String, tx: Sender<ToolMessage>) {
|
||||
let client = ShodanClient::new(key);
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
runtime.block_on(async {
|
||||
if let Ok(data) = client.host_info(&self.ip).await {
|
||||
if let Ok(new_data) = serde_json::from_str::<ShodanData>(&data.to_string()) {
|
||||
if let Some(ports) = new_data.ports {
|
||||
ports.iter().for_each(|p| {
|
||||
self.add_port(&p.to_string());
|
||||
});
|
||||
}
|
||||
if let Some(vulns) = new_data.vulns {
|
||||
vulns.iter().for_each(|v| {
|
||||
self.findings.push(format!("shodan: {}", v));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let _ = tx.send(ToolMessage::Output((
|
||||
0,
|
||||
"[error] parsing shodand data!".to_string(),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
let _ = tx.send(ToolMessage::Output((
|
||||
0,
|
||||
"[error] getting shodand data!".to_string(),
|
||||
)));
|
||||
}
|
||||
});
|
||||
let _ = tx.send(ToolMessage::Output((
|
||||
0,
|
||||
format!("shodan_function finished for {}", self.ip),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -3321,9 +3547,16 @@ struct PromptQuery {
|
||||
query: String,
|
||||
}
|
||||
|
||||
struct ToolProgress {
|
||||
pub struct ToolProgress {
|
||||
name: String,
|
||||
total: usize,
|
||||
complete: usize,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default, Clone)]
|
||||
pub struct ShodanData {
|
||||
pub ip_str: Option<String>,
|
||||
pub vulns: Option<Vec<String>>,
|
||||
pub ports: Option<Vec<i64>>,
|
||||
}
|
||||
|
||||
+10
@@ -1,4 +1,5 @@
|
||||
use clap::Parser;
|
||||
use keyring::Entry;
|
||||
use rand::RngExt;
|
||||
use rand::distr::Alphanumeric;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -190,6 +191,15 @@ async fn main() {
|
||||
.collect();
|
||||
if let Some(pass) = args.password {
|
||||
server_pass = pass;
|
||||
} else if let Ok(entry) = Entry::new("tetanus-server", &address) {
|
||||
if let Ok(password) = entry.get_password() {
|
||||
server_pass = password;
|
||||
println!("loaded passwrod from keyring.");
|
||||
} else {
|
||||
if let Err(e) = entry.set_password(&server_pass) {
|
||||
println!("error saving password to keyring! {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let new_server = server::Server {
|
||||
address,
|
||||
|
||||
@@ -153,3 +153,7 @@ pub fn rustbuster(target: BusterTarget) -> Option<String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shodan(ip: String) -> String {
|
||||
return "todo".to_string();
|
||||
}
|
||||
|
||||
+30
-1
@@ -1,3 +1,4 @@
|
||||
use keyring::Entry;
|
||||
use rcgen::generate_simple_self_signed;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
@@ -53,7 +54,7 @@ pub struct Server {
|
||||
}
|
||||
|
||||
pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Error>> {
|
||||
let lock = server.lock().unwrap();
|
||||
let mut lock = server.lock().unwrap();
|
||||
let mut name_path = lock.certificate_path.clone();
|
||||
name_path.pop();
|
||||
name_path.push("names.txt");
|
||||
@@ -90,6 +91,34 @@ pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Erro
|
||||
let cert = generate_simple_self_signed(current_config_names)?;
|
||||
write(&lock.certificate_path, cert.cert.pem())?;
|
||||
write(&lock.key_path, cert.signing_key.serialize_pem())?;
|
||||
println!("attempting to load saved password...");
|
||||
if let Ok(entry) = Entry::new("tetanus-server", &lock.address) {
|
||||
if let Ok(password) = entry.get_password() {
|
||||
lock.password = password;
|
||||
println!("password loaded from keyring!");
|
||||
} else {
|
||||
println!("error getting password, using randomly generated one.");
|
||||
if let Err(e) = entry.set_password(&lock.password) {
|
||||
println!("error saving generated password! {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("error creating entry, using randomly generated password.");
|
||||
}
|
||||
}
|
||||
println!("attempting to load saved password...");
|
||||
if let Ok(entry) = Entry::new("tetanus-server", &lock.address) {
|
||||
if let Ok(password) = entry.get_password() {
|
||||
lock.password = password;
|
||||
println!("password loaded from keyring!");
|
||||
} else {
|
||||
println!("error getting password, using randomly generated one.");
|
||||
if let Err(e) = entry.set_password(&lock.password) {
|
||||
println!("error saving generated password! {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("error creating entry, using randomly generated password.");
|
||||
}
|
||||
let certs = load_certs(&lock.certificate_path)?;
|
||||
let key = load_key(&lock.key_path)?;
|
||||
|
||||
Reference in New Issue
Block a user