########################################################################
# web2ldap
# (c) by Michael Stroeder <michael@stroeder.com>
# Distributed under GNU Public License (GPL)
# web-based LDAP Client, see http://www.web2ldap.de for details
########################################################################

import sys,os,string,re,ldap,cgiforms,ldapbase,ldapsession,ldif,web2ldapcnf,w2lcore,w2lgui,msgzip

def HandleHTTPRequest(
  inf=sys.stdin,
  outfile=sys.stdout,
  env=os.environ
):

  if ('gzip' in string.split(string.lower(env.get('HTTP_ACCEPT_ENCODING','')),',')) and \
     (web2ldapcnf.gzip_level>0) and \
     msgzip.GzipFile:
    # Create new gzip-stream file object
    outf = msgzip.GzipFile(
      filename='web2ldap output',
      mode='wb',
      compresslevel=web2ldapcnf.gzip_level,
      fileobj=outfile
    )
  else:
    outf = outfile

  http_accept_charset = env.get('HTTP_ACCEPT_CHARSET')
  if http_accept_charset and web2ldapcnf.print_rawutf8:
    if ('utf-8' in string.split(http_accept_charset,',')) or \
       (web2ldapcnf.print_rawutf8==2):
      accept_charset = 'utf-8'
    else:
      accept_charset = 'iso-8859-1'
  else:
    accept_charset = 'iso-8859-1'

  command = env.get('PATH_INFO','/')[1:]

  # form initialisieren
  form = cgiforms.formClass(inf,env,accept_charset)

  form.add(cgiforms.formInputClass('who',u'Bind DN',255,u'.*',size=40))
  form.add(cgiforms.formInputClass('cred',u'with Password',30,u'.*',size=15))
  form.add(cgiforms.formInputClass('host',u'Host:Port',255,unicode(w2lcore.host_pattern),size=30,required=(command!='')))
  form.add(
    cgiforms.formInputClass(
      'dn',u'DN',255,unicode(ldapbase.dn_pattern),size=25,
      required=(command in ['add','modify','read','modifyform'])
    )
  )

  if command in ['searchform','bind']:
    form.add(cgiforms.formSelectClass('searchform_mode',u'Search form mode',[(u'base',u'Base'),(u'adv',u'Advanced'),(u'exp',u'Expert')],u'base'))
    form.add(cgiforms.formFieldClass('search_params',u'Anzahl Suchangaben',2,'[0-9]+'))
    form.add(cgiforms.formSelectClass('search_scope',u'Scope',[(u'base',u'Base'),(u'one',u'One level'),(u'sub',u'Sub tree')],u'sub'))
    form.add(cgiforms.formInputClass('search_filterstr',u'Search filter string',120,'.*'))
    form.add(cgiforms.formSelectClass('search_resnumber',u'Number of results to display',[(u'0',u'unlimited'),(u'10',u'10'),(u'20',u'20'),(u'50',u'50'),(u'100',u'100'),(u'200',u'200')],u'10'))

  elif command=='search':
    form.add(cgiforms.formInputClass('search_filterstr',u'Search filter string',120,u'.*'))
    form.add(cgiforms.formInputClass('search_resminindex',u'Minimum index of search results',10,u'[0-9]+'))
    form.add(cgiforms.formInputClass('search_resnumber',u'Number of results to display',3,u'[0-9]+'))
    form.add(cgiforms.formSelectClass('search_output',u'Search output format',[u'table',u'ldif',u'ldif1',u'dsml',u'print']))
    form.add(cgiforms.formSelectClass('search_mode',u'Search Mode',[ur'(&%s)',ur'(|%s)']))
    form.add(cgiforms.formSelectClass('search_scope',u'Scope',[(u'base',u'Base'),(u'one',u'One level'),(u'sub',u'Sub tree')],u'sub'))
    form.add(cgiforms.formInputClass('search_attr',u'Search attribute',250,ur'[\w_;-]+',multiple=web2ldapcnf.max_searchparams))
    form.add(cgiforms.formSelectClass('search_option',u'Search option',w2lcore.search_options,multiple=web2ldapcnf.max_searchparams))
    form.add(cgiforms.formInputClass('search_string',u'Search string',60,u'.*',multiple=web2ldapcnf.max_searchparams))

  elif command in ['addform','modifyform']:
    from ldapoc import ldap_oc
    known_objectclasses = ldap_oc.keys()
    known_objectclasses.sort()
    if command=='addform':
      form.add(cgiforms.formInputClass('ldap_rdn','RDN of new entry',255,'.*'))
    form.add(cgiforms.formSelectClass('ldap_oc','Object classes',known_objectclasses,size=12,multiple=1,multiselect=1,ignorecase=1))

  elif command=='add':
    from ldapoc import ldap_oc
    known_objectclasses = ldap_oc.keys()
    form.add(cgiforms.formInputClass('ldap_rdn',u'RDN of new entry',255,'.*'))
    form.add(cgiforms.formSelectClass('ldap_oc',u'Object classes',known_objectclasses,size=12,multiple=1,multiselect=1,ignorecase=1))
    form.add(cgiforms.formTextareaClass('input_ldif',u'LDIF data',web2ldapcnf.ldif_maxbytes,ldif.ldif_pattern))
    form.add(cgiforms.formInputClass('input_attr',u'Attributename',255,'[\w_;-]*',multiple=web2ldapcnf.input_maxattrs))
    form.add(cgiforms.formInputClass('input_data',u'Attributedata',web2ldapcnf.input_maxfieldlen,'.*',multiple=web2ldapcnf.input_maxattrs))

  elif command=='modify':
    from ldapoc import ldap_oc
    known_objectclasses = ldap_oc.keys()
    form.add(cgiforms.formSelectClass('ldap_oc',u'Object classes',map(unicode,known_objectclasses),size=12,multiple=1,multiselect=1,ignorecase=1))
    form.add(cgiforms.formInputClass('input_attr',u'Attribute Name',255,u'[\w_;-]+',multiple=web2ldapcnf.input_maxattrs))
    form.add(cgiforms.formInputClass('input_data',u'Attribute Data',web2ldapcnf.input_maxfieldlen,'.*',multiple=web2ldapcnf.input_maxattrs))
    form.add(cgiforms.formTextareaClass('input_ldif',u'LDIF data',web2ldapcnf.ldif_maxbytes,ldif.ldif_pattern))

  elif command=='delete':
    form.add(cgiforms.formSelectClass('delete_confirm',u'Confirmation',['yes','no']))
    form.add(cgiforms.formCheckboxClass('delete_recursive',u'recursive delete','yes',checked=0))
    form.add(cgiforms.formCheckboxClass('delete_coe',u'continue on error','yes',checked=0))

  elif command=='modrdn':
    form.add(cgiforms.formInputClass('modrdn_newrdn',u'New RDN',255,unicode(ldapbase.rdn_pattern),size=50))
    form.add(cgiforms.formCheckboxClass('modrdn_delold',u'Delete old',value="yes",checked=1))

  elif command=='passwd':
    import w2lpasswd
    form.add(cgiforms.formFieldClass('passwd_newpasswd',u'New password',100,'.*',multiple=2))
    form.add(cgiforms.formSelectClass('passwd_hash',u'Hash',w2lpasswd.available_hashtypes,w2lpasswd.available_hashtypes[-1]))

  elif command=='read' or command=='vcard':
    form.add(cgiforms.formInputClass('read_attr',u'Read attribute',255,u'[\w_;-]+'))
    form.add(cgiforms.formSelectClass('read_attrmode',u'Read attribute',[u'view',u'load']))
    form.add(cgiforms.formInputClass('read_attrindex',u'Read attribute',255,u'[0-9]+'))
    form.add(cgiforms.formInputClass('read_attrmimetype',u'MIME type',255,u'[\w.-]+/[\w.-]+'))

  elif command=='login':
    form.add(cgiforms.formInputClass('login_binddn',u'Bind DN',255,unicode(ldapbase.dn_pattern),size=25))

  try:
    ls = ldapsession.LDAPSession()
    if command=='ldapurl':
      ldap_url = env.get('QUERY_STRING','')
      if ldap_url:
        if ldapbase.is_ldap_url(ldap_url):
          ls.host,ls.dn,search_attr,search_scope,search_filterstr,ext = ldapbase.parse_ldap_url(ldap_url)
          if not ls.host:
            raise w2lcore.ErrorExitClass(ls,'LDAP URL without host name currently not supported.')
          ls = w2lcore.LDAPConnect(ls)
          if search_scope==ldap.SCOPE_BASE:
            import w2lread
            w2lcore.CheckSecurityLevel('read',ls)
            w2lread.w2l_Read(outf,'read',form,ls,search_attr)
          else:
            import w2lsearch
            w2lcore.CheckSecurityLevel('search',ls)
            w2lsearch.w2l_Search(outf,'search',form,ls,'table',search_scope,search_filterstr)
        else:
          raise w2lcore.ErrorExitClass(ls,'LDAP URL has invalid format.')
      else:
        raise w2lcore.ErrorExitClass(ls,'No LDAP URL specified.')

    else:
      form.getparams(ignoreemptyparams=0)
      ls = w2lcore.LDAPSessionParams(form,ls)

      w2lcore.CheckSecurityLevel(command,ls)

      if not (command in ['','addform']):
        ls = w2lcore.LDAPConnect(ls)

    if command in ['searchform','bind']:
      import w2lsearchform
      w2lsearchform.w2l_SearchForm(outf,command,form,ls)

    elif command=='search':
      import w2lsearch
      w2lsearch.w2l_Search(outf,command,form,ls)

    elif command=='addform' or command=='modifyform':
      import w2laddmodifyform
      w2laddmodifyform.w2l_AddModifyForm(outf,command,form,ls)

    elif command=='add':
      import w2ladd
      w2ladd.w2l_Add(outf,command,form,ls)

    elif command=='modify':
      import w2lmodify
      w2lmodify.w2l_Modify(outf,command,form,ls)

    elif command=='delete':
      import w2ldelete
      w2ldelete.w2l_Delete(outf,command,form,ls)

    elif command=='modrdn':
      import w2lmodrdn
      w2lmodrdn.w2l_ModRDN(outf,command,form,ls)

    elif command=='passwd':
      import w2lpasswd
      w2lpasswd.w2l_Passwd(outf,command,form,ls)

    elif command=='read' or command=='vcard':
      import w2lread
      w2lread.w2l_Read(outf,command,form,ls)

    elif command=='secinfo':
      import w2lsecinfo
      w2lsecinfo.w2l_SecInfo(outf,command,form,ls)

    elif command=='login':
      import w2llogin
      w2llogin.w2l_Login(outf,command,form,ls)

    elif command=='':
      import w2lconnect
      w2lconnect.w2l_Connect(outf,command,form,ls)

    elif command=='ldapurl':
      pass

    else:
      raise w2lcore.ErrorExitClass(ls,'Invalid web2ldap command <STRONG>%s</STRONG>!' % (command))

  except cgiforms.formException,e:
    w2lgui.PrintHeader(outf,'FORM Error',form.accept_charset)
    outf.write("""<div id=MessageDiv>
<h1>FORM Error</h1>
<p>
  %s
  For questions or error reports contact:
  <a href="mailto:%s">%s</a>
</p>
</div>
""" % (str(e),env.get('SERVER_ADMIN',''),env.get('SERVER_ADMIN','')))
    w2lgui.PrintFooter(outf)
  except ldap.PARTIAL_RESULTS,e:
    import w2lreferral
    w2lreferral.w2l_ChaseReferral(outf,command,form,ls,e,reuse_binddn=0)
  except ldap.INSUFFICIENT_ACCESS,e:
    import w2llogin
    w2llogin.w2l_Relogin(outf,command,form,ls,e,reuse_binddn=0)
  except ldap.INAPPROPRIATE_AUTH,e:
    import w2llogin
    w2llogin.w2l_Relogin(outf,command,form,ls,e,reuse_binddn=0)
  except ldap.INVALID_CREDENTIALS,e:
    import w2llogin
    w2llogin.w2l_Relogin(outf,command,form,ls,e,reuse_binddn=1)
  except ldapbase.USERNAME_NOT_FOUND,e:
    import w2llogin
    w2llogin.w2l_Relogin(outf,command,form,ls,e,reuse_binddn=0)
  except ldapbase.USERNAME_NOT_UNIQUE,e:
    import w2llogin
    w2llogin.w2l_Relogin(outf,command,form,ls,e,reuse_binddn=0)
  except w2lcore.ErrorExitClass,e:
    w2lgui.PrintHeader(outf,'Error',form.accept_charset)
    w2lgui.MainMenuTable(outf,form,e.ls)
    outf.write("""<div id=MessageDiv>
<h1>Error</h1>
<p>
  %s
  For questions or error reports contact:
  <a href="mailto:%s">%s</a>
</p>
</div>
""" % (e.Msg,env.get('SERVER_ADMIN',''),env.get('SERVER_ADMIN','')))
    w2lgui.PrintFooter(outf)
    # help garbage collection
    sys.exc_traceback = None
    e = None

  except ldap.LDAPError:
    exc_obj,exc_value,exc_traceback = sys.exc_info()
    w2lgui.PrintHeader(outf,'Error',form.accept_charset)
    w2lgui.MainMenuTable(outf,form,ls)
    outf.write("""<div id=MessageDiv>
<h1>Unhandled LDAP exception</h1>
<p>
  %s: %s
</p>
<p>
  For questions or error reports contact:
  <a href="mailto:%s">%s</a>
</p>
</div>
""" % (exc_obj,exc_value,env.get('SERVER_ADMIN',''),env.get('SERVER_ADMIN','')))
    w2lgui.PrintFooter(outf)
    # help garbage collection
    sys.exc_traceback = None

  except UnicodeError:
    exc_obj,exc_value,exc_traceback = sys.exc_info()
    w2lgui.PrintHeader(outf,'Error',form.accept_charset)
    w2lgui.MainMenuTable(outf,form,ls)
    outf.write("""<div id=MessageDiv>
<h1>Unicode error</h1>
<p>
  Some data received was malformed UTF-8 encoded. For security
  reasons the UTF-8 checking is strict!
</p>
<p>
  For questions or error reports contact:
  <a href="mailto:%s">%s</a>
</p>
</div>
""" % (env.get('SERVER_ADMIN',''),env.get('SERVER_ADMIN','')))
    w2lgui.PrintFooter(outf)
    # help garbage collection
    sys.exc_traceback = None

  except IOError:
    exc_obj,exc_value,exc_traceback = sys.exc_info()
    w2lgui.PrintHeader(outf,'Error',form.accept_charset)
    w2lgui.MainMenuTable(outf,form,ls)
    outf.write("""<div id=MessageDiv>
<h1>Unhandled I/O Error</h1>
<p>
  %s: %s
</p>
<p>
  For questions or error reports contact:
  <a href="mailto:%s">%s</a>
</p>
</div>
""" % (exc_obj,exc_value,env.get('SERVER_ADMIN',''),env.get('SERVER_ADMIN','')))
    w2lgui.PrintFooter(outf)
    # help garbage collection
    sys.exc_traceback = None

  # Close gzip file-object if necessary
  if hasattr(outf,'fileobj'):
    outf.close()
    outf = None

  # Close LDAP connection object if necessary
  try:
    ls.l.unbind_s()
  except ldap.LDAPError:
    pass
  except AttributeError:
    pass

  return

