# -*- coding: utf-8 -*-
"""w2lapp.handler.py: base handler

web2ldap - a web-based LDAP Client,
see http://www.web2ldap.de for details

(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)
"""

NON_INTERACTIVE_LOGIN_MECHS = set(['EXTERNAL','GSSAPI'])

import sys,types,socket,errno,time,\
       ldap,ldaputil,ldaputil.dns,ldapsession,mssignals, \
       pyweblib.forms,pyweblib.httphelper,pyweblib.sslenv,pyweblib.helper,pyweblib.session

# Import the application modules
import w2lapp.core,w2lapp.gui,w2lapp.cnf, \
       w2lapp.passwd,w2lapp.searchform,w2lapp.locate, \
       w2lapp.search,w2lapp.addmodifyform,w2lapp.add, \
       w2lapp.modify,w2lapp.dds,w2lapp.delete,w2lapp.ldapparams, \
       w2lapp.read,w2lapp.conninfo,w2lapp.login,w2lapp.connect, \
       w2lapp.referral,w2lapp.monitor,w2lapp.groupadm,w2lapp.rename, \
       w2lapp.srvrr,w2lapp.schema.viewer

from types import UnicodeType,StringType
from ldapurl import isLDAPUrl
from ldaputil.extldapurl import ExtendedLDAPUrl
from ldapsession import LDAPSession
from w2lapp.gui import ExceptionMsg
from w2lapp.form import Web2LDAPForm,FORM_CLASS
from w2lapp.session import session

try:
  SocketErrors = (socket.error,socket.gaierror)
except AttributeError:
  SocketErrors = socket.error

def CheckRequiredSecLevel(ls,env):
  required_ssl_level = w2lapp.cnf.GetParam(ls,'ssl_minlevel',0)
  current_ssl_level = pyweblib.sslenv.SecLevel(
    env,
    w2lapp.cnf.misc.sec_sslacceptedciphers,
    w2lapp.cnf.GetParam(ls,'ssl_valid_dn',''),
    w2lapp.cnf.GetParam(ls,'ssl_valid_idn','')
  )
  if current_ssl_level < required_ssl_level:
    raise w2lapp.core.ErrorExit(
      u'Access denied. SSL security level %d not sufficient. Must be at least %d.' % (
        current_ssl_level,required_ssl_level
      )
    )


def CommandDispatcher(sid,outf,command,form,connLDAPUrl,ls,dn,env):

  assert type(dn)==UnicodeType, TypeError("Type of argument 'dn' must be UnicodeType: %s" % repr(dn))

  """Execute function for command"""
  if command=='searchform':
    w2lapp.searchform.w2l_SearchForm(sid,outf,command,form,ls,dn)
  elif command=='search':
    w2lapp.search.w2l_Search(sid,outf,command,form,ls,dn,connLDAPUrl)
  elif command=='addform':
    w2lapp.addmodifyform.w2l_AddForm(sid,outf,command,form,ls,dn)
  elif command=='modifyform':
    w2lapp.addmodifyform.w2l_ModifyForm(sid,outf,command,form,ls,dn)
  elif command=='add':
    w2lapp.add.w2l_Add(sid,outf,command,form,ls,dn)
  elif command=='modify':
    w2lapp.modify.w2l_Modify(sid,outf,command,form,ls,dn)
  elif command=='dds':
    w2lapp.dds.w2l_DDS(sid,outf,command,form,ls,dn)
  elif command=='delete':
    w2lapp.delete.w2l_Delete(sid,outf,command,form,ls,dn,connLDAPUrl)
  elif command=='rename':
    w2lapp.rename.w2l_Rename(sid,outf,command,form,ls,dn)
  elif command=='passwd':
    w2lapp.passwd.w2l_Passwd(sid,outf,command,form,ls,dn,connLDAPUrl)
  elif command=='read':
    w2lapp.read.w2l_Read(
      sid,outf,command,form,ls,dn,
      wanted_attrs={
        0:connLDAPUrl.attrs,1:[]
      }[connLDAPUrl.attrs is None],
    )
  elif command=='conninfo':
    w2lapp.conninfo.w2l_ConnInfo(sid,outf,command,form,ls,dn,env)
  elif command=='ldapparams':
    w2lapp.ldapparams.w2l_LDAPParameters(sid,outf,command,form,ls,dn)
  elif command=='login':
    w2lapp.login.w2l_Login(
      sid,outf,'searchform',form,ls,dn,connLDAPUrl,
      form.getInputValue('login_search_root',[ls.getSearchRoot(dn)])[0],
      login_default_mech=connLDAPUrl.saslMech
    )
  elif command=='groupadm':
    w2lapp.groupadm.w2l_GroupAdm(sid,outf,command,form,ls,dn)
  elif command=='oid':
    w2lapp.schema.viewer.w2l_DisplaySchemaElement(sid,outf,command,form,ls,dn)
  return # CommandDispatcher()


def ExtractPathInfo(env):
  # Extract the command from PATH_INFO env var
  path_info = env.get('PATH_INFO','/')[1:]
  if not path_info:
    c,s = '',''
  else:
    # Work around broken web servers which adds the script name
    # to path info as well
    script_name = env['SCRIPT_NAME']
    if path_info.startswith(script_name):
      path_info = path_info[len(script_name):]
    try:
      c,s = path_info.split('/',1)
    except ValueError:
      c,s = path_info,''
  return c,s # extractPathInfo()


def HandleHTTPRequest(inf,outfile,errfile,env,script_name=None):

  current_access_time = time.time()

  ls = None

  ####################################################
  # Main try-except block for catching and logging
  # all unhandled exceptions
  ####################################################

  try:

    command,sid = ExtractPathInfo(env)

    form = FORM_CLASS.get(command,Web2LDAPForm)(inf,env,script_name or env['SCRIPT_NAME'])

    outf = form.outFileObject(outfile)

    if not FORM_CLASS.has_key(command):
      pyweblib.httphelper.URLRedirect(
        outf,form.script_name,2,
        'Invalid web2ldap command %s!' % pyweblib.forms.escapeHTML(repr(command))
      )
      return

    dn = None

    try:
      query_string = env.get('QUERY_STRING','').decode(form.accept_charset)
    except UnicodeError:
      query_string = env.get('QUERY_STRING','').decode('iso-8859-1')
    query_string = query_string.encode(form.accept_charset)

    ####################################################################
    # try-except block for gracefully handling of certain exceptions
    # (mainly w2lapp.core.ErrorExit and ldap.LDAPError)
    ####################################################################

    try:

      if command=='urlredirect':
        if isLDAPUrl(query_string):
          command=''
        else:
          # Check for valid session
          if session.sessiondict.has_key(sid) or query_string=='http://www.web2ldap.de/':
            # URL redirecting has absolutely nothing to do with rest
            pyweblib.httphelper.URLRedirect(outfile,query_string)
          else:
            pyweblib.httphelper.URLRedirect(outf,form.script_name,0,'Redirecting without valid session disallowed!')
          return

      elif command=='monitor':
        # Output simple monitor page. Does not require session handling.
        w2lapp.monitor.w2l_Monitor(outf,command,form,env)
        return
      elif command=='locate':
        form.getInputFields(ignoreEmptyFields=0)
        w2lapp.locate.w2l_Locate(outf,command,form)
        return
      elif command=='disconnect':
        # Explicitly remove old session
        session.deleteSession(sid)
        # Redirect to start page to avoid people bookmarking disconnect URL
        w2lapp.gui.URLRedirect(outf,form,form.script_name,0,redirect_msg='Disconnecting...')
        return
      elif command=='':
        # New connect => remove old session if necessary
        session.deleteSession(sid)
        # Just output a connect form if there was not query string
        if not query_string:
          w2lapp.connect.w2l_Connect(outf,form)
          return

      ####################################################
      # Restore old or initialize new web session object
      ####################################################

      if sid:

        #################################################
        # Session ID given => try to restore old session
        #################################################

        try:
          last_session_timestamp,_ = session.sessiondict[sid]
        except KeyError:
          # will be handled later on...
          pass

        try:
          ls = session.retrieveSession(sid,env)
        except pyweblib.session.SessionExpired,e:
          pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'Session expired.')
          return
        except pyweblib.session.InvalidSessionId,e:
          pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'Session ID not found.')
          return
        except pyweblib.session.SessionHijacked,e:
          if __debug__:
            w2lapp.core.log_exception(errfile,ls,env)
          pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'Session hijacking detected. Access denied!')
          return
        except pyweblib.session.SessionException,e:
          pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'Session handling error.')
          return
        else:
          if not isinstance(ls,LDAPSession):
            pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'Session not properly initialized.')
            return
          if w2lapp.cnf.misc.session_paranoid and \
             current_access_time-last_session_timestamp>w2lapp.cnf.misc.session_paranoid:
            # Store session with new session ID
            sid = session.renameSession(sid,env)

      else:

        #################################################
        # No session ID given => create new session
        #################################################

        sid = session.newSession(env)
        ls = LDAPSession(
          on_behalf=w2lapp.core.guessClientAddr(env),
          traceLevel=w2lapp.cnf.misc.ldap_trace_level,traceFile=errfile
        )

      if isLDAPUrl(query_string):
        # Extract the connection parameters from a LDAP URL
        try:
          inputLDAPUrl = ExtendedLDAPUrl(query_string)
          command = command or {
            None:'search',
            ldap.SCOPE_BASE:'read',
            ldap.SCOPE_ONELEVEL:'search',
            ldap.SCOPE_SUBTREE:'search',
          }[inputLDAPUrl.scope]
        except ValueError,e:
          raise w2lapp.core.ErrorExit(u'Error parsing LDAP URL: %s.' % (unicode(str(e),form.accept_charset)))
        else:
          # Re-instantiate form based on command derived from LDAP URL
          form = FORM_CLASS.get(command,Web2LDAPForm)(inf,env,script_name or env['SCRIPT_NAME'])

      else:

        # Extract the connection parameters from form fields
        form.getInputFields(ignoreEmptyFields=0)

        try:
          del_sid = form.field['delsid'].value[0]
        except IndexError,e:
          pass
        else:
          session.deleteSession(del_sid)

        if 'ldapurl' in form.inputFieldNames:
          # One form parameter with LDAP URL
          ldap_url_input = form.field['ldapurl'].value[0]
          try:
            inputLDAPUrl = ExtendedLDAPUrl(ldap_url_input.encode('ascii'))
          except ValueError,e:
            raise w2lapp.core.ErrorExit(u'Error parsing LDAP URL: %s.' % (unicode(str(e),form.accept_charset)))
        else:
          inputLDAPUrl = ExtendedLDAPUrl()
          conntype = int(form.getInputValue('conntype',[0])[0])
          inputLDAPUrl.urlscheme = w2lapp.form.CONNTYPE2URLSCHEME[conntype]
          inputLDAPUrl.hostport = form.getInputValue('host',[None])[0]
          inputLDAPUrl.x_startTLS = str(ldapsession.START_TLS_REQUIRED * (conntype==1))

      # Separate parameters for dn, who, cred and scope
      # have predecence over parameters specified in LDAP URL

      dn = form.getInputValue('dn',[unicode(inputLDAPUrl.dn,form.accept_charset)])[0]

      who = form.getInputValue('who',[None])[0]
      if who==None:
        if inputLDAPUrl.who!=None:
          who = unicode(inputLDAPUrl.who,form.accept_charset)
      else:
        inputLDAPUrl.who = who.encode(form.accept_charset)

      cred = form.getInputValue('cred',[None])[0]
      if cred==None:
        if inputLDAPUrl.cred!=None:
          cred = unicode(inputLDAPUrl.cred,form.accept_charset)
      else:
        inputLDAPUrl.cred = cred.encode(form.accept_charset)

      assert type(inputLDAPUrl.dn)==StringType, TypeError("Type of variable 'inputLDAPUrl.dn' must be StringType: %s" % repr(inputLDAPUrl.dn))
      assert inputLDAPUrl.who==None or type(inputLDAPUrl.who)==StringType, TypeError("Type of variable 'inputLDAPUrl.who' must be StringType: %s" % repr(inputLDAPUrl.who))
      assert inputLDAPUrl.cred==None or type(inputLDAPUrl.cred)==StringType, TypeError("Type of variable 'inputLDAPUrl.cred' must be StringType: %s" % repr(inputLDAPUrl.cred))

      assert type(dn)==UnicodeType, TypeError("Type of variable 'dn' must be UnicodeType: %s" % repr(dn))
      assert who==None or type(who)==UnicodeType, TypeError("Type of variable 'who' must be UnicodeType: %s" % repr(who))
      assert cred==None or type(cred)==UnicodeType, TypeError("Type of variable 'cred' must be UnicodeType: %s" % repr(cred))

      if not ldaputil.base.is_dn(dn):
        raise w2lapp.core.ErrorExit(u'Invalid DN.')

      scope_str=form.getInputValue(
        'scope',
        [
          {0:str(inputLDAPUrl.scope),1:''}[inputLDAPUrl.scope is None]
        ]
       )[0]
      if scope_str:
        inputLDAPUrl.scope=int(scope_str)
      else:
        inputLDAPUrl.scope=None

      command = command or {
        None:'searchform',
        ldap.SCOPE_BASE:'read',
        ldap.SCOPE_ONELEVEL:'search',
        ldap.SCOPE_SUBTREE:'search',
      }[inputLDAPUrl.scope]

      ####################################################
      # Connect to LDAP host
      ####################################################

      if not inputLDAPUrl.hostport is None and \
         inputLDAPUrl.hostport=='' and \
         inputLDAPUrl.urlscheme=='ldap' and \
         ls.uri is None:
        # Force a SRV RR lookup for dc-style DNs,
        # create list of URLs to connect to
        dns_srv_rrs = ldaputil.dns.dcDNSLookup(dn)
        initializeUrl_list = [
          ExtendedLDAPUrl(urlscheme='ldap',hostport=host,dn=dn).initializeUrl()
          for host in dns_srv_rrs
        ]
        if not initializeUrl_list:
          # No host specified in user's input
          session.deleteSession(sid)
          w2lapp.connect.w2l_Connect(
            outf,form,
            Msg='Connect failed',
            ErrorMsg='No host specified.'
          )
          return
        elif len(initializeUrl_list)==1:
          initializeUrl = initializeUrl_list[0]
        else:
          w2lapp.srvrr.w2l_ChaseSRVRecord(sid,outf,command,form,ls,dn,initializeUrl_list)
          return
      elif not inputLDAPUrl.hostport is None:
        initializeUrl = str(inputLDAPUrl.initializeUrl()[:])
      else:
        initializeUrl = None

      if initializeUrl and (ls.uri is None or initializeUrl!=ls.uri):
        # Delete current LDAPSession instance and create new
        del ls
        ls = LDAPSession(
          on_behalf=w2lapp.core.guessClientAddr(env),
          traceLevel=w2lapp.cnf.misc.ldap_trace_level,traceFile=errfile
        )
        # Check whether gateway access is allowed
        if w2lapp.cnf.hosts.restricted_ldap_uri_list and \
           not initializeUrl in w2lapp.core.ldap_uri_list_check_dict:
          raise w2lapp.core.ErrorExit(u'Access allowed through this gateway only to pre-configured target LDAP servers (check parameters restricted_ldap_uri_list and ldap_uri_list).')
        startTLSextop = inputLDAPUrl.getStartTLSOpt(
          w2lapp.cnf.GetParam(inputLDAPUrl,'starttls',ldapsession.START_TLS_NO)
        )
        # Connect to new specified host
        ls.open(
          initializeUrl,
          w2lapp.cnf.GetParam(inputLDAPUrl,'timeout',-1),
          startTLSextop,
          env,
          w2lapp.cnf.GetParam(inputLDAPUrl,'session_track_control',0),
          tls_cacertdir=w2lapp.cnf.GetParam(inputLDAPUrl,'tls_cacertdir',''),
          tls_cacertfile=w2lapp.cnf.GetParam(inputLDAPUrl,'tls_cacertfile',''),
          tls_certfile=w2lapp.cnf.GetParam(inputLDAPUrl,'tls_certfile',''),
          tls_keyfile=w2lapp.cnf.GetParam(inputLDAPUrl,'tls_keyfile',''),
        )
        ls.l.set_option(ldap.OPT_RESTART,0)
        ls.l.set_option(ldap.OPT_DEREF,0)
        ls.l.set_option(ldap.OPT_REFERRALS,0)
        ls.timeout = w2lapp.cnf.GetParam(ls,'timeout',-1)
        ls.l.timeout = ls.timeout
        # Store session data in case anything goes wrong after here
        # to give the exception handler a good chance
        session.storeSession(sid,ls)
        # Search and set backend specific timeout
        ls.timeout = w2lapp.cnf.GetParam(ls,'timeout',-1)
        ls.l.timeout = ls.timeout

      if ls.uri is None:
        session.deleteSession(sid)
        w2lapp.connect.w2l_Connect(
          outf,form,
          Msg='Connect failed',
          ErrorMsg='No valid LDAP connection.'
        )
        return

      # Store session data in case anything goes wrong after here
      # to give the exception handler a good chance
      session.storeSession(sid,ls)

      login_mech = form.getInputValue(
        'login_mech',
        [inputLDAPUrl.saslMech or w2lapp.cnf.GetParam(ls,'login_default_mech',[''])]
      )[0].upper() or None

      if who!=None and cred is None and not login_mech in NON_INTERACTIVE_LOGIN_MECHS:
        # first ask for password in a login form
        #---------------------------------------
        ls.setDN(dn)
        w2lapp.login.w2l_Login(
          sid,outf,command,form,ls,dn,inputLDAPUrl,
          form.getInputValue('login_search_root',[ls.getSearchRoot(dn)])[0],
          login_msg='',
          who=who,relogin=0,nomenu=1,
          login_default_mech=inputLDAPUrl.saslMech
        )
        return

      elif (who!=None and cred!=None) or login_mech in NON_INTERACTIVE_LOGIN_MECHS:
        # real bind operation
        #------------------------------
        login_search_root = form.getInputValue('login_search_root',[None])[0]
        if who!=None and not ldaputil.base.is_dn(who) and login_search_root==None:
          login_search_root = ls.getSearchRoot(dn)
        try:
          ls.bind(
            who,
            cred or '',
            login_mech,
            ''.join((
              form.getInputValue('login_authzid_prefix',[''])[0],
              form.getInputValue('login_authzid',[inputLDAPUrl.saslAuthzId or ''])[0],
            )) or None,
            form.getInputValue('login_realm',[inputLDAPUrl.saslRealm])[0],
            binddn_filtertemplate=form.getInputValue('login_filterstr',[ur'(uid=%s)'])[0],
            whoami_filtertemplate=w2lapp.cnf.GetParam(ls,'binddnsearch',ur'(uid=%s)'),
            loginSearchRoot = login_search_root,
          )
        except ldap.NO_SUCH_OBJECT,e:
          ls.setDN(dn)
          w2lapp.login.w2l_Login(
            sid,outf,command,form,ls,dn,inputLDAPUrl,login_search_root,
            login_msg=w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),
            who=who,relogin=1
          )
          return
      else:
        # anonymous access
        #------------------------------
        ls.getRootDSE()

      ls.setDN(dn)

      # Check for valid LDAPSession and connection to provide reasonable
      # error message instead of logging exception in case user is playing
      # with manually generated URLs
      if not isinstance(ls,LDAPSession) or ls.uri is None:
        pyweblib.httphelper.URLRedirect(outf,form.script_name,2,'No valid LDAP connection!')
        return

      # Store session data in case anything goes wrong after here
      # to give the exception handler a good chance
      session.storeSession(sid,ls)

      # Check backend specific required SSL level
      CheckRequiredSecLevel(ls,env)

      # Execute the command module
      try:
        CommandDispatcher(sid,outf,command,form,inputLDAPUrl,ls,dn,env)
      except ldap.SERVER_DOWN:
        ls.l.reconnect(ls.uri)
        CommandDispatcher(sid,outf,command,form,inputLDAPUrl,ls,dn,env)
      else:
        # Store current session
        session.storeSession(sid,ls)

    except pyweblib.forms.FormException,e:
      if ls is None:
        dn = None
      else:
        dn = ls.__dict__.get('_dn',None)
      try:
        e_msg = unicode(str(e))
      except UnicodeDecodeError:
        e_msg = unicode(repr(str(e)))
      ExceptionMsg(sid,outf,form,ls,dn,u'Error parsing form',u'Error parsing form: %s' % (form.utf2display(e_msg)))

    except ldap.SERVER_DOWN,e:
      # Server is down and reconnecting impossible => remove session
      session.deleteSession(sid)
      # Redirect to entry page
      w2lapp.connect.w2l_Connect(
        outf,form,
        Msg='Connect failed',
        ErrorMsg='Connecting to %s impossible!<br>%s' % (
          form.utf2display((initializeUrl or '-').decode('utf-8')),
          w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset)
        )
      )

    except ldap.NO_SUCH_OBJECT,e:

      #########################################
      # Generic handler for "No such object"
      #########################################

      host_list = ldaputil.dns.dcDNSLookup(dn)
      if (not host_list) or (ExtendedLDAPUrl(ls.uri).hostport in host_list):
        # Did not find another LDAP server for this naming context
        try:
          if type(e.args[0])==types.DictType and e.args[0].has_key('matched'):
            new_dn = unicode(e.args[0]['matched'],ls.charset)
          else:
            new_dn = dn
        except IndexError:
          new_dn = dn
        ExceptionMsg(
          sid,outf,form,ls,new_dn,u'No such object',
          w2lapp.gui.LDAPError2ErrMsg(
            e,form,ls.charset,
            template='{error_msg}<br>%s<br>{matched_dn}' % (w2lapp.gui.DisplayDN(sid,form,ls,dn))
          )
        )
      else:
        # Found LDAP server for this naming context via DNS SRV RR
        w2lapp.srvrr.w2l_ChaseSRVRecord(sid,outf,command,form,ls,dn,host_list)

    except (ldap.PARTIAL_RESULTS,ldap.REFERRAL),e:
      w2lapp.referral.w2l_ChaseReferral(sid,outf,command,form,ls,dn,e)

    except (ldap.INSUFFICIENT_ACCESS,ldap.STRONG_AUTH_REQUIRED),e:
      w2lapp.login.w2l_Login(
        sid,outf,command,form,ls,dn,inputLDAPUrl,
        form.getInputValue('login_search_root',[ls.getSearchRoot(dn)])[0],
        who='',
        login_msg=w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),relogin=1
      )

    except (ldap.INVALID_CREDENTIALS,ldap.INAPPROPRIATE_AUTH,ldapsession.USERNAME_NOT_FOUND),e:
      w2lapp.login.w2l_Login(
        sid,outf,command,form,ls,dn,inputLDAPUrl,
        form.getInputValue('login_search_root',[ls.getSearchRoot(dn)])[0],
        login_msg=w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),
        who=who,relogin=1
      )

    except ldapsession.PWD_EXPIRATION_WARNING,e:
      # Setup what's required to the case command=='passwd'
      ls.setDN(dn or e.who)
      form.addField(pyweblib.forms.Select('passwd_scheme',u'Password hash scheme',1,options=w2lapp.passwd.available_hashtypes,default=w2lapp.passwd.available_hashtypes[-1]))
      form.addField(pyweblib.forms.Checkbox('passwd_ntpasswordsync',u'Sync ntPassword for Samba',1,default="yes",checked=1))
      form.addField(pyweblib.forms.Checkbox('passwd_settimesync',u'Sync password setting times',1,default="yes",checked=1))
      # Directly generate the change password form
      w2lapp.passwd.PasswdForm(
        sid,outf,command,form,ls,dn,None,
        None,e.who.decode(ls.charset),None,
        'Password change needed',
        form.utf2display(
          u'Password will expire in %s!' % (
            w2lapp.gui.ts2repr(
              (
                (u'weeks',604800),
                (u'days',86400),
                (u'hours',3600),
                (u'mins',60),
                (u'secs',1),
              ),
              u' ',
              e.timeBeforeExpiration,
            )
        )),
      )

    except ldapsession.PasswordPolicyException,e:
      # Setup what's required to the case command=='passwd'
      ls.setDN(dn or e.who)
      form.addField(pyweblib.forms.Select('passwd_scheme',u'Password hash scheme',1,options=w2lapp.passwd.available_hashtypes,default=w2lapp.passwd.available_hashtypes[-1]))
      form.addField(pyweblib.forms.Checkbox('passwd_ntpasswordsync',u'Sync ntPassword for Samba',1,default="yes",checked=1))
      form.addField(pyweblib.forms.Checkbox('passwd_settimesync',u'Sync password setting times',1,default="yes",checked=1))
      # Directly generate the change password form
      w2lapp.passwd.PasswdForm(
        sid,outf,command,form,ls,dn,None,
        None,e.who.decode(ls.charset),None,
        'Password change needed',
        form.utf2display(unicode(e.desc))
      )

    except ldapsession.USERNAME_NOT_UNIQUE,e:
      login_search_root = form.getInputValue('login_search_root',[ls.getSearchRoot(dn)])[0]
      w2lapp.login.w2l_Login(
        sid,outf,command,form,ls,dn,inputLDAPUrl,
        login_search_root,
        login_msg=' | '.join([
          w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),
          form.applAnchor(
            'search','Show',sid,
            [
              ('dn',login_search_root),
              ('scope',str(ldap.SCOPE_SUBTREE)),
              ('filterstr',w2lapp.cnf.GetParam(ls,'binddnsearch',r'(uid=%s)').replace('%s',who))
            ]
        )]),
        who=who,relogin=1
      )

    except ldap.TIMEOUT,e:
      if __debug__:
        w2lapp.core.log_exception(errfile,ls,env)
      ExceptionMsg(
        sid,outf,form,ls,dn,
        u'LDAP timeout',
        u'Timeout of %d secs exceeded.' % (w2lapp.cnf.GetParam(ls,'timeout',-1))
      )

    except (
      ldap.DECODING_ERROR,
      ldap.LOCAL_ERROR,
      ldap.PARAM_ERROR,
      ldap.OTHER,
      ldap.USER_CANCELLED
    ),e:
      w2lapp.core.log_exception(errfile,ls,env)
      ExceptionMsg(sid,outf,form,ls,dn,u'LDAP exception',w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset))

    except ldap.LDAPError,e:
      if __debug__:
        w2lapp.core.log_exception(errfile,ls,env)
      ExceptionMsg(sid,outf,form,ls,dn,u'LDAP exception',w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset))

    except mssignals.SigPipeException,e:
      # SIGPIPE received
      if __debug__:
        w2lapp.core.log_exception(errfile,ls,env)

    except UnicodeError,e:
      if __debug__:
        w2lapp.core.log_exception(errfile,ls,env)
      ExceptionMsg(sid,outf,form,None,None,u'Unicode Error',form.utf2display(unicode(str(e),'ascii')))

    except SocketErrors,e:
      try:
        socket_errno = e.errno
      except AttributeError:
        socket_errno = None
      if not socket_errno in [errno.EPIPE,errno.ECONNRESET]:
        ExceptionMsg(sid,outf,form,None,None,u'Socket Error',form.utf2display(unicode(str(e),'ascii')))
      raise e

    except IOError,e:
      if __debug__:
        w2lapp.core.log_exception(errfile,ls,env)
      ExceptionMsg(sid,outf,form,ls,dn,u'I/O Error','See error log for details')

    except w2lapp.core.ErrorExit,e:
      ExceptionMsg(sid,outf,form,ls,dn,u'Error',e.Msg)

    except pyweblib.session.MaxSessionCountExceeded:
      pyweblib.httphelper.SimpleMsg(outfile,'Too many web sessions! Try later...')

  except pyweblib.forms.InvalidRequestMethod:
    pyweblib.httphelper.SimpleMsg(outfile,'Invalid request method!')

  except:
    # Log unhandled exceptions
    w2lapp.core.log_exception(errfile,ls,env)

  outfile.flush()
  errfile.flush()

  return # HandleHTTPRequest()
