001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020
021package org.apache.directory.server.dns.store.jndi.operations;
022
023
024import java.util.Collections;
025import java.util.EnumMap;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Properties;
030import java.util.Set;
031
032import javax.naming.CompoundName;
033import javax.naming.Name;
034import javax.naming.NamingEnumeration;
035import javax.naming.NamingException;
036import javax.naming.directory.Attribute;
037import javax.naming.directory.Attributes;
038import javax.naming.directory.DirContext;
039import javax.naming.directory.SearchControls;
040import javax.naming.directory.SearchResult;
041
042import org.apache.directory.api.ldap.model.constants.SchemaConstants;
043import org.apache.directory.server.dns.messages.QuestionRecord;
044import org.apache.directory.server.dns.messages.RecordClass;
045import org.apache.directory.server.dns.messages.RecordType;
046import org.apache.directory.server.dns.messages.ResourceRecord;
047import org.apache.directory.server.dns.messages.ResourceRecordModifier;
048import org.apache.directory.server.dns.store.DnsAttribute;
049import org.apache.directory.server.dns.store.jndi.DnsOperation;
050import org.apache.directory.server.i18n.I18n;
051
052
053/**
054 * A JNDI context operation for looking up Resource Records from an embedded JNDI provider.
055 *
056 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
057 */
058public class GetRecords implements DnsOperation
059{
060    /** The name of the question to get. */
061    private final QuestionRecord question;
062
063
064    /**
065     * Creates the action to be used against the embedded JNDI provider.
066     * 
067     * @param question 
068     */
069    public GetRecords( QuestionRecord question )
070    {
071        this.question = question;
072    }
073
074    /**
075     * Mappings of type to objectClass.
076     */
077    private static final Map<RecordType, String> TYPE_TO_OBJECTCLASS;
078
079    static
080    {
081        EnumMap<RecordType, String> typeToObjectClass = new EnumMap<>( RecordType.class );
082        typeToObjectClass.put( RecordType.SOA, "apacheDnsStartOfAuthorityRecord" );
083        typeToObjectClass.put( RecordType.A, "apacheDnsAddressRecord" );
084        typeToObjectClass.put( RecordType.NS, "apacheDnsNameServerRecord" );
085        typeToObjectClass.put( RecordType.CNAME, "apacheDnsCanonicalNameRecord" );
086        typeToObjectClass.put( RecordType.PTR, "apacheDnsPointerRecord" );
087        typeToObjectClass.put( RecordType.MX, "apacheDnsMailExchangeRecord" );
088        typeToObjectClass.put( RecordType.SRV, "apacheDnsServiceRecord" );
089        typeToObjectClass.put( RecordType.TXT, "apacheDnsTextRecord" );
090
091        TYPE_TO_OBJECTCLASS = Collections.unmodifiableMap( typeToObjectClass );
092    }
093
094    /**
095     * Mappings of type to objectClass.
096     */
097    private static final Map<String, RecordType> OBJECTCLASS_TO_TYPE;
098
099    static
100    {
101        Map<String, RecordType> objectClassToType = new HashMap<>();
102        objectClassToType.put( "apacheDnsStartOfAuthorityRecord", RecordType.SOA );
103        objectClassToType.put( "apacheDnsAddressRecord", RecordType.A );
104        objectClassToType.put( "apacheDnsNameServerRecord", RecordType.NS );
105        objectClassToType.put( "apacheDnsCanonicalNameRecord", RecordType.CNAME );
106        objectClassToType.put( "apacheDnsPointerRecord", RecordType.PTR );
107        objectClassToType.put( "apacheDnsMailExchangeRecord", RecordType.MX );
108        objectClassToType.put( "apacheDnsServiceRecord", RecordType.SRV );
109        objectClassToType.put( "apacheDnsTextRecord", RecordType.TXT );
110        objectClassToType.put( "apacheDnsReferralNameServer", RecordType.NS );
111        objectClassToType.put( "apacheDnsReferralAddress", RecordType.A );
112
113        OBJECTCLASS_TO_TYPE = Collections.unmodifiableMap( objectClassToType );
114    }
115
116
117    /**
118     * Note that the base is a relative path from the exiting context.
119     * It is not a Dn.
120     */
121    public Set<ResourceRecord> execute( DirContext ctx, Name base ) throws Exception
122    {
123        if ( question == null )
124        {
125            return null;
126        }
127
128        String name = question.getDomainName();
129        RecordType type = question.getRecordType();
130
131        SearchControls controls = new SearchControls();
132        controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
133
134        String filter = "(objectClass=" + TYPE_TO_OBJECTCLASS.get( type ) + ")";
135
136        NamingEnumeration<SearchResult> list = ctx.search( transformDomainName( name ), filter, controls );
137
138        Set<ResourceRecord> set = new HashSet<>();
139
140        while ( list.hasMore() )
141        {
142            SearchResult result = list.next();
143            Name relative = getRelativeName( ctx.getNameInNamespace(), result.getName() );
144
145            set.add( getRecord( result.getAttributes(), relative ) );
146        }
147
148        return set;
149    }
150
151
152    /**
153     * Marshals a RecordStoreEntry from an Attributes object.
154     *
155     * @param attrs the attributes of the DNS question
156     * @return the entry for the question
157     * @throws NamingException if there are any access problems
158     */
159    private ResourceRecord getRecord( Attributes attrs, Name relative ) throws NamingException
160    {
161        String soaMinimum = "86400";
162        String soaClass = "IN";
163
164        ResourceRecordModifier modifier = new ResourceRecordModifier();
165
166        Attribute attr;
167
168        // if no name, transform rdn
169        attr = attrs.get( DnsAttribute.NAME );
170
171        if ( attr != null )
172        {
173            modifier.setDnsName( ( String ) attr.get() );
174        }
175        else
176        {
177            relative = getDomainComponents( relative );
178
179            String dnsName;
180            dnsName = transformDistinguishedName( relative.toString() );
181            modifier.setDnsName( dnsName );
182        }
183
184        // type is implicit in objectclass
185        attr = attrs.get( DnsAttribute.TYPE );
186
187        if ( attr != null )
188        {
189            modifier.setDnsType( RecordType.valueOf( ( String ) attr.get() ) );
190        }
191        else
192        {
193            modifier.setDnsType( getType( attrs.get( SchemaConstants.OBJECT_CLASS_AT ) ) );
194        }
195
196        // class defaults to SOA CLASS
197        attr = attrs.get( DnsAttribute.CLASS );
198        String dnsClass = attr != null ? ( String ) attr.get() : soaClass;
199        modifier.setDnsClass( RecordClass.valueOf( dnsClass ) );
200
201        // ttl defaults to SOA MINIMUM
202        attr = attrs.get( DnsAttribute.TTL );
203        String dnsTtl = attr != null ? ( String ) attr.get() : soaMinimum;
204        modifier.setDnsTtl( Integer.parseInt( dnsTtl ) );
205
206        NamingEnumeration<String> ids = attrs.getIDs();
207
208        while ( ids.hasMore() )
209        {
210            String id = ids.next();
211            modifier.put( id, ( String ) attrs.get( id ).get() );
212        }
213
214        return modifier.getEntry();
215    }
216
217
218    /**
219     * Uses the algorithm in <a href="http://www.faqs.org/rfcs/rfc2247.html">RFC 2247</a>
220     * to transform any Internet domain name into a distinguished name.
221     *
222     * @param domainName the domain name
223     * @return the distinguished name
224     */
225    String transformDomainName( String domainName )
226    {
227        if ( domainName == null || domainName.length() == 0 )
228        {
229            return "";
230        }
231
232        StringBuilder buf = new StringBuilder( domainName.length() + 16 );
233
234        buf.append( "dc=" );
235        buf.append( domainName.replaceAll( "\\.", ",dc=" ) );
236
237        return buf.toString();
238    }
239
240
241    /**
242     * Uses the algorithm in <a href="http://www.faqs.org/rfcs/rfc2247.html">RFC 2247</a>
243     * to transform a distinguished name into an Internet domain name.
244     *
245     * @param distinguishedName the distinguished name
246     * @return the domain name
247     */
248    String transformDistinguishedName( String distinguishedName )
249    {
250        if ( distinguishedName == null || distinguishedName.length() == 0 )
251        {
252            return "";
253        }
254
255        String domainName = distinguishedName.replaceFirst( "dc=", "" );
256        domainName = domainName.replaceAll( ",dc=", "." );
257
258        return domainName;
259    }
260
261
262    private RecordType getType( Attribute objectClass ) throws NamingException
263    {
264        NamingEnumeration<?> list = objectClass.getAll();
265
266        while ( list.hasMore() )
267        {
268            String value = ( String ) list.next();
269
270            if ( !value.equals( "apacheDnsAbstractRecord" ) )
271            {
272                RecordType type = OBJECTCLASS_TO_TYPE.get( value );
273
274                if ( type == null )
275                {
276                    throw new RuntimeException( I18n.err( I18n.ERR_646 ) );
277                }
278
279                return type;
280            }
281        }
282
283        throw new NamingException( I18n.err( I18n.ERR_647 ) );
284    }
285
286
287    private Name getRelativeName( String nameInNamespace, String baseDn ) throws NamingException
288    {
289        Properties props = new Properties();
290        props.setProperty( "jndi.syntax.direction", "right_to_left" );
291        props.setProperty( "jndi.syntax.separator", "," );
292        props.setProperty( "jndi.syntax.ignorecase", "true" );
293        props.setProperty( "jndi.syntax.trimblanks", "true" );
294
295        Name searchBaseDn = null;
296
297        Name ctxRoot = new CompoundName( nameInNamespace, props );
298        searchBaseDn = new CompoundName( baseDn, props );
299
300        if ( !searchBaseDn.startsWith( ctxRoot ) )
301        {
302            throw new NamingException( I18n.err( I18n.ERR_648, baseDn ) );
303        }
304
305        for ( int ii = 0; ii < ctxRoot.size(); ii++ )
306        {
307            searchBaseDn.remove( 0 );
308        }
309
310        return searchBaseDn;
311    }
312
313
314    private Name getDomainComponents( Name name ) throws NamingException
315    {
316        for ( int ii = 0; ii < name.size(); ii++ )
317        {
318            if ( !name.get( ii ).startsWith( "dc=" ) )
319            {
320                name.remove( ii );
321            }
322        }
323
324        return name;
325    }
326}