summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Hurst <julian.hurst92@gmail.com>2022-05-10 00:24:39 +0200
committerJulian Hurst <julian.hurst92@gmail.com>2022-05-10 00:24:39 +0200
commitf9bd71ba39272caa99069f01068bc12f04ec40de (patch)
treec9d7d8cf9e61b9f9e56e79c03de8366a31d88b33
downloadimp-f9bd71ba39272caa99069f01068bc12f04ec40de.tar.gz
Initial commit
-rw-r--r--imp.ha322
1 files changed, 322 insertions, 0 deletions
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;
+};