use fmt; use getopt; use os; use os::exec; use io; use strings; use bufio; use format::ini; use strio; 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 { if (displaypass) { let st = strio::dynamic(); defer io::close(&st)!; for (let i = 0z; i < len(accounts); i += 1) { const facc = format(accounts[i]); defer free(facc); strio::concat(&st, "key ", facc, "\n")!; }; strio::concat(&st, "prompt disclose")!; if (!prompt(strio::string(&st))!) { fmt::fatal("Request denied"); }; }; 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::hasprefix(acc.group, f.groups[i])) { return true; }; }; for (let i = 0z; i < len(f.urls); i += 1) { const url1 = strings::rtrim(f.urls[i], '/'); const url2 = strings::rtrim(acc.url, '/'); if (strings::compare(url1, url2) == 0) { return true; }; }; if (len(f.notes) != 0 && strings::contains(acc.notes, f.notes)) { return true; }; return false; }; fn prompt(in: str) (bool | os::exec::error | io::error) = { let cmd = os::exec::cmd("hiprompt-gtk")?; let pipe_stdin = exec::pipe(); let pipe_stdout = exec::pipe(); os::exec::addfile(&cmd, os::stdin_file, pipe_stdin.0); os::exec::addfile(&cmd, os::stdout_file, pipe_stdout.1); fmt::fprintln(os::stderr, "version")!; fmt::fprintln(os::stderr, in)!; let proc = os::exec::start(&cmd)?; //io::writeall(pipe_stdin.1, strings::toutf8(in))?; io::close(pipe_stdin.0)?; io::close(pipe_stdout.0)?; io::close(pipe_stdout.1)!; fmt::fprintln(pipe_stdin.1, in)?; io::close(pipe_stdin.1)!; //io::close(pipe_stderr.0)?; //io::close(pipe_stderr.1)!; let status = os::exec::wait(&proc)?; match (os::exec::check(&status)) { case let e: !os::exec::exit_status => fmt::fprintln(os::stderr, exec::exitstr(e))!; return false; case void => return true; }; }; // Return needs to be freed fn format(acc: account) str = { //return fmt::asprintf("url={} user={} pass!={}", acc.url, acc.user, acc.pass); return fmt::asprintf("user={} pass!={}", acc.user, acc.pass); }; 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; };