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}