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    
021    package org.apache.directory.server.dns.store.jndi.operations;
022    
023    
024    import java.util.Collections;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Map;
028    import java.util.Properties;
029    import java.util.Set;
030    
031    import javax.naming.CompoundName;
032    import javax.naming.Name;
033    import javax.naming.NamingEnumeration;
034    import javax.naming.NamingException;
035    import javax.naming.directory.Attribute;
036    import javax.naming.directory.Attributes;
037    import javax.naming.directory.DirContext;
038    import javax.naming.directory.SearchControls;
039    import javax.naming.directory.SearchResult;
040    
041    import org.apache.directory.server.dns.messages.QuestionRecord;
042    import org.apache.directory.server.dns.messages.RecordClass;
043    import org.apache.directory.server.dns.messages.RecordType;
044    import org.apache.directory.server.dns.messages.ResourceRecord;
045    import org.apache.directory.server.dns.messages.ResourceRecordModifier;
046    import org.apache.directory.server.dns.store.DnsAttribute;
047    import org.apache.directory.server.dns.store.jndi.DnsOperation;
048    import org.apache.directory.server.i18n.I18n;
049    import org.apache.directory.shared.ldap.constants.SchemaConstants;
050    
051    
052    /**
053     * A JNDI context operation for looking up Resource Records from an embedded JNDI provider.
054     *
055     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
056     * @version $Rev$, $Date$
057     */
058    public class GetRecords implements DnsOperation
059    {
060        private static final long serialVersionUID = 1077580995617778894L;
061    
062        /** The name of the question to get. */
063        private final QuestionRecord question;
064    
065    
066        /**
067         * Creates the action to be used against the embedded JNDI provider.
068         * 
069         * @param question 
070         */
071        public GetRecords( QuestionRecord question )
072        {
073            this.question = question;
074        }
075    
076        /**
077         * Mappings of type to objectClass.
078         */
079        private static final Map<RecordType, String> TYPE_TO_OBJECTCLASS;
080    
081        static
082        {
083            Map<RecordType, String> typeToObjectClass = new HashMap<RecordType, String>();
084            typeToObjectClass.put( RecordType.SOA, "apacheDnsStartOfAuthorityRecord" );
085            typeToObjectClass.put( RecordType.A, "apacheDnsAddressRecord" );
086            typeToObjectClass.put( RecordType.NS, "apacheDnsNameServerRecord" );
087            typeToObjectClass.put( RecordType.CNAME, "apacheDnsCanonicalNameRecord" );
088            typeToObjectClass.put( RecordType.PTR, "apacheDnsPointerRecord" );
089            typeToObjectClass.put( RecordType.MX, "apacheDnsMailExchangeRecord" );
090            typeToObjectClass.put( RecordType.SRV, "apacheDnsServiceRecord" );
091            typeToObjectClass.put( RecordType.TXT, "apacheDnsTextRecord" );
092    
093            TYPE_TO_OBJECTCLASS = Collections.unmodifiableMap( typeToObjectClass );
094        }
095    
096        /**
097         * Mappings of type to objectClass.
098         */
099        private static final Map<String, RecordType> OBJECTCLASS_TO_TYPE;
100    
101        static
102        {
103            Map<String, RecordType> objectClassToType = new HashMap<String, RecordType>();
104            objectClassToType.put( "apacheDnsStartOfAuthorityRecord", RecordType.SOA );
105            objectClassToType.put( "apacheDnsAddressRecord", RecordType.A );
106            objectClassToType.put( "apacheDnsNameServerRecord", RecordType.NS );
107            objectClassToType.put( "apacheDnsCanonicalNameRecord", RecordType.CNAME );
108            objectClassToType.put( "apacheDnsPointerRecord", RecordType.PTR );
109            objectClassToType.put( "apacheDnsMailExchangeRecord", RecordType.MX );
110            objectClassToType.put( "apacheDnsServiceRecord", RecordType.SRV );
111            objectClassToType.put( "apacheDnsTextRecord", RecordType.TXT );
112            objectClassToType.put( "apacheDnsReferralNameServer", RecordType.NS );
113            objectClassToType.put( "apacheDnsReferralAddress", RecordType.A );
114    
115            OBJECTCLASS_TO_TYPE = Collections.unmodifiableMap( objectClassToType );
116        }
117    
118    
119        /**
120         * Note that the base is a relative path from the exiting context.
121         * It is not a DN.
122         */
123        public Set<ResourceRecord> execute( DirContext ctx, Name base ) throws Exception
124        {
125            if ( question == null )
126            {
127                return null;
128            }
129    
130            String name = question.getDomainName();
131            RecordType type = question.getRecordType();
132    
133            SearchControls controls = new SearchControls();
134            controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
135    
136            String filter = "(objectClass=" + TYPE_TO_OBJECTCLASS.get( type ) + ")";
137    
138            NamingEnumeration<SearchResult> list = ctx.search( transformDomainName( name ), filter, controls );
139    
140            Set<ResourceRecord> set = new HashSet<ResourceRecord>();
141    
142            while ( list.hasMore() )
143            {
144                SearchResult result = list.next();
145                Name relative = getRelativeName( ctx.getNameInNamespace(), result.getName() );
146    
147                set.add( getRecord( result.getAttributes(), relative ) );
148            }
149    
150            return set;
151        }
152    
153    
154        /**
155         * Marshals a RecordStoreEntry from an Attributes object.
156         *
157         * @param attrs the attributes of the DNS question
158         * @return the entry for the question
159         * @throws NamingException if there are any access problems
160         */
161        private ResourceRecord getRecord( Attributes attrs, Name relative ) throws NamingException
162        {
163            String SOA_MINIMUM = "86400";
164            String SOA_CLASS = "IN";
165    
166            ResourceRecordModifier modifier = new ResourceRecordModifier();
167    
168            Attribute attr;
169    
170            // if no name, transform rdn
171            attr = attrs.get( DnsAttribute.NAME );
172    
173            if ( attr != null )
174            {
175                modifier.setDnsName( ( String ) attr.get() );
176            }
177            else
178            {
179                relative = getDomainComponents( relative );
180    
181                String dnsName;
182                dnsName = transformDistinguishedName( relative.toString() );
183                modifier.setDnsName( dnsName );
184            }
185    
186            // type is implicit in objectclass
187            attr = attrs.get( DnsAttribute.TYPE );
188    
189            if ( attr != null )
190            {
191                modifier.setDnsType( RecordType.valueOf( ( String ) attr.get() ) );
192            }
193            else
194            {
195                modifier.setDnsType( getType( attrs.get( SchemaConstants.OBJECT_CLASS_AT ) ) );
196            }
197    
198            // class defaults to SOA CLASS
199            String dnsClass = ( attr = attrs.get( DnsAttribute.CLASS ) ) != null ? ( String ) attr.get() : SOA_CLASS;
200            modifier.setDnsClass( RecordClass.valueOf( dnsClass ) );
201    
202            // ttl defaults to SOA MINIMUM
203            String dnsTtl = ( attr = attrs.get( DnsAttribute.TTL ) ) != null ? ( String ) attr.get() : SOA_MINIMUM;
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            StringBuffer buf = new StringBuffer( 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    }