001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS 026 */ 027package org.opends.server.authorization.dseecompat; 028 029import static org.opends.messages.AccessControlMessages.*; 030import static org.opends.server.authorization.dseecompat.Aci.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.net.InetAddress; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.regex.Matcher; 037import java.util.regex.Pattern; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.i18n.slf4j.LocalizedLogger; 041 042/** 043 * This class implements the dns bind rule keyword. 044 */ 045public class DNS implements KeywordBindRule { 046 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 047 048 /** List of patterns to match against. */ 049 private List<String> patterns; 050 051 /** The enumeration representing the bind rule type of the DNS rule. */ 052 private EnumBindRuleType type; 053 054 /** Regular expression group used to match a dns rule. */ 055 private static final String valueRegex = "([a-zA-Z0-9\\.\\-\\*]+)"; 056 057 /** Regular expression group used to match one or more DNS values. */ 058 private static final String valuesRegExGroup = 059 valueRegex + ZERO_OR_MORE_WHITESPACE + 060 "(," + ZERO_OR_MORE_WHITESPACE + valueRegex + ")*"; 061 062 /** 063 * Create a class representing a dns bind rule keyword. 064 * @param patterns List of dns patterns to match against. 065 * @param type An enumeration representing the bind rule type. 066 */ 067 DNS(List<String> patterns, EnumBindRuleType type) { 068 this.patterns=patterns; 069 this.type=type; 070 } 071 072 /** 073 * Decode an string representing a dns bind rule. 074 * @param expr A string representation of the bind rule. 075 * @param type An enumeration representing the bind rule type. 076 * @return A keyword bind rule class that can be used to evaluate 077 * this bind rule. 078 * @throws AciException If the expression string is invalid. 079 */ 080 public static DNS decode(String expr, EnumBindRuleType type) 081 throws AciException 082 { 083 if (!Pattern.matches(valuesRegExGroup, expr)) { 084 LocalizableMessage message = WARN_ACI_SYNTAX_INVALID_DNS_EXPRESSION.get(expr); 085 throw new AciException(message); 086 } 087 List<String> dns = new LinkedList<>(); 088 int valuePos = 1; 089 Pattern valuePattern = Pattern.compile(valueRegex); 090 Matcher valueMatcher = valuePattern.matcher(expr); 091 while (valueMatcher.find()) { 092 String hn=valueMatcher.group(valuePos); 093 String[] hnArray=hn.split("\\.", -1); 094 for(int i=1, n=hnArray.length; i < n; i++) { 095 if(hnArray[i].equals("*")) { 096 LocalizableMessage message = 097 WARN_ACI_SYNTAX_INVALID_DNS_WILDCARD.get(expr); 098 throw new AciException(message); 099 } 100 } 101 102 // If the provided hostname does not contain any wildcard 103 // characters, then it must be the canonical hostname for the 104 // associated IP address. If it is not, then it will not match the 105 // intended target, and we should generate a warning message to let 106 // the administrator know about it. If the provided value does not 107 // match the canonical name for the associated IP address, and the 108 // given hostname is "localhost", then we should treat it specially 109 // and also match the canonical hostname. This is necessary because 110 // "localhost" is likely to be very commonly used in these kinds of 111 // rules and on some systems the canonical representation is 112 // configured to be "localhost.localdomain" which may not be known 113 // to the administrator. 114 if (!hn.contains("*")) 115 { 116 try 117 { 118 for (InetAddress addr : InetAddress.getAllByName(hn)) 119 { 120 String canonicalName = addr.getCanonicalHostName(); 121 if (! hn.equalsIgnoreCase(canonicalName)) 122 { 123 if (hn.equalsIgnoreCase("localhost") 124 && !dns.contains(canonicalName)) 125 { 126 dns.add(canonicalName); 127 128 logger.warn(WARN_ACI_LOCALHOST_DOESNT_MATCH_CANONICAL_VALUE, expr, hn, canonicalName); 129 } 130 else 131 { 132 logger.warn(WARN_ACI_HOSTNAME_DOESNT_MATCH_CANONICAL_VALUE, expr, hn, addr.getHostAddress(), 133 addr.getCanonicalHostName()); 134 } 135 } 136 } 137 } 138 catch (Exception e) 139 { 140 logger.traceException(e); 141 142 logger.warn(WARN_ACI_ERROR_CHECKING_CANONICAL_HOSTNAME, hn, expr, getExceptionMessage(e)); 143 } 144 } 145 146 dns.add(hn); 147 } 148 return new DNS(dns, type); 149 } 150 151 /** 152 * Performs evaluation of dns keyword bind rule using the provided 153 * evaluation context. 154 * @param evalCtx An evaluation context to use in the evaluation. 155 * @return An enumeration evaluation result. 156 */ 157 @Override 158 public EnumEvalResult evaluate(AciEvalContext evalCtx) { 159 EnumEvalResult matched=EnumEvalResult.FALSE; 160 String[] remoteHost = evalCtx.getHostName().split("\\.", -1); 161 for(String p : patterns) { 162 String[] pat = p.split("\\.", -1); 163 if(evalHostName(remoteHost, pat)) { 164 matched=EnumEvalResult.TRUE; 165 break; 166 } 167 } 168 return matched.getRet(type, false); 169 } 170 171 /** 172 * Checks an array containing the remote client's hostname against 173 * patterns specified in the bind rule expression. Wild-cards are 174 * only permitted in the leftmost field and the rest of the domain 175 * name array components must match. A single wild-card matches any 176 * hostname. 177 * @param remoteHostName Array containing components of the remote clients 178 * hostname (split on "."). 179 * @param pat An array containing the pattern specified in 180 * the bind rule expression. The first array slot may be a wild-card "*". 181 * @return True if the remote hostname matches the pattern. 182 */ 183 boolean evalHostName(String[] remoteHostName, String[] pat) { 184 boolean wildCard=pat[0].equals("*"); 185 //Check if there is a single wild-card. 186 if(pat.length == 1 && wildCard) { 187 return true; 188 } 189 int remoteHnIndex=remoteHostName.length-pat.length; 190 if(remoteHnIndex < 0) { 191 return false; 192 } 193 int patternIndex=0; 194 if(!wildCard) { 195 remoteHnIndex=0; 196 } else { 197 patternIndex=1; 198 remoteHnIndex++; 199 } 200 for(int i=remoteHnIndex ;i<remoteHostName.length;i++) { 201 if(!pat[patternIndex++].equalsIgnoreCase(remoteHostName[i])) { 202 return false; 203 } 204 } 205 return true; 206 } 207 208 /** {@inheritDoc} */ 209 @Override 210 public String toString() { 211 final StringBuilder sb = new StringBuilder(); 212 toString(sb); 213 return sb.toString(); 214 } 215 216 /** {@inheritDoc} */ 217 @Override 218 public final void toString(StringBuilder buffer) { 219 buffer.append(super.toString()); 220 } 221 222}