From f9bd71ba39272caa99069f01068bc12f04ec40de Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Tue, 10 May 2022 00:24:39 +0200 Subject: Initial commit --- imp.ha | 322 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 imp.ha diff --git a/imp.ha b/imp.ha new file mode 100644 index 0000000..57226a7 --- /dev/null +++ b/imp.ha @@ -0,0 +1,322 @@ +use fmt; +use getopt; +use os; +use os::exec; +use io; +use strings; +use bufio; +use format::ini; + +let verbose: bool = false; + +type account = struct { + name: const str, + user: str, + pass: str, + group: str, + notes: str, + url: str, //match +}; + +type filter = struct { + accnames: []str, + urls: []str, + groups: []str, + notes: str, +}; + +export fn main() void = { + //testmap(); + const cmd = getopt::parse(os::args, + "imp", + ('v', "Verbose mode"), + ('l', "List the accounts"), + ('p', "Display the account passwords"), + ('f', "file", "The accounts file to use (IMP_FILE by default)"), + ('g', "groups...", "Filters by a comma-separated list of groups"), + ('m', "matches...", "Filters by a comma-separated list of matches"), + ('n', "note", "Filters by notes containing the given keywords"), + "accounts...", + ); + defer getopt::finish(&cmd); + + let file = match (os::getenv("IMP_FILE")) { + case let f: str => + yield f; + case void => + yield ""; + }; + + let displaypass = false; + + let list = false; + + let accfilter = filter {...}; + + for (let i = 0z; i < len(cmd.opts); i += 1) { + let opt = cmd.opts[i]; + switch (opt.0) { + case 'v' => + verbose = true; + case 'p' => + displaypass = true; + case 'l' => + list = true; + case 'g' => + let spl = strings::split(opt.1, ","); + defer free(spl); + append(accfilter.groups, spl...); + case 'm' => + let spl = strings::split(opt.1, ","); + defer free(spl); + append(accfilter.urls, spl...); + case 'n' => + accfilter.notes = opt.1; + case 'f' => + file = opt.1; + }; + }; + + + for (let i = 0z; i < len(cmd.args); i += 1) { + append(accfilter.accnames, cmd.args[i]); + }; + + if (verbose) { + printfilter(accfilter); + }; + + if (file == "") { + fmt::fatal("No file specified and the IMP_FILE environment variable isn't set"); + }; + + let ini_data = match (decrypt(file)) { + case let s: []u8 => + yield s; + case let e: os::exec::error => + fmt::fatal(os::exec::strerror(e)); + case let e: io::error => + fmt::fatal(io::strerror(e)); + }; + defer free(ini_data); + + let accounts = match (parse(ini_data)) { + case let e: []account => + yield e; + case let e: format::ini::error => + fmt::fatal(format::ini::strerror(e)); + }; + + accounts = accs_filter(accounts, accfilter); + defer accounts_free(accounts); + if (list) { + for (let i = 0z; i < len(accounts); i += 1) { + let acc = accounts[i]; + fmt::println(acc.name)!; + }; + } else { + for (let i = 0z; i < len(accounts); i += 1) { + let acc = accounts[i]; + fmt::printfln("name\t{}", acc.name)!; + fmt::printfln("user\t{}", acc.user)!; + if (displaypass) { + fmt::printfln("pass\t{}", acc.pass)!; + }; + if (len(acc.group) != 0) { + fmt::printfln("group\t{}", acc.group)!; + }; + if (len(acc.url) != 0) { + fmt::printfln("match\t{}", acc.url)!; + }; + if (len(acc.notes) != 0) { + fmt::printfln("notes\t{}", acc.notes)!; + }; + if (i != len(accounts) - 1) { + fmt::println()!; + }; + }; + }; +}; + +fn printfilter(f: filter) void = { + fmt::fprintln(os::stderr, "printing filter:")!; + + fmt::fprint(os::stderr, "accs: ")!; + let accnames = strings::join(", ", f.accnames...); + defer free(accnames); + fmt::fprintln(os::stderr, accnames)!; + + fmt::fprint(os::stderr, "groups: ")!; + let groups = strings::join(", ", f.groups...); + defer free(groups); + fmt::fprintln(os::stderr, groups)!; + + fmt::fprint(os::stderr, "urls: ")!; + let urls = strings::join(", ", f.urls...); + defer free(urls); + fmt::fprintln(os::stderr, urls)!; + + fmt::fprintfln(os::stderr, "notes: {}", f.notes)!; +}; + +fn isemptyfilter(f: filter) bool = { + return len(f.accnames) == 0 && + len(f.groups) == 0 && + len(f.urls) == 0 && + len(f.notes) == 0; +}; + +fn isfiltered(acc: account, f: filter) bool = { + for (let i = 0z; i < len(f.accnames); i += 1) { + if (strings::compare(f.accnames[i], acc.name) == 0) { + return true; + }; + }; + for (let i = 0z; i < len(f.groups); i += 1) { + if (strings::compare(f.groups[i], acc.group) == 0) { + return true; + }; + }; + for (let i = 0z; i < len(f.urls); i += 1) { + if (strings::compare(f.urls[i], acc.url) == 0) { + return true; + }; + }; + if (len(f.notes) != 0 && strings::contains(acc.notes, f.notes)) { + return true; + }; + return false; +}; + +fn accs_filter(accounts: []account, accfilter: filter) []account = { + if (isemptyfilter(accfilter)) { + return accounts; + }; + if (verbose) { + fmt::fprintln(os::stderr, "filter not empty")!; + }; + let resaccs: []account = []; + for (let i = 0z; i < len(accounts); i += 1) { + let acc = accounts[i]; + if (isfiltered(acc, accfilter)) { + append(resaccs, acc); + }; + }; + return resaccs; +}; + +fn accounts_free(accounts: []account) void = { + for (let i = 0z; i < len(accounts); i += 1) { + account_free(accounts[i]); + }; + free(accounts); +}; + +fn account_free(acc: account) void = { + free(acc.name); + if (len(acc.pass) != 0) { + free(&acc.pass); + }; + if (len(acc.url) != 0) { + free(&acc.url); + }; + if (len(acc.notes) != 0) { + free(&acc.notes); + }; + free(acc.group); +}; + +fn parse(data: []u8) ([]account | format::ini::error) = { + let scanner = format::ini::scan(&bufio::fixed(data, io::mode::READ)); + defer format::ini::finish(&scanner); + + //let entries: []format::ini::entry = []; + let accounts: []account = []; + + for (true) { + let entry = match (format::ini::next(&scanner)?) { + case let e: format::ini::entry => + yield e; + case io::EOF => + break; + }; + match (lookupaccount(accounts, entry.0)) { + case void => + let name = strings::dup(entry.0); + let spl = strings::split(name, "/"); + defer free(spl); + let group = ""; + if (len(spl) > 1) { + group = strings::join("/", spl[..len(spl) - 1]...); + }; + let acc = account { + name = name, + group = group, + ... + }; + setfieldaccount(&acc, strings::dup(entry.1), strings::dup(entry.2)); + append(accounts, acc); + case let acc: *account => + setfieldaccount(acc, strings::dup(entry.1), strings::dup(entry.2)); + }; + }; + return accounts; +}; + +fn setfieldaccount(acc: *account, key: str, val: str) void = { + key = strings::trim(key, ' '); + defer free(key); + val = strings::trim(val, ' '); + //defer free(val); + + // Handle quoted values + let valrunes = strings::runes(val); + defer free(valrunes); + if (valrunes[0] == '"' && valrunes[len(valrunes)-1] == '"') { + val = strings::trim(val, '"'); + }; + + if (strings::compare(key, "user") == 0) { + acc.user = val; + } else if (strings::compare(key, "pass") == 0) { + acc.pass = val; + } else if (strings::compare(key, "notes") == 0) { + acc.notes = val; + } else if (strings::compare(key, "match") == 0) { + acc.url = val; + } else { + free(&val); + }; +}; + +fn lookupaccount(accounts: []account, name: str) (*account | void) = { + for (let i = 0z; i < len(accounts); i += 1) { + if (accounts[i].name == name) + return &accounts[i]; + }; + return; +}; + +fn decrypt(file: str) ([]u8 | os::exec::error | io::error) = { + let cmd = os::exec::cmd("gpg", "-d", file)?; + let pipe_stdout = os::exec::pipe(); + + os::exec::addfile(&cmd, os::stdout_file, pipe_stdout.1); + os::exec::addfile(&cmd, os::stderr, os::exec::nullfd); + + let proc = os::exec::start(&cmd)?; + io::close(pipe_stdout.1)?; + + let out = io::drain(pipe_stdout.0)?; + io::close(pipe_stdout.0)?; + + let status = os::exec::wait(&proc)?; + match (os::exec::check(&status)) { + case let e: os::exec::exit_status => + fmt::fatal(os::exec::exitstr(e)); + case void => + yield; + }; + + return out; +}; -- cgit v1.2.3