# -*- coding: utf-8 -*-
"""
w2lapp.locate.py: Try to locate a LDAP host with various methods.

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)
"""

import re,socket,ldap

try:
  import DNS
except ImportError:
  DNS = None

from ldaputil.extldapurl import ExtendedLDAPUrl
from ldapurl import LDAPUrlExtension,LDAPUrlExtensions

# Modules shipped with web2ldap
import ldaputil.base,ldaputil.dns,w2lapp.core,w2lapp.gui,w2lapp.schema.syntaxes


ldap_hostname_aliases = [
#  'ldap','dsa','x500','ldapdb','nds','openldap'
  'ldap',
]

##############################################################################
# LDAP Service Locator
##############################################################################

LOCATE_NAME_RFC822 = 0
LOCATE_NAME_DCDN   = 1
LOCATE_NAME_DOMAIN = 2

def w2l_Locate(outf,command,form):
  """
  Try to locate a LDAP server in DNS by several heuristics
  """
  # Check if DNS module is present
  if DNS is None:
    raise w2lapp.core.ErrorExit(u'Could not import DNS module.')

  w2lapp.gui.TopSection(None,outf,form,None,None,'DNS lookup',w2lapp.gui.EntryMainMenu(form),[])

  locate_name = form.getInputValue('locate_name',[''])[0].strip()

  outf.write('<div id="Message" class="Main">\n')

  msg_html = ''
  outf_lines = []

  if locate_name:

    # Try to determine the format of the input parameter
    if ldaputil.base.is_dn(locate_name):
      # Use dc-style LDAP DN
      msg_html = 'Input is considered LDAP distinguished name.'
      locate_domain = ldaputil.dns.dcdn2dnsdomain(locate_name)
      locate_name_type = LOCATE_NAME_DCDN
    elif u'@' in locate_name:
      # Use domain part of RFC822 mail address
      msg_html = 'Input is considered e-mail address or user principal name.'
      locate_domain = locate_name.split('@')[-1]
      locate_name_type = LOCATE_NAME_RFC822
    else:
      # Use DNS domain directly
      msg_html = 'Input is considered DNS domain name.'
      locate_domain = locate_name
      locate_name_type = LOCATE_NAME_DOMAIN

    if locate_domain:

      dns_list = locate_domain.lower().split(u'.')

      for dns_index in range(len(dns_list),0,-1):

        dns_name = '.'.join([ label.encode('idna') for label in dns_list[-dns_index:]])
        dns_name_u = '.'.join([ label.decode('idna') for label in dns_name.split('.')])

        outf_lines.append('<h1>Domain %s</h1><p>Encoded domain name: <strong>%s</strong></p>\n' % (
          form.utf2display(dns_name_u),
          form.utf2display(dns_name.decode('ascii')),
        ))

        search_base = ldaputil.dns.dnsdomain2dcdn(dns_name)
        if dns_name.endswith('de-mail-test.de') or dns_name.endswith('de-mail.de'):
          search_base = ','.join((search_base,'cn=de-mail'))
          lu_extensions = LDAPUrlExtensions({
            'x-saslmech':LDAPUrlExtension(
              critical=0,
              extype='x-saslmech',
              exvalue='EXTERNAL'
            )
          })
        else:
          lu_extensions = None

        outf_lines.append('<h2>Found SRV RRs</h2>\n')
        # Search for a SRV RR of dns_name
        try:
          ldap_srv_result = ldaputil.dns.ldapSRV(dns_name,srv_prefix='_ldap._tcp')
        except (DNS.Error,socket.error),e:
          raise w2lapp.core.ErrorExit(u'DNS or socket error: %s' % (str(e)))
        try:
          ldaps_srv_result = ldaputil.dns.ldapSRV(dns_name,srv_prefix='_ldaps._tcp')
        except (DNS.Error,socket.error),e:
          pass


        # Display SRV search results
        for url_scheme,srv_result in (
          ('ldap',ldap_srv_result),
          ('ldaps',ldaps_srv_result),
        ):
          for priority, weight, port, hostname in srv_result:
            outf_lines.append('<p>Found SRV record: %s:%d (priority %d, weight %d)</p>' % (
                hostname,port,priority,weight,
              )
            )
            try:
              host_address = socket.gethostbyname(hostname)
            except socket.error,e:
              outf_lines.append('<p class="ErrorMessage">Did not find IP address for hostname <em>%s</em>.</p>' % (
                form.utf2display(hostname.decode('ascii'))
              ))
            else:
              ldap_url = ExtendedLDAPUrl(
                urlscheme=url_scheme,
                hostport='%s:%d' % (hostname,port),
                dn=search_base,
                scope=ldap.SCOPE_BASE,
                extensions=lu_extensions
              )
              outf_lines.append("""
                <p>IP address found for host name %s: %s</p>
                <table>
                  <tr>
                    <td>%s</td>
                    <td><a href="%s">%s</a></td>
                  </tr>
              """ % (
                  hostname,
                  host_address,
                  w2lapp.gui.LDAPURLButton(None,form,None,str(ldap_url)),
                  ldap_url.unparse(),
                  ldap_url.unparse(),
                )
              )

            if locate_name_type==LOCATE_NAME_RFC822:
              ldap_url = ExtendedLDAPUrl(
                urlscheme=url_scheme,
                hostport='%s:%d' % (hostname,port),
                dn=search_base,
                scope=ldap.SCOPE_SUBTREE,
                filterstr='(mail=%s)' % (locate_name),
                extensions=lu_extensions
              )
              outf_lines.append("""<tr>
            <td>%s</td>
            <td><a href="%s">Search %s</a></td>
          </tr>
      """ % (
                  w2lapp.gui.LDAPURLButton(None,form,None,ldap_url),
                  ldap_url.unparse(),
                  ldap_url.unparse(),
                )
              )
            outf_lines.append('</table>\n')

        outf_lines.append('<h2>Found well known aliases</h2>\n')
        # Search for well known aliases of LDAP servers under dns_name
        for alias in ldap_hostname_aliases:
          alias_name = '.'.join([alias,dns_name])
          try:
            host_address = socket.gethostbyname(alias_name)
          except socket.error:
            pass
          else:
            ldap_url = ExtendedLDAPUrl(
              hostport=alias_name,
              dn=search_base,
              scope=ldap.SCOPE_BASE
            )
            outf_lines.append("""
      <p>IP address found for host name %s: %s</p>
      <table>
        <tr>
          <td>%s</td>
          <td><a href="%s">%s</a></td>
        </tr>
      </table>
    """ % (
                alias_name,
                host_address,
                w2lapp.gui.LDAPURLButton(None,form,None,ldap_url),
                ldap_url.unparse(),
                ldap_url.unparse(),
              )
            )

  outf.write("""
  <h1>Locate LDAP host via DNS</h1>
  %s
  %s
  <form
    action="%s/locate"
    method="GET"
    enctype="application/x-www-form-urlencoded"
    accept-charset="%s"
  >
    <p>
      Search for well-known DNS aliases
      of LDAP servers and DNS SRV records in
      a given DNS domain (experimental).
    </p>
    <fieldset title="Locate LDAP host by DNS name or DN.">
      <input type="submit" value="Locate"><br>
      <table>
        <tr>
          <td>
            E-mail address, DNS domain or dc-style DN:
          </td>
          <td>
            <input name="locate_name" size="60">
          </td>
        </tr>
      </table>
    </fieldset>
  </form>

</div>
""" % (
    msg_html,
    '\n'.join(outf_lines),
    form.script_name,
    form.accept_charset,
  )
)

  w2lapp.gui.PrintFooter(outf,form)
