#!/usr/bin/python

import sys, getpass
from ldaptor.protocols.ldap import ldapclient, distinguishedname, ldapconnector, ldapsyntax
from ldaptor.protocols import pureber, pureldap
from ldaptor import usage, generate_password
from twisted.internet import defer, reactor, protocol

class PasswdClient(ldapclient.LDAPClient):
    def connectionMade(self):
        if self.factory.binddn:
            pwd = self.factory.bindPassword
            if pwd is None:
                pwd = getpass.getpass('Password for %s: ' \
                                      % self.factory.binddn)
            d=self.bind(self.factory.binddn, pwd)
        else:
            d=self.bind()
        d.addCallbacks(self._handle_bind_success,
                       self._handle_bind_fail)

    def _report_ldap_error(self, fail):
        print >>sys.stderr, "fail:", fail.getErrorMessage()
        global exitStatus
        exitStatus=1
        return fail

    def getPassword(self, dn):
        if not self.factory.generatePasswords:
            pwd=getpass.getpass('NEW Password for %s: ' % dn)
            return defer.succeed((pwd,))
        else:
            return generate_password.generate(reactor)

    def _handle_bind_success(self, x):
        l=[]
        for dn in self.factory.dnlist:
            d=self.getPassword(dn)
            d.addCallbacks(callback=self._got_password,
                           callbackArgs=(dn,),
                           errback=self._report_pwgen_error)
            l.append(d)
        dl=defer.DeferredList(l)
        dl.addBoth(lambda x, f=self.transport.loseConnection: f())
        dl.addErrback(defer.logError)
        dl.addBoth(lambda x, f=reactor.stop: f())
        return x

    def _handle_bind_fail(self, fail):
        self._report_ldap_error(fail)
        self.transport.loseConnection()
        reactor.stop()

    def _report_new_password(self, dummy, dn, password):
        if self.factory.generatePasswords:
            print dn, password

    def _got_password(self, password, dn):
        assert len(password)==1
        password=password[0]
        o=ldapsyntax.LDAPEntry(client=self, dn=dn)
        d=o.setPassword(newPasswd=password)
        d.addCallbacks(callback=self._report_new_password,
                       callbackArgs=(dn, password),
                       errback=self._report_ldap_error)
        return d

    def _report_pwgen_error(self, fail):
        fail.trap(PwgenException)
        print >>sys.stderr, 'pwgen:', fail.getErrorMessage()
        return fail

class PasswdClientFactory(protocol.ClientFactory):
    protocol = PasswdClient
    def __init__(self, binddn, bindPassword=None,
                 dnlist=(), generatePasswords=0):
        self.binddn = binddn
        self.bindPassword = bindPassword
        self.dnlist=dnlist
        self.generatePasswords=generatePasswords

exitStatus=0

class MyOptions(usage.Options,
                usage.Options_service_location,
                usage.Options_bind_mandatory):
    """LDAPtor command line password change utility"""
    synopsis = "Usage: %s --binddn=DN [OPTION..] [DN..]" % sys.argv[0]
    optFlags = [
        ('generate', None, 'Generate random passwords'),
        ]

    def parseArgs(self, *dnlist):
        if not dnlist:
            dnlist=(self.opts['binddn'],)
        self.opts['dnlist'] = dnlist

if __name__ == "__main__":
    import sys, os
    from twisted.python import log
    log.startLogging(sys.stderr, setStdout=0)

    try:
        config = MyOptions()
        config.parseOptions()
    except usage.UsageError, ue:
        print >>sys.stderr, '%s:'%sys.argv[0], ue
        sys.exit(1)

    bindPassword=None
    if config.opts['bind-auth-fd']:
        f=os.fdopen(config.opts['bind-auth-fd'])
        bindPassword=f.readline()
        assert bindPassword[-1]=='\n'
        bindPassword=bindPassword[:-1]
        f.close()

    s=PasswdClientFactory(dnlist=config.opts['dnlist'],
                          binddn=config.opts['binddn'],
                          bindPassword=bindPassword,
                          generatePasswords=config.opts['generate'],
                          )
    dn = distinguishedname.DistinguishedName(stringValue=config.opts['binddn'])
    c=ldapconnector.LDAPConnector(reactor, dn, s, overrides=config.opts['service-location'])
    c.connect()
    reactor.run()
    sys.exit(exitStatus)
