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 }