#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%enable-all-warnings
%new-style
%strict-args
%require-types

# uses the openldap module
%requires openldap

# ensure minimum version of qore
%requires qore >= 0.8.7

main();

const Defaults = (
    "uri": "ldap://localhost:389",
    "filter": "objectClass=*",
    );

const ScopeMap = (
    "base": LDAP_SCOPE_BASE,
    "one": LDAP_SCOPE_ONELEVEL,
    "sub": LDAP_SCOPE_SUBTREE,
    "children": LDAP_SCOPE_CHILDREN,
    );

const opts = (
    # search
    "base": "b,basedn=s",
    "scope": "s,scope=s",

    # common
    "uri": "H,uri=s",
    "binddn": "D,binddn=s",
    "password": "w,passwd=s",
    "promptbind": "W,prompt-bind",
    "info": "i,info",
    "verbose": "v,verbose",
    "timeout": "l,timeout=i",
    "protocol": "P,protocol=i",
    "no-referrals": "r,no-referrals",
    "starttls": "Z,starttls",
    "help": "h,help",
    );

const LdapOptions = ("binddn", "password", "timeout", "protocol", "no-referrals", "starttls");

sub main() {
    # process command-line options
    GetOpt g(opts);
    hash opts = g.parse3(\ARGV);
    if (opts.help)
	usage();

    if (opts.info) {
	printf("%N\n", LdapClient::getInfo());
	exit(0);
    }

    if (opts.scope) {
	*int scope = ScopeMap.(opts.scope);
	if (!exists scope) {
	    stderr.printf("%s: invalid scope %y (expecting one of %y)\n", get_script_name(), opts.scope, ScopeMap.keys());
	    exit(1);
	}
	opts.scope = scope;
    }

    if (ARGV[0]) {
	opts.filter = shift ARGV;
	opts.attributes = ARGV;
    }
    else
	opts.filter = Defaults.filter;

    if (opts.promptbind) {
        stdout.printf("Enter LDAP Bind Password: ");
        TerminalInputHelper t();
        opts.password = t.getLine();
    }

    hash lopt = opts{LdapOptions};
    if (!opts.uri)
	opts.uri = Defaults.uri;

    if (opts.verbose)
        printf("uri: %y, lopt: %y\n", opts.uri, lopt);

    LdapClient ldap(opts.uri, lopt);

    printf("%N\n", ldap.search(opts.("base", "filter", "attributes", "scope")));
}

sub usage() {
    printf("usage: %s [options] filter
Search Options:
  -b,--basedn=ARG    base dn for search
  -s,--scope=ARG     the search scope, one of 'base', 'one', 'sub', or 'children'

Common LDAP Options:
  -D,--binddn=ARG    bind DN
  -H,--uri=ARG       LDAP Uniform Resource Identifier(s)
  -l,--timeout=ARG   set timeout in milliseconds (default: %y)
  -P,--protocol=ARG  set protocol version (default: 3)
  -r,--no-referrals  do not chase referrals
  -v,--verbose       verbose mode; shows more information
  -w,--passwd=ARG    bind password (for simple authentication)
  -W,--prompt-bind   prompt for bind password
  -Z,--starttls      ensure a secure connection

Other Options:
  -i,--info          show ldap library info and exit
  -h,--help          this help text
", get_script_name(), OpenLdap::DefaultTimeout);
    exit(0);
}

class TerminalInputHelper {
    private {
        # saved original terminal attributes
        TermIOS orig = stdin.getTerminalAttributes();

        # input terminal attributes
        TermIOS input;

        # restore flag
        bool rest = False;
    }

    public {}

    constructor() {
        input = orig.copy();

        # get local flags
        int lflag = input.getLFlag();

        # turn on "raw" mode: disable canonical input mode
        lflag &= ~ICANON;

        # turn off echo mode
        lflag &= ~ECHO;

        # set the new local flags
        input.setLFlag(lflag);

        # turn off input buffering: set minimum characters to return on a read
        input.setCC(VMIN, 1);

        # disable input timer: set character input timer to 0.1 second increments
        input.setCC(VTIME, 0);
    }

    destructor() {
        if (rest)
            restore();
    }

    *string getLine() {
        # set input attributes
        stdin.setTerminalAttributes(TCSADRAIN, input);
        rest = True;

        # restore attributes on exit
        on_exit {
            stdin.setTerminalAttributes(TCSADRAIN, orig);
            rest = False;
            stdout.print("\n");
        }

        return stdin.readLine(False);
    }

    restore() {
        # restore terminal attributes
        stdin.setTerminalAttributes(TCSADRAIN, orig);
    }
}
