ntdsextract2 1.4.22

Display contents of Active Directory database files (ntds.dit)
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread::spawn;

use cursive::view::{Nameable, Resizable, Scrollable};
use cursive::views::Panel;
use cursive::views::{LinearLayout, NamedView, ResizedView, ScrollView};
use cursive::Cursive;
use cursive_table_view::TableView;
use cursive_tree_view::Placement;
use cursive_tree_view::TreeView;
use libntdsextract2::{CDatabase, EntryId, ObjectTreeEntry};

mod attribute_column;
mod attribute_line;
mod tree_entry;

use attribute_column::*;
use attribute_line::*;
use tree_entry::*;

const TREE: &str = "tree_view";
const ATTRIBUTES: &str = "attributes_view";
const LEFT_PANEL: &str = "left_panel";
const RIGHT_PANEL: &str = "right_panel";
const LAYOUT: &str = "layout";

pub fn run_tui<'db, 'info>(database: CDatabase<'db, 'info>) -> anyhow::Result<()> {
    let mut tree_view = TreeView::new();
    insert_item(&mut tree_view, Arc::clone(database.object_tree().root()), 0);

    // because CDatabase is neither Send nor Sync, we leave it in the current
    // thread and move all the UI stuff to other threads
    let (tx_entry_id, rx_entry_id) = channel();
    let (tx_attribs, rx_attribs) = channel();
    let ui_thread = spawn(move || {
        ui_thread_fn(
            tree_view,
            Arc::new(Mutex::new(tx_entry_id)),
            Arc::new(Mutex::new(rx_attribs)),
        )
    });

    // from this point on, the current threads work as a data provider server
    // for the ui thread
    loop {
        match rx_entry_id.recv() {
            Ok(entry_id) => {
                let attributes = if let Ok(Some(entry)) = database.entry(entry_id) {
                    let attributes: Vec<_> = entry
                        .all_attributes()
                        .iter()
                        .map(|(i, a)| AttributeLine::from(i, a))
                        .collect();
                    attributes
                } else {
                    vec![]
                };
                if tx_attribs.send(attributes).is_err() {
                    drop(rx_entry_id);
                    break;
                }
            }
            Err(_) => {
                drop(tx_attribs);
                break;
            }
        }
    }
    let _ = ui_thread.join();
    Ok(())
}

fn insert_item(tree_view: &mut TreeView<TreeEntry>, item: Arc<ObjectTreeEntry>, row: usize) {
    if item.children().lock().unwrap().is_empty() {
        tree_view.insert_item(TreeEntry::from(item), Placement::LastChild, row);
    } else {
        let row = tree_view
            .insert_container_item(
                TreeEntry::from(Arc::clone(&item)),
                Placement::LastChild,
                row,
            )
            .unwrap_or(0);
        // inserting elements in collapsed containers does not work
        tree_view.set_collapsed(row, false);
        for child in item.children().lock().unwrap().iter() {
            insert_item(tree_view, Arc::clone(child), row);
        }
        tree_view.set_collapsed(row, true);
    }
}

fn ui_thread_fn(
    mut tree_view: TreeView<TreeEntry>,
    tx_entry_id: Arc<Mutex<Sender<EntryId>>>,
    rx_attribs: Arc<Mutex<Receiver<Vec<AttributeLine>>>>,
) {
    let mut siv = cursive::default();
    let attributes: TableView<AttributeLine, AttributeColumn> = TableView::new()
        .column(AttributeColumn::Name, "Name", |c| {
            c.align(cursive::align::HAlign::Left).width(33)
        })
        .column(AttributeColumn::Type, "Type", |c| {
            c.align(cursive::align::HAlign::Left).width(12)
        })
        .column(AttributeColumn::Value, "Value", |c| {
            c.align(cursive::align::HAlign::Left)
        })
        .default_column(AttributeColumn::Name);
    siv.add_global_callback('q', |s| s.quit());
    siv.add_global_callback('+', enlarge_tree);
    siv.add_global_callback('-', shrink_tree);

    let tx_entry_id_on_select = Arc::clone(&tx_entry_id);
    let rx_attribs_on_select = Arc::clone(&rx_attribs);
    tree_view.set_on_select(move |siv, row| {
        let tx_entry_id = Arc::clone(&tx_entry_id_on_select);
        let rx_attribs = Arc::clone(&rx_attribs_on_select);
        on_select(siv, row, tx_entry_id, rx_attribs)
    });

    let tree_view_width = 60;
    let ntds_view = LinearLayout::horizontal()
        .child(
            Panel::new(tree_view.with_name(TREE).scrollable())
                .fixed_width(tree_view_width)
                .with_name(LEFT_PANEL),
        )
        .child(Panel::new(attributes.with_name(ATTRIBUTES).full_screen()).with_name(RIGHT_PANEL))
        .with_name(LAYOUT)
        .full_screen();
    siv.set_user_data(tree_view_width);

    siv.add_layer(ntds_view);
    siv.run();
    if let Ok(tx) = tx_entry_id.lock() {
        drop(tx)
    }
    if let Ok(rx) = rx_attribs.lock() {
        drop(rx)
    }
}

fn on_select(
    siv: &mut Cursive,
    row: usize,
    tx_entry_id: Arc<Mutex<Sender<EntryId>>>,
    rx_attribs: Arc<Mutex<Receiver<Vec<AttributeLine>>>>,
) {
    let tree_view = siv.find_name::<TreeView<TreeEntry>>(TREE).unwrap();
    if let Some(entry) = tree_view.borrow_item(row) {
        match tx_entry_id.lock() {
            Ok(tx) => {
                if tx
                    .send(EntryId::Id(*entry.entry().record_ptr().ds_record_id()))
                    .is_err()
                {
                    log::error!("unable to send record ID");
                    siv.quit();
                    return;
                }
            }
            _ => {
                log::error!("unable to acquire sender lock");
                siv.quit();
                return;
            }
        }

        let attribs: Vec<AttributeLine> = match rx_attribs.lock() {
            Ok(rx) => match rx.recv() {
                Ok(attribs) => attribs,
                Err(_) => {
                    siv.quit();
                    return;
                }
            },
            _ => {
                log::error!("unable to acquire receiver lock");
                siv.quit();
                return;
            }
        };

        let mut attributes = siv
            .find_name::<TableView<AttributeLine, AttributeColumn>>(ATTRIBUTES)
            .unwrap();
        //attributes.clear();
        attributes.set_items(attribs);
        attributes.set_selected_row(0);
    }
}

fn enlarge_tree(siv: &mut Cursive) {
    let mut left_panel = siv
        .find_name::<ResizedView<Panel<ScrollView<NamedView<TreeView<TreeEntry>>>>>>(LEFT_PANEL)
        .unwrap();
    siv.with_user_data(move |tree_view_width| {
        *tree_view_width += 1;
        left_panel.set_width(cursive::view::SizeConstraint::Fixed(*tree_view_width));
    });
}

fn shrink_tree(siv: &mut Cursive) {
    let mut left_panel = siv
        .find_name::<ResizedView<Panel<ScrollView<NamedView<TreeView<TreeEntry>>>>>>(LEFT_PANEL)
        .unwrap();
    siv.with_user_data(move |tree_view_width| {
        *tree_view_width -= 1;
        left_panel.set_width(cursive::view::SizeConstraint::Fixed(*tree_view_width));
    });
}