HADOOP-15996.  Improved Kerberos username mapping strategy in Hadoop.
               Contributed by Bolke de Bruin

(cherry picked from commit d43af8b3db4743b4b240751b6f29de6c20cfd6e5)
(cherry picked from commit febafd0e4f6e7b0a95045565e149e3ed2bee4b6c)
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
index 887548b..7a918b2 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java
@@ -88,6 +88,12 @@
    */
   public static final String NAME_RULES = TYPE + ".name.rules";
 
+  /**
+   * Constant for the configuration property that indicates how auth_to_local
+   * rules are evaluated.
+   */
+  public static final String RULE_MECHANISM = TYPE + ".name.rules.mechanism";
+
   private String type;
   private String keytab;
   private GSSManager gssManager;
@@ -163,7 +169,10 @@
       if (nameRules != null) {
         KerberosName.setRules(nameRules);
       }
-
+      String ruleMechanism = config.getProperty(RULE_MECHANISM, null);
+      if (ruleMechanism != null) {
+        KerberosName.setRuleMechanism(ruleMechanism);
+      }
       try {
         gssManager = Subject.doAs(serverSubject,
             new PrivilegedExceptionAction<GSSManager>() {
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
index 287bb13..684d2c8 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosName.java
@@ -44,6 +44,19 @@
 public class KerberosName {
   private static final Logger LOG = LoggerFactory.getLogger(KerberosName.class);
 
+  /**
+   * Constant that defines auth_to_local legacy hadoop evaluation
+   */
+  public static final String MECHANISM_HADOOP = "hadoop";
+
+  /**
+   * Constant that defines auth_to_local MIT evaluation
+   */
+  public static final String MECHANISM_MIT = "mit";
+
+  /** Constant that defines the default behavior of the rule mechanism */
+  public static final String DEFAULT_MECHANISM = MECHANISM_HADOOP;
+
   /** The first component of the name */
   private final String serviceName;
   /** The second component of the name. It may be null. */
@@ -81,6 +94,11 @@
    */
   private static List<Rule> rules;
 
+  /**
+   * How to evaluate auth_to_local rules
+   */
+  private static String ruleMechanism = null;
+
   private static String defaultRealm = null;
 
   @VisibleForTesting
@@ -304,10 +322,11 @@
      * array.
      * @param params first element is the realm, second and later elements are
      *        are the components of the name "a/b@FOO" -> {"FOO", "a", "b"}
+     * @param ruleMechanism defines the rule evaluation mechanism
      * @return the short name if this rule applies or null
      * @throws IOException throws if something is wrong with the rules
      */
-    String apply(String[] params) throws IOException {
+    String apply(String[] params, String ruleMechanism) throws IOException {
       String result = null;
       if (isDefault) {
         if (getDefaultRealm().equals(params[0])) {
@@ -323,7 +342,9 @@
           }
         }
       }
-      if (result != null && nonSimplePattern.matcher(result).find()) {
+      if (result != null
+              && nonSimplePattern.matcher(result).find()
+              && ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) {
         throw new NoMatchingRule("Non-simple name " + result +
                                  " after auth_to_local rule " + this);
       }
@@ -392,21 +413,22 @@
     } else {
       params = new String[]{realm, serviceName, hostName};
     }
+    String ruleMechanism = this.ruleMechanism;
+    if (ruleMechanism == null && rules != null) {
+      LOG.warn("auth_to_local rule mechanism not set."
+      + "Using default of " + DEFAULT_MECHANISM);
+      ruleMechanism = DEFAULT_MECHANISM;
+    }
     for(Rule r: rules) {
-      String result = r.apply(params);
+      String result = r.apply(params, ruleMechanism);
       if (result != null) {
         return result;
       }
     }
-    throw new NoMatchingRule("No rules applied to " + toString());
-  }
-
-  /**
-   * Set the rules.
-   * @param ruleString the rules string.
-   */
-  public static void setRules(String ruleString) {
-    rules = (ruleString != null) ? parseRules(ruleString) : null;
+    if (ruleMechanism.equalsIgnoreCase(MECHANISM_HADOOP)) {
+      throw new NoMatchingRule("No rules applied to " + toString());
+    }
+    return toString();
   }
 
   /**
@@ -434,6 +456,47 @@
     return rules != null;
   }
 
+  /**
+   * Indicates of the rule mechanism has been set
+   *
+   * @return if the rule mechanism has been set.
+   */
+  public static boolean hasRuleMechanismBeenSet() {
+    return ruleMechanism != null;
+  }
+
+  /**
+   * Set the rules.
+   * @param ruleString the rules string.
+   */
+  public static void setRules(String ruleString) {
+    rules = (ruleString != null) ? parseRules(ruleString) : null;
+  }
+
+  /**
+   *
+   * @param ruleMech the evaluation type: hadoop, mit
+   *                 'hadoop' indicates '@' or '/' are not allowed the result
+   *                 evaluation. 'MIT' indicates that auth_to_local
+   *                 rules follow MIT Kerberos evaluation.
+   */
+  public static void setRuleMechanism(String ruleMech) {
+    if (ruleMech != null
+            && (!ruleMech.equalsIgnoreCase(MECHANISM_HADOOP)
+            && !ruleMech.equalsIgnoreCase(MECHANISM_MIT))) {
+      throw new IllegalArgumentException("Invalid rule mechanism: " + ruleMech);
+    }
+    ruleMechanism = ruleMech;
+  }
+
+  /**
+   * Get the rule evaluation mechanism
+   * @return the rule evaluation mechanism
+   */
+  public static String getRuleMechanism() {
+    return ruleMechanism;
+  }
+
   static void printRules() throws IOException {
     int i = 0;
     for(Rule r: rules) {
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
index 4aabb34..177bcb4 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java
@@ -65,6 +65,7 @@
     props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
     props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
                       "RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+    props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM, "hadoop");
     return props;
   }
 
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
index 8b4bc15..6d60eae 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
@@ -65,6 +65,8 @@
             KerberosTestUtils.getKeytabFile());
     props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
             "RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+    props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM,
+            KerberosName.MECHANISM_HADOOP);
     return props;
   }
 
@@ -89,18 +91,18 @@
   }
 
   @Test(timeout=60000)
-  public void testNameRules() throws Exception {
+  public void testNameRulesHadoop() throws Exception {
     KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
     Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
 
     //destroy handler created in setUp()
     handler.destroy();
-
-    KerberosName.setRules("RULE:[1:$1@$0](.*@FOO)s/@.*//\nDEFAULT");
-    
     handler = getNewAuthenticationHandler();
+
     Properties props = getDefaultProperties();
-    props.setProperty(KerberosAuthenticationHandler.NAME_RULES, "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+    props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
+        "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+
     try {
       handler.init(props);
     } catch (Exception ex) {
@@ -116,7 +118,55 @@
     }
   }
 
-  @Test(timeout=60000)
+  @Test
+  public void testNameRulesCompat() throws Exception {
+    KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
+    Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
+
+    //destroy handler created in setUp()
+    handler.destroy();
+    handler = getNewAuthenticationHandler();
+
+    Properties props = getDefaultProperties();
+    props.setProperty(KerberosAuthenticationHandler.NAME_RULES, "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
+    props.setProperty(KerberosAuthenticationHandler.RULE_MECHANISM, KerberosName.MECHANISM_MIT);
+
+    try {
+      handler.init(props);
+    } catch (Exception ex) {
+    }
+    kn = new KerberosName("bar@BAR");
+    Assert.assertEquals("bar", kn.getShortName());
+    kn = new KerberosName("bar@FOO");
+    Assert.assertEquals("bar@FOO", kn.getShortName());
+  }
+
+  @Test
+  public void testNullProperties() throws Exception {
+    KerberosName kn = new KerberosName(KerberosTestUtils.getServerPrincipal());
+    Assert.assertEquals(KerberosTestUtils.getRealm(), kn.getRealm());
+
+    KerberosName.setRuleMechanism("MIT");
+    KerberosName.setRules("DEFAULT");
+
+    //destroy handler created in setUp()
+    handler.destroy();
+    handler = getNewAuthenticationHandler();
+
+    Properties props = getDefaultProperties();
+    props.remove(KerberosAuthenticationHandler.NAME_RULES);
+    props.remove(KerberosAuthenticationHandler.RULE_MECHANISM);
+
+    try {
+      handler.init(props);
+    } catch (Exception ex) {
+    }
+
+    Assert.assertEquals("MIT", KerberosName.getRuleMechanism());
+    Assert.assertEquals("DEFAULT", KerberosName.getRules());
+  }
+
+  @Test
   public void testInit() throws Exception {
     Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
     Set<KerberosPrincipal> principals = handler.getPrincipals();
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
index 2db0df4..105fa11 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosName.java
@@ -40,6 +40,7 @@
       "RULE:[2:$1;$2](^.*;admin$)s/;admin$//\n" +
       "RULE:[2:$2](root)\n" +
       "DEFAULT";
+    KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
     KerberosName.setRules(rules);
     KerberosName.printRules();
   }
@@ -85,10 +86,16 @@
 
   @Test
   public void testAntiPatterns() throws Exception {
+    KerberosName.setRuleMechanism(KerberosName.MECHANISM_HADOOP);
     checkBadName("owen/owen/owen@FOO.COM");
     checkBadName("owen@foo/bar.com");
+
     checkBadTranslation("foo@ACME.COM");
     checkBadTranslation("root/joe@FOO.COM");
+
+    KerberosName.setRuleMechanism(KerberosName.MECHANISM_MIT);
+    checkTranslation("foo@ACME.COM", "foo@ACME.COM");
+    checkTranslation("root/joe@FOO.COM", "root/joe@FOO.COM");
   }
 
   @Test
@@ -129,6 +136,11 @@
     checkTranslation("Joe/guestguest@FOO.COM", "joe");
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void testInvalidRuleMechanism() throws Exception {
+    KerberosName.setRuleMechanism("INVALID_MECHANISM");
+  }
+
   @After
   public void clear() {
     System.clearProperty("java.security.krb5.realm");
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
index 8523423..d910cdd 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
@@ -607,6 +607,13 @@
    * <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
    * core-default.xml</a>
    */
+  public static final String  HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM =
+    "hadoop.security.auth_to_local.mechanism";
+  /**
+   * @see
+   * <a href="{@docRoot}/../hadoop-project-dist/hadoop-common/core-default.xml">
+   * core-default.xml</a>
+   */
   public static final String HADOOP_SECURITY_DNS_INTERFACE_KEY =
     "hadoop.security.dns.interface";
   /**
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
index d2ba469..4b67b63 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java
@@ -1126,7 +1126,6 @@
       params.put("kerberos.keytab", httpKeytab);
     }
     params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
-
     defineFilter(webAppContext, SPNEGO_FILTER,
                  AuthenticationFilter.class.getName(), params, null);
   }
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
index 55b2786..df96c50 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HadoopKerberosName.java
@@ -19,6 +19,7 @@
 package org.apache.hadoop.security;
 
 import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM;
 
 import java.io.IOException;
 
@@ -27,6 +28,9 @@
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.authentication.util.KerberosName;
 import org.apache.hadoop.security.authentication.util.KerberosUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * This class implements parsing and handling of Kerberos principal names. In 
  * particular, it splits them apart and translates them down into local
@@ -36,6 +40,8 @@
 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
 @InterfaceStability.Evolving
 public class HadoopKerberosName extends KerberosName {
+  private static final Logger LOG =
+          LoggerFactory.getLogger(HadoopKerberosName.class);
 
   /**
    * Create a name from the full Kerberos principal name.
@@ -45,8 +51,8 @@
     super(name);
   }
   /**
-   * Set the static configuration to get the rules.
-   * <p/>
+   * Set the static configuration to get and evaluate the rules.
+   * <p>
    * IMPORTANT: This method does a NOP if the rules have been set already.
    * If there is a need to reset the rules, the {@link KerberosName#setRules(String)}
    * method should be invoked directly.
@@ -73,6 +79,9 @@
     }
     String ruleString = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL, defaultRule);
     setRules(ruleString);
+
+    String ruleMechanism = conf.get(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM,  DEFAULT_MECHANISM);
+    setRuleMechanism(ruleMechanism);
   }
 
   public static void main(String[] args) throws Exception {
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
index 1711921..2c6de4d 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
@@ -22,6 +22,7 @@
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.conf.Configured;
 import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.authentication.util.KerberosName;
 import org.apache.hadoop.security.token.Token;
 import org.apache.hadoop.security.token.TokenIdentifier;
 import org.apache.hadoop.util.ExitUtil;
@@ -54,6 +55,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*;
 import static org.apache.hadoop.security.UserGroupInformation.*;
@@ -129,6 +131,12 @@
   private boolean nofail = false;
   private boolean nologin = false;
   private boolean jaas = false;
+  private boolean checkShortName = false;
+
+  /**
+   * A pattern that recognizes simple/non-simple names. Per KerberosName
+   */
+  private static final Pattern nonSimplePattern = Pattern.compile("[/@]");
 
   /**
    * Flag set to true if a {@link #verify(boolean, String, String, Object...)}
@@ -157,6 +165,8 @@
 
   public static final String ARG_SECURE = "--secure";
 
+  public static final String ARG_VERIFYSHORTNAME = "--verifyshortname";
+
   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
   public KDiag(Configuration conf,
       PrintWriter out,
@@ -200,6 +210,7 @@
     nofail = popOption(ARG_NOFAIL, args);
     jaas = popOption(ARG_JAAS, args);
     nologin = popOption(ARG_NOLOGIN, args);
+    checkShortName = popOption(ARG_VERIFYSHORTNAME, args);
 
     // look for list of resources
     String resource;
@@ -245,7 +256,9 @@
       + arg(ARG_NOLOGIN, "", "Do not attempt to log in")
       + arg(ARG_OUTPUT, "<file>", "Write output to a file")
       + arg(ARG_RESOURCE, "<resource>", "Load an XML configuration resource")
-      + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure");
+      + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure")
+      + arg(ARG_VERIFYSHORTNAME, ARG_PRINCIPAL + " <principal>",
+      "Verify the short name of the specific principal does not contain '@' or '/'");
   }
 
   private String arg(String name, String params, String meaning) {
@@ -278,6 +291,7 @@
     println("%s = %d", ARG_KEYLEN, minKeyLength);
     println("%s = %s", ARG_KEYTAB, keytab);
     println("%s = %s", ARG_PRINCIPAL, principal);
+    println("%s = %s", ARG_VERIFYSHORTNAME, checkShortName);
 
     // Fail fast on a JVM without JCE installed.
     validateKeyLength();
@@ -376,6 +390,9 @@
       validateKinitExecutable();
       validateJAAS(jaas);
       validateNTPConf();
+      if (checkShortName) {
+        validateShortName();
+      }
 
       if (!nologin) {
         title("Logging in");
@@ -431,6 +448,32 @@
   }
 
   /**
+   * Verify whether auth_to_local rules transform a principal name
+   * <p>
+   * Having a local user name "bar@foo.com" may be harmless, so it is noted at
+   * info. However if what was intended is a transformation to "bar"
+   * it can be difficult to debug, hence this check.
+   */
+  protected void validateShortName() {
+    failif(principal == null, CAT_KERBEROS, "No principal defined");
+
+    try {
+      KerberosName kn = new KerberosName(principal);
+      String result = kn.getShortName();
+      if (nonSimplePattern.matcher(result).find()) {
+        warn(CAT_KERBEROS, principal + " short name: " + result +
+                " still contains @ or /");
+      }
+    } catch (IOException e) {
+      throw new KerberosDiagsFailure(CAT_KERBEROS, e,
+              "Failed to get short name for " + principal, e);
+    } catch (IllegalArgumentException e) {
+      error(CAT_KERBEROS, "KerberosName(" + principal + ") failed: %s\n%s",
+              e, StringUtils.stringifyException(e));
+    }
+  }
+
+  /**
    * Get the default realm.
    * <p>
    * Not having a default realm may be harmless, so is noted at info.
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index a94ff78..4c24906 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -663,6 +663,15 @@
 </property>
 
 <property>
+  <name>hadoop.security.auth_to_local.mechanism</name>
+  <value>hadoop</value>
+  <description>The mechanism by which auth_to_local rules are evaluated.
+    If set to 'hadoop' it will not allow resulting local user names to have
+    either '@' or '/'. If set to 'MIT' it will follow MIT evaluation rules
+    and the restrictions of 'hadoop' do not apply.</description>
+</property>
+
+<property>
   <name>hadoop.token.files</name>
   <value></value>
   <description>List of token cache files that have delegation tokens for hadoop service</description>
diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md b/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
index 09f1eed..856861f 100644
--- a/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
+++ b/hadoop-common-project/hadoop-common/src/site/markdown/SecureMode.md
@@ -133,22 +133,35 @@
 
 ### Mapping from Kerberos principals to OS user accounts
 
-Hadoop maps Kerberos principals to OS user (system) accounts using rules specified by `hadoop.security.auth_to_local`. These rules work in the same way as the `auth_to_local` in [Kerberos configuration file (krb5.conf)](http://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html). In addition, Hadoop `auth_to_local` mapping supports the **/L** flag that lowercases the returned name.
+Hadoop maps Kerberos principals to OS user (system) accounts using rules specified by `hadoop.security.auth_to_local`. How Hadoop evaluates these rules is determined by the setting of `hadoop.security.auth_to_local.mechanism`.
 
-The default is to pick the first component of the principal name as the system user name if the realm matches the `default_realm` (usually defined in /etc/krb5.conf). e.g. The default rule maps the principal `host/full.qualified.domain.name@REALM.TLD` to system user `host`. The default rule will *not be appropriate* for most clusters.
+In the default `hadoop` mode a Kerberos principal *must* be matched against a rule that transforms the principal to a simple form, i.e. a user account name without '@' or '/', otherwise a principal will not be authorized and a error will be logged.  In case of the `MIT` mode the rules work in the same way as the `auth_to_local` in [Kerberos configuration file (krb5.conf)](http://web.mit.edu/Kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html) and the restrictions of `hadoop` mode do *not* apply. If you use `MIT` mode it is suggested to use the same `auth_to_local` rules that are specified in your /etc/krb5.conf as part of your default realm and keep them in sync. In both `hadoop` and `MIT` mode the rules are being applied (with the exception of `DEFAULT`) to *all* principals regardless of their specified realm. Also, note you should *not* rely on the `auth_to_local` rules as an ACL and use proper (OS) mechanisms.
 
+Possible values for `auth_to_local` are:
+
+* `RULE:exp` The local name will be formulated from exp. The format for exp is `[n:string](regexp)s/pattern/replacement/g`. The integer n indicates how many components the target principal should have. If this matches, then a string will be formed from string, substituting the realm of the principal for `$0` and the n’th component of the principal for `$n` (e.g., if the principal was johndoe/admin then `[2:$2$1foo]` would result in the string `adminjohndoefoo`). If this string matches regexp, then the `s//[g]` substitution command will be run over the string. The optional g will cause the substitution to be global over the string, instead of replacing only the first match in the string. As an extension to MIT, Hadoop `auth_to_local` mapping supports the **/L** flag that lowercases the returned name.
+
+* `DEFAULT` Picks the first component of the principal name as the system user name if and only if the realm matches the `default_realm` (usually defined in /etc/krb5.conf). e.g. The default rule maps the principal `host/full.qualified.domain.name@MYREALM.TLD` to system user `host` if the default realm is `MYREALM.TLD`.
+
+In case no rules are specified Hadoop defaults to using `DEFAULT`, which is probably *not suitable* to most of the clusters.
+
+Please note that Hadoop does not support multiple default realms (e.g like Heimdal does). Also, Hadoop does not do a verification on mapping whether a local system account exists.
+
+### Example rules
 In a typical cluster HDFS and YARN services will be launched as the system `hdfs` and `yarn` users respectively. `hadoop.security.auth_to_local` can be configured as follows:
 
     <property>
       <name>hadoop.security.auth_to_local</name>
       <value>
-        RULE:[2:$1/$2@$0]([ndj]n/.*@REALM.TLD)s/.*/hdfs/
-        RULE:[2:$1/$2@$0]([rn]m/.*@REALM.TLD)s/.*/yarn/
-        RULE:[2:$1/$2@$0](jhs/.*@REALM.TLD)s/.*/mapred/
+        RULE:[2:$1/$2@$0]([ndj]n/.*@REALM.\TLD)s/.*/hdfs/
+        RULE:[2:$1/$2@$0]([rn]m/.*@REALM\.TLD)s/.*/yarn/
+        RULE:[2:$1/$2@$0](jhs/.*@REALM\.TLD)s/.*/mapred/
         DEFAULT
       </value>
     </property>
 
+This would map any principal `nn, dn, jn` on any `host` from realm `REALM.TLD` to the local system account `hdfs`. Secondly it would map any principal `rm, nm` on any `host` from `REALM.TLD` to the local system account `yarn`. Thirdly, it would map the principal `jhs` on any `host` from realm `REALM.TLD` to the local system account `mapred`. Finally, any principal on any host from the default realm will be mapped to the user component of that principal.
+
 Custom rules can be tested using the `hadoop kerbname` command.  This command allows one to specify a principal and apply Hadoop's current `auth_to_local` ruleset.
 
 ### Mapping from user to group
@@ -470,6 +483,7 @@
   [--out <file>] : Write output to a file.
   [--resource <resource>] : Load an XML configuration resource.
   [--secure] : Require the hadoop configuration to be secure.
+  [--verifyshortname <principal>]: Verify the short name of the specific principal does not contain '@' or '/'
 ```
 
 #### `--jaas`: Require a JAAS file to be defined in `java.security.auth.login.config`.
@@ -565,6 +579,11 @@
 
 Needless to say, an application so configured cannot talk to a secure Hadoop cluster.
 
+#### `--verifyshortname <principal>`: validate the short name of a principal
+
+This verifies that the short name of a principal contains neither the `"@"`
+nor `"/"` characters.
+
 ### Example
 
 ```
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
index 3895ae1..e395566 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestKDiag.java
@@ -165,6 +165,22 @@
   }
 
   @Test
+  public void testKerberosName() throws Throwable {
+    kdiagFailure(ARG_KEYLEN, KEYLEN,
+            ARG_VERIFYSHORTNAME,
+            ARG_PRINCIPAL, "foo/foo/foo@BAR.COM");
+  }
+
+  @Test
+  public void testShortName() throws Throwable {
+    kdiag(ARG_KEYLEN, KEYLEN,
+            ARG_KEYTAB, keytab.getAbsolutePath(),
+            ARG_PRINCIPAL,
+            ARG_VERIFYSHORTNAME,
+            ARG_PRINCIPAL, "foo@EXAMPLE.COM");
+  }
+
+  @Test
   public void testFileOutput() throws Throwable {
     File f = new File("target/kdiag.txt");
     kdiag(ARG_KEYLEN, KEYLEN,
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
index ce10067..cf2a8b6 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java
@@ -74,6 +74,7 @@
 import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
 import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN;
 import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM;
 import static org.apache.hadoop.test.MetricsAsserts.assertCounter;
 import static org.apache.hadoop.test.MetricsAsserts.assertCounterGt;
 import static org.apache.hadoop.test.MetricsAsserts.assertGaugeGt;
@@ -329,6 +330,7 @@
     // security off, but use rules if explicitly set
     conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL,
         "RULE:[1:$1@$0](.*@OTHER.REALM)s/(.*)@.*/other-$1/");
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
     UserGroupInformation.setConfiguration(conf);
     testConstructorSuccess("user1", "user1");
     testConstructorSuccess("user4@OTHER.REALM", "other-user4");
@@ -336,25 +338,52 @@
     testConstructorFailures("user2@DEFAULT.REALM");
     testConstructorFailures("user3/cron@DEFAULT.REALM");
     testConstructorFailures("user5/cron@OTHER.REALM");
+
+    // with MIT
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "mit");
+    UserGroupInformation.setConfiguration(conf);
+    testConstructorSuccess("user2@DEFAULT.REALM", "user2@DEFAULT.REALM");
+    testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3/cron@DEFAULT.REALM");
+    testConstructorSuccess("user5/cron@OTHER.REALM", "user5/cron@OTHER.REALM");
+
+    // failures
+    testConstructorFailures("user6@example.com@OTHER.REALM");
+    testConstructorFailures("user7@example.com@DEFAULT.REALM");
     testConstructorFailures(null);
     testConstructorFailures("");
+
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
+
   }
   
   /** test constructor */
   @Test (timeout = 30000)
   public void testConstructorWithKerberos() throws Exception {
     // security on, default is remove default realm
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
     SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
     UserGroupInformation.setConfiguration(conf);
 
     testConstructorSuccess("user1", "user1");
     testConstructorSuccess("user2@DEFAULT.REALM", "user2");
-    testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");    
+    testConstructorSuccess("user3/cron@DEFAULT.REALM", "user3");
+
     // failure test
     testConstructorFailures("user4@OTHER.REALM");
     testConstructorFailures("user5/cron@OTHER.REALM");
+
+    // with MIT
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "mit");
+    UserGroupInformation.setConfiguration(conf);
+    testConstructorSuccess("user4@OTHER.REALM", "user4@OTHER.REALM");
+    testConstructorSuccess("user5/cron@OTHER.REALM", "user5/cron@OTHER.REALM");
+
+    // failures
     testConstructorFailures(null);
     testConstructorFailures("");
+
+    conf.set(HADOOP_SECURITY_AUTH_TO_LOCAL_MECHANISM, "hadoop");
+
   }
 
   /** test constructor */
@@ -393,8 +422,9 @@
     } catch (IllegalArgumentException e) {
       String expect = (userName == null || userName.isEmpty())
           ? "Null user" : "Illegal principal name "+userName;
-      assertTrue("Did not find "+ expect + " in " + e,
-          e.toString().contains(expect));
+      String expect2 = "Malformed Kerberos name: "+userName;
+      assertTrue("Did not find "+ expect + " or " + expect2 + " in " + e,
+          e.toString().contains(expect) || e.toString().contains(expect2));
     }
   }