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 }