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, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.security;
019
020import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
021import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION;
022import static org.apache.hadoop.util.PlatformName.IBM_JAVA;
023
024import java.io.File;
025import java.io.IOException;
026import java.lang.reflect.UndeclaredThrowableException;
027import java.net.URL;
028import java.security.AccessControlContext;
029import java.security.AccessController;
030import java.security.Principal;
031import java.security.PrivilegedAction;
032import java.security.PrivilegedActionException;
033import java.security.PrivilegedExceptionAction;
034
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.Iterator;
041import java.util.LinkedHashSet;
042import java.util.List;
043import java.util.Locale;
044import java.util.Map;
045import java.util.Set;
046import javax.security.auth.Subject;
047import javax.security.auth.kerberos.KerberosPrincipal;
048import javax.security.auth.kerberos.KerberosTicket;
049import javax.security.auth.kerberos.KeyTab;
050import javax.security.auth.login.AppConfigurationEntry;
051import javax.security.auth.login.LoginContext;
052import javax.security.auth.login.LoginException;
053
054import org.apache.commons.logging.Log;
055import org.apache.commons.logging.LogFactory;
056import org.apache.hadoop.classification.InterfaceAudience;
057import org.apache.hadoop.classification.InterfaceStability;
058import org.apache.hadoop.classification.MapRModified;
059import org.apache.hadoop.conf.Configuration;
060import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
061import org.apache.hadoop.io.Text;
062import org.apache.hadoop.metrics2.annotation.Metric;
063import org.apache.hadoop.metrics2.annotation.Metrics;
064import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
065import org.apache.hadoop.metrics2.lib.MetricsRegistry;
066import org.apache.hadoop.metrics2.lib.MutableQuantiles;
067import org.apache.hadoop.metrics2.lib.MutableRate;
068import org.apache.hadoop.security.SaslRpcServer.AuthMethod;
069import org.apache.hadoop.security.login.HadoopLoginModule;
070import org.apache.hadoop.security.rpcauth.RpcAuthMethod;
071import org.apache.hadoop.security.rpcauth.RpcAuthRegistry;
072import org.apache.hadoop.security.token.Token;
073import org.apache.hadoop.security.token.TokenIdentifier;
074import org.apache.hadoop.util.Shell;
075import org.apache.hadoop.util.Time;
076
077
078import com.google.common.annotations.VisibleForTesting;
079
080/**
081 * User and group information for Hadoop.
082 * This class wraps around a JAAS Subject and provides methods to determine the
083 * user's username and groups. It supports both the Windows, Unix and Kerberos 
084 * login modules.
085 */
086@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "HBase", "Hive", "Oozie"})
087@InterfaceStability.Evolving
088@MapRModified
089public class UserGroupInformation {
090  private static final Log LOG =  LogFactory.getLog(UserGroupInformation.class);
091  /**
092   * Percentage of the ticket window to use before we renew ticket.
093   */
094  private static final float TICKET_RENEW_WINDOW = 0.80f;
095  private static boolean shouldRenewImmediatelyForTests = false;
096  static final String HADOOP_USER_NAME = "HADOOP_USER_NAME";
097  static final String HADOOP_PROXY_USER = "HADOOP_PROXY_USER";
098
099  /**
100   * For the purposes of unit tests, we want to test login
101   * from keytab and don't want to wait until the renew
102   * window (controlled by TICKET_RENEW_WINDOW).
103   * @param immediate true if we should login without waiting for ticket window
104   */
105  @VisibleForTesting
106  static void setShouldRenewImmediatelyForTests(boolean immediate) {
107    shouldRenewImmediatelyForTests = immediate;
108  }
109
110  // Begin MapR section
111  private static final String HADOOP_SECURITY_SPOOF_USER = "hadoop.spoof.user";
112  private static final String HADOOP_SECURITY_SPOOFED_USER = "hadoop.spoofed.user.username";
113  private static boolean spoofUser = false;
114  private static String spoofedUser;
115
116  private static void checkSpoofing(Configuration conf) {
117    // spoof users by default on Windows
118    spoofUser = conf.getBoolean(HADOOP_SECURITY_SPOOF_USER,
119        windows ? true : false);
120
121    if (!spoofUser) {
122      return;
123    }
124
125    spoofedUser = conf.get(HADOOP_SECURITY_SPOOFED_USER, "root");
126  }
127  // End MapR section
128
129  /** 
130   * UgiMetrics maintains UGI activity statistics
131   * and publishes them through the metrics interfaces.
132   */
133  @Metrics(about="User and group related metrics", context="ugi")
134  static class UgiMetrics {
135    final MetricsRegistry registry = new MetricsRegistry("UgiMetrics");
136
137    @Metric("Rate of successful kerberos logins and latency (milliseconds)")
138    MutableRate loginSuccess;
139    @Metric("Rate of failed kerberos logins and latency (milliseconds)")
140    MutableRate loginFailure;
141    @Metric("GetGroups") MutableRate getGroups;
142    MutableQuantiles[] getGroupsQuantiles;
143
144    static UgiMetrics create() {
145      return DefaultMetricsSystem.instance().register(new UgiMetrics());
146    }
147
148    void addGetGroups(long latency) {
149      getGroups.add(latency);
150      if (getGroupsQuantiles != null) {
151        for (MutableQuantiles q : getGroupsQuantiles) {
152          q.add(latency);
153        }
154      }
155    }
156  }
157
158  /** Metrics to track UGI activity */
159  static UgiMetrics metrics = UgiMetrics.create();
160  /** The auth method to use */
161  private static AuthenticationMethod authenticationMethod;
162  /** Server-side groups fetching service */
163  private static Groups groups;
164  /** The configuration to use */
165  private static Configuration conf;
166
167  /** JAAS configuration name to be used for user login */
168  private static String userJAASConfName;
169
170  /** JAAS configuration name to be used for service login */
171  private static String serviceJAASConfName;
172  
173  /** Leave 10 minutes between relogin attempts. */
174  private static final long MIN_TIME_BEFORE_RELOGIN = 10 * 60 * 1000L;
175  
176  /**Environment variable pointing to the token cache file*/
177  public static final String HADOOP_TOKEN_FILE_LOCATION = 
178    "HADOOP_TOKEN_FILE_LOCATION";
179
180  private static Class<? extends Principal> customAuthPrincipalClass;
181
182  private static Class<? extends RpcAuthMethod> customRpcAuthMethodClass;
183
184  // mapr_extensibility
185  public static final String USER_TICKET_FILE_LOCATION = "MAPR_TICKETFILE_LOCATION";
186
187  private static final String JAVA_SECURITY_AUTH_LOGIN_CONFIG = "java.security.auth.login.config";
188
189  /** 
190   * A method to initialize the fields that depend on a configuration.
191   * Must be called before useKerberos or groups is used.
192   */
193  private static void ensureInitialized() {
194    if (conf == null) {
195      synchronized(UserGroupInformation.class) {
196        if (conf == null) { // someone might have beat us
197          initialize(new Configuration(), false);
198        }
199      }
200    }
201  }
202
203  /**
204   * Initialize UGI and related classes.
205   * @param conf the configuration to use
206   */
207  private static synchronized void initialize(Configuration conf,
208                                              boolean overrideNameRules) {
209    AuthenticationMethod authenticationMethodFromConf = getAuthenticationMethodFromConfiguration(conf);
210    if (LOG.isDebugEnabled())
211      LOG.debug("HADOOP_SECURITY_AUTHENTICATION is set to: " + authenticationMethodFromConf);
212
213    // spoofing is only allowed when insecure
214    if (authenticationMethodFromConf == null || authenticationMethodFromConf.equals(AuthenticationMethod.SIMPLE)) {
215      checkSpoofing(conf);
216    } else {
217      // Initialize custom auth method.
218      // TODO This code really belongs to RpcAuthRegistry.
219      customAuthPrincipalClass = SecurityUtil.getCustomAuthPrincipal(conf);
220      customRpcAuthMethodClass = SecurityUtil.getCustomRpcAuthMethod(conf);
221    }
222
223    String jaasConfName = null;
224    if (authenticationMethodFromConf == AuthenticationMethod.SIMPLE) {
225      jaasConfName = "simple";
226    } else {
227      LOG.debug("Security is enabled.");
228      // ignore what is passed in and instead honor extra configuration
229      // or JVM setting if neither set, use default.
230      jaasConfName = System.getProperty("hadoop.login");
231      if (jaasConfName == null) {
232        jaasConfName = conf.get("hadoop.login", "default");
233      }
234    }
235
236    userJAASConfName = jaasConfName.startsWith("hadoop_")
237        ? jaasConfName : "hadoop_" + jaasConfName;
238    serviceJAASConfName = userJAASConfName.endsWith("_keytab")
239        ? userJAASConfName : userJAASConfName + "_keytab";
240
241    if (LOG.isDebugEnabled())
242      LOG.debug("Login configuration entry is " + userJAASConfName);
243
244    String loginConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
245    
246    if (loginConfPath == null) {
247      loginConfPath = conf.get(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
248      if (loginConfPath != null ) {
249        System.setProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG, loginConfPath);
250        loginConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
251        LOG.info("Java System property 'java.security.auth.login.config' not"
252            + " set, unilaterally setting to " + loginConfPath);
253      }
254    }
255    // still can be null
256    if ( loginConfPath == null || !(new File(loginConfPath)).canRead() ) {
257      if ( LOG.isDebugEnabled()) {
258        LOG.debug(loginConfPath + " either null or can not be read. Trying to load " 
259        + "'java.security.auth.login.config' from jar");
260      }
261      String javaSecurityJarPath = 
262          conf.get(CommonConfigurationKeysPublic.HADOOP_SECURITY_JAVA_SECURITY_JAR_PATH);
263      URL javaSecurityURL = null;
264      if ( javaSecurityJarPath != null ) {
265        javaSecurityURL = UserGroupInformation.class.getResource(javaSecurityJarPath); 
266        if ( javaSecurityURL != null ) {
267          loginConfPath = javaSecurityURL.toExternalForm();
268          System.setProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG, loginConfPath);
269          if ( LOG.isDebugEnabled()) {
270            LOG.debug("Loading 'java.security.auth.login.config' from: " + loginConfPath);
271          }
272        } 
273      } 
274      if (javaSecurityJarPath == null || javaSecurityURL == null ) {
275        LOG.warn("'java.security.auth.login.config' is not"
276            + " configured either in Hadoop configuration or"
277            + " via Java property or not loaded from jar, may cause login failure");
278      }  
279    }
280    // by this time JAAS config file is fully loaded. Set UGI's authenticationMethod from JAAS config.
281    setUGIAuthenticationMethodFromJAASConfiguration(jaasConfName);
282
283    if (overrideNameRules || !HadoopKerberosName.hasRulesBeenSet()) {
284      try {
285        HadoopKerberosName.setConfiguration(conf);
286      } catch (IOException ioe) {
287        throw new RuntimeException(
288            "Problem with Kerberos auth_to_local name configuration", ioe);
289      }
290    }
291    // If we haven't set up testing groups, use the configuration to find it
292    if (!(groups instanceof TestingGroups)) {
293      groups = Groups.getUserToGroupsMappingService(conf);
294    }
295    UserGroupInformation.conf = conf;
296
297    if (metrics.getGroupsQuantiles == null) {
298      int[] intervals = conf.getInts(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS);
299      if (intervals != null && intervals.length > 0) {
300        final int length = intervals.length;
301        MutableQuantiles[] getGroupsQuantiles = new MutableQuantiles[length];
302        for (int i = 0; i < length; i++) {
303          getGroupsQuantiles[i] = metrics.registry.newQuantiles(
304            "getGroups" + intervals[i] + "s",
305            "Get groups", "ops", "latency", intervals[i]);
306        }
307        metrics.getGroupsQuantiles = getGroupsQuantiles;
308      }
309    }
310  }
311
312  private static AuthenticationMethod getAuthenticationMethodFromConfiguration(Configuration conf) {
313    String value = conf.get(HADOOP_SECURITY_AUTHENTICATION, "simple");
314    try {
315      return Enum.valueOf(AuthenticationMethod.class, value.toUpperCase(Locale.ENGLISH));
316    } catch (IllegalArgumentException iae) {
317      throw new IllegalArgumentException("Invalid attribute value for " +
318          HADOOP_SECURITY_AUTHENTICATION + " of " + value);
319    }
320  }
321
322  /**
323   * The only supported options in JAAS config file (currently) are SIMPLE, KERBEROS and CUSTOM.
324   *
325   * DIGEST/TOKEN is internal to hadoop. PROXY is used during impersonation. These two options
326   * are therefore not never returned via this method.
327   */
328  private static void setUGIAuthenticationMethodFromJAASConfiguration(String jaasConfName) {
329    if (jaasConfName.contains("simple")) {
330      UserGroupInformation.authenticationMethod = AuthenticationMethod.SIMPLE;
331    } else if (jaasConfName.contains("kerberos")) {
332      UserGroupInformation.authenticationMethod = AuthenticationMethod.KERBEROS;
333    } else { // if it's not SIMPLE and not KERBEROS, treat it as CUSTOM
334      UserGroupInformation.authenticationMethod = AuthenticationMethod.CUSTOM;
335    }
336    if (LOG.isDebugEnabled()) {
337      LOG.debug("authenticationMethod from JAAS configuration:" + UserGroupInformation.authenticationMethod);
338    }
339  }
340
341  /**
342   * Returns authenticationMethod obtained by inspecting JAAS configuration.
343   * @return
344   */
345  public static AuthenticationMethod getUGIAuthenticationMethod() {
346    if (UserGroupInformation.authenticationMethod == null) {
347      ensureInitialized();
348    }
349    return UserGroupInformation.authenticationMethod;
350  }
351
352  /**
353   * Set the static configuration for UGI.
354   * In particular, set the security authentication mechanism and the
355   * group look up service.
356   * @param conf the configuration to use
357   */
358  @InterfaceAudience.Public
359  @InterfaceStability.Evolving
360  public static void setConfiguration(Configuration conf) {
361    initialize(conf, true);
362  }
363  
364  @InterfaceAudience.Private
365  @VisibleForTesting
366  static void reset() {
367    authenticationMethod = null;
368    conf = null;
369    groups = null;
370    setLoginUser(null);
371    HadoopKerberosName.setRules(null);
372  }
373  
374  /**
375   * Determine if UserGroupInformation is using Kerberos to determine
376   * user identities or is relying on simple authentication
377   * 
378   * @return true if UGI is working in a secure environment
379   */
380  public static boolean isSecurityEnabled() {
381    return !isAuthenticationMethodEnabled(AuthenticationMethod.SIMPLE);
382  }
383  
384  @InterfaceAudience.Private
385  @InterfaceStability.Evolving
386  private static boolean isAuthenticationMethodEnabled(AuthenticationMethod method) {
387    ensureInitialized();
388    return (authenticationMethod == method);
389  }
390  
391  /**
392   * Information about the logged in user.
393   */
394  private static UserGroupInformation loginUser = null;
395  private static String keytabPrincipal = null;
396  private static String keytabFile = null;
397
398  private final Subject subject;
399  // All non-static fields must be read-only caches that come from the subject.
400  private final User user;
401  private final boolean isKeytab;
402  private final boolean isKrbTkt;
403  private List<RpcAuthMethod> rpcAuthMethodList;
404
405  private static final boolean windows = 
406      System.getProperty("os.name").startsWith("Windows");
407
408  private static class RealUser implements Principal {
409    private final UserGroupInformation realUser;
410    
411    RealUser(UserGroupInformation realUser) {
412      this.realUser = realUser;
413    }
414    
415    @Override
416    public String getName() {
417      return realUser.getUserName();
418    }
419    
420    public UserGroupInformation getRealUser() {
421      return realUser;
422    }
423    
424    @Override
425    public boolean equals(Object o) {
426      if (this == o) {
427        return true;
428      } else if (o == null || getClass() != o.getClass()) {
429        return false;
430      } else {
431        return realUser.equals(((RealUser) o).realUser);
432      }
433    }
434    
435    @Override
436    public int hashCode() {
437      return realUser.hashCode();
438    }
439    
440    @Override
441    public String toString() {
442      return realUser.toString();
443    }
444  }
445
446  private static LoginContext
447  newLoginContext(String appName, Subject subject,
448    Map<String,?> overrideOptions)
449      throws LoginException {
450    // Temporarily switch the thread's ContextClassLoader to match this
451    // class's classloader, so that we can properly load HadoopLoginModule
452    // from the JAAS libraries.
453    Thread t = Thread.currentThread();
454    ClassLoader oldCCL = t.getContextClassLoader();
455    t.setContextClassLoader(HadoopLoginModule.class.getClassLoader());
456    try {
457      if (overrideOptions != null) {
458        javax.security.auth.login.Configuration cfg =
459            new DynamicLoginConfiguration(getJAASConf(), overrideOptions);
460        return new LoginContext(appName, subject, null, cfg);
461      }
462      return new LoginContext(appName, subject);
463    } finally {
464      t.setContextClassLoader(oldCCL);
465    }
466  }
467
468  private static LoginContext newLoginContextWithKeyTab(String appName,
469      Subject subject, String keytab, String principal) throws LoginException {
470    Map<String,String> overrideOptions = new HashMap<String, String>();
471    overrideOptions.put("keyTab", keytab);
472    overrideOptions.put("principal", principal);
473    return newLoginContext(appName, subject, overrideOptions);
474  }
475
476  private static javax.security.auth.login.Configuration getJAASConf() {
477    return javax.security.auth.login.Configuration.getConfiguration();
478  }
479
480  private LoginContext getLogin() {
481    return user.getLogin();
482  }
483  
484  private void setLogin(LoginContext login) {
485    user.setLogin(login);
486  }
487
488  private void configureRpcAuthMethods(String confName) {
489    ArrayList<RpcAuthMethod> authMethodList = new ArrayList<RpcAuthMethod>();
490    if (isSecurityEnabled() && confName != null) {
491      Set<RpcAuthMethod> authMethods = new LinkedHashSet<RpcAuthMethod>();
492      AppConfigurationEntry[] appInfo =
493          getJAASConf().getAppConfigurationEntry(confName);
494      for (int i = 0; appInfo != null && i < appInfo.length; i++) {
495        String module = appInfo[i].getLoginModuleName();
496        RpcAuthMethod rpcAuthMethod = RpcAuthRegistry.getAuthMethodForLoginModule(module);
497        if (rpcAuthMethod != null) {
498          authMethods.add(rpcAuthMethod);
499        }
500      }
501
502      if (isSecurityEnabled()
503          && !"hadoop_simple".equals(confName)
504          && authMethods.size() == 0) {
505        LOG.warn("Security is enabled but no suitable RPC authentication "
506            + "method is found in the provided JAAS configuration: " + confName);
507      }
508      authMethodList.addAll(authMethods);
509    } else {
510      authMethodList.add(RpcAuthRegistry.SIMPLE);
511    }
512
513    this.rpcAuthMethodList = Collections.unmodifiableList(authMethodList);
514  }
515
516  /**
517   * Create a UserGroupInformation for the given subject.
518   * This does not change the subject or acquire new credentials.
519   * @param subject the user's subject
520   */
521  UserGroupInformation(Subject subject) {
522    this(subject, userJAASConfName);
523  }
524
525  UserGroupInformation(Subject subject, String loginConfName) {
526    this.subject = subject;
527    this.user = subject.getPrincipals(User.class).iterator().next();
528    this.isKeytab = !subject.getPrivateCredentials(KeyTab.class).isEmpty();
529    this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
530    configureRpcAuthMethods(loginConfName);
531
532    /* Since multiple methods are allowed at once, this isn't a great setting.
533     * We just prefer the dominant method. This concept really should be removed.
534     * Better to have the login modules set the methods of authentication based
535     * upon some common mapping or even callers just look directly at the subject
536     * to determine what is in it.
537     */
538    if (!subject.getPrincipals(KerberosPrincipal.class).isEmpty()) {
539      this.user.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
540      LOG.debug("found Kerberos Principal in subject, marking as such");
541    } else if (customAuthPrincipalClass != null && !subject.getPrincipals(customAuthPrincipalClass).isEmpty()) {
542      this.user.setAuthenticationMethod(AuthenticationMethod.CUSTOM);
543      LOG.debug("found custom auth principal " + customAuthPrincipalClass.getName() + "  in subject. " +
544          "marking authentication method as " + AuthenticationMethod.CUSTOM);
545    }
546  }
547  
548  /**
549   * checks if logged in using kerberos
550   * @return true if the subject logged via keytab or has a Kerberos TGT
551   */
552  public boolean hasKerberosCredentials() {
553    return isKeytab || isKrbTkt;
554  }
555
556  public List<RpcAuthMethod> getRpcAuthMethodList() {
557    return rpcAuthMethodList;
558  }
559
560  /**
561   * Return the current user, including any doAs in the current stack.
562   * @return the current user
563   * @throws IOException if login fails
564   */
565  @InterfaceAudience.Public
566  @InterfaceStability.Evolving
567  public synchronized
568  static UserGroupInformation getCurrentUser() throws IOException {
569    AccessControlContext context = AccessController.getContext();
570    Subject subject = Subject.getSubject(context);
571    if (subject == null || subject.getPrincipals(User.class).isEmpty()) {
572      return getLoginUser();
573    } else {
574      return new UserGroupInformation(subject);
575    }
576  }
577
578  /**
579   * Find the most appropriate UserGroupInformation to use
580   *
581   * @param ticketCachePath    The Kerberos ticket cache path, or NULL
582   *                           if none is specfied
583   * @param user               The user name, or NULL if none is specified.
584   *
585   * @return                   The most appropriate UserGroupInformation
586   */ 
587  public static UserGroupInformation getBestUGI(
588      String ticketCachePath, String user) throws IOException {
589    if (ticketCachePath != null) {
590      return getUGIFromTicketCache(ticketCachePath, user);
591    } else if (user == null) {
592      return getCurrentUser();
593    } else {
594      return createRemoteUser(user);
595    }    
596  }
597
598  /**
599   * Create a UserGroupInformation from a Kerberos ticket cache.
600   * 
601   * @param user                The principal name to load from the ticket
602   *                            cache
603   * @param ticketCachePath     the path to the ticket cache file
604   *
605   * @throws IOException        if the kerberos login fails
606   */
607  @InterfaceAudience.Public
608  @InterfaceStability.Evolving
609  public static UserGroupInformation getUGIFromTicketCache(
610            String ticketCache, String user) throws IOException {
611    if (!isAuthenticationMethodEnabled(AuthenticationMethod.KERBEROS)) {
612      return getBestUGI(null, user);
613    }
614    try {
615      Map<String,String> krbOptions = new HashMap<String,String>();
616      if (IBM_JAVA) {
617        krbOptions.put("useDefaultCcache", "true");
618        // The first value searched when "useDefaultCcache" is used.
619        System.setProperty("KRB5CCNAME", ticketCache);
620      } else {
621        krbOptions.put("ticketCache", ticketCache);
622      }
623      krbOptions.put("renewTGT", "false");
624      LoginContext login = newLoginContext(userJAASConfName, null, krbOptions);
625      login.login();
626
627      Subject loginSubject = login.getSubject();
628      Set<Principal> loginPrincipals = loginSubject.getPrincipals();
629      if (loginPrincipals.isEmpty()) {
630        throw new RuntimeException("No login principals found!");
631      }
632      if (loginPrincipals.size() != 1) {
633        LOG.warn("found more than one principal in the ticket cache file " +
634          ticketCache);
635      }
636      User ugiUser = new User(loginPrincipals.iterator().next().getName(),
637          AuthenticationMethod.KERBEROS, login);
638      loginSubject.getPrincipals().add(ugiUser);
639      UserGroupInformation ugi = new UserGroupInformation(loginSubject);
640      ugi.setLogin(login);
641      ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
642      return ugi;
643    } catch (LoginException le) {
644      throw new IOException("failure to login using ticket cache file " +
645          ticketCache, le);
646    }
647  }
648
649   /**
650   * Create a UserGroupInformation from a Subject with Kerberos principal.
651   *
652   * @param user                The KerberosPrincipal to use in UGI
653   *
654   * @throws IOException        if the kerberos login fails
655   */
656  public static UserGroupInformation getUGIFromSubject(Subject subject)
657      throws IOException {
658    if (subject == null) {
659      throw new IOException("Subject must not be null");
660    }
661
662    if (subject.getPrincipals(KerberosPrincipal.class).isEmpty()) {
663      throw new IOException("Provided Subject must contain a KerberosPrincipal");
664    }
665
666    KerberosPrincipal principal =
667        subject.getPrincipals(KerberosPrincipal.class).iterator().next();
668
669    User ugiUser = new User(principal.getName(),
670        AuthenticationMethod.KERBEROS, null);
671    subject.getPrincipals().add(ugiUser);
672    UserGroupInformation ugi = new UserGroupInformation(subject);
673    ugi.setLogin(null);
674    ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
675    return ugi;
676  }
677
678  /**
679   * Get the currently logged in user.
680   * @return the logged in user
681   * @throws IOException if login fails
682   */
683  @InterfaceAudience.Public
684  @InterfaceStability.Evolving
685  public synchronized 
686  static UserGroupInformation getLoginUser() throws IOException {
687    if (loginUser == null) {
688      loginUserFromSubject(null);
689    }
690    return loginUser;
691  }
692
693  /**
694   * remove the login method that is followed by a space from the username
695   * e.g. "jack (auth:SIMPLE)" -> "jack"
696   *
697   * @param userName
698   * @return userName without login method
699   */
700  public static String trimLoginMethod(String userName) {
701    int spaceIndex = userName.indexOf(' ');
702    if (spaceIndex >= 0) {
703      userName = userName.substring(0, spaceIndex);
704    }
705    return userName;
706  }
707
708  /**
709   * Log in a user using the given subject
710   * @parma subject the subject to use when logging in a user, or null to 
711   * create a new subject.
712   * @throws IOException if login fails
713   */
714  @InterfaceAudience.Public
715  @InterfaceStability.Evolving
716  public synchronized 
717  static void loginUserFromSubject(Subject subject) throws IOException {
718    ensureInitialized();
719    try {
720      if (subject == null) {
721        subject = new Subject();
722      }
723      LoginContext login = newLoginContext(userJAASConfName, subject, null);
724      login.login();
725      UserGroupInformation realUser = new UserGroupInformation(subject, userJAASConfName);
726      realUser.setLogin(login);
727      // AuthenticationMethod is now computed based upon subject
728      // loginUser.setAuthenticationMethod(...);
729      setUserAuthenticationMethod(realUser);
730      realUser = new UserGroupInformation(login.getSubject(), userJAASConfName);
731      // If the HADOOP_PROXY_USER environment variable or property
732      // is specified, create a proxy user as the logged in user.
733      String proxyUser = System.getenv(HADOOP_PROXY_USER);
734      if (proxyUser == null) {
735        proxyUser = System.getProperty(HADOOP_PROXY_USER);
736      }
737      loginUser = proxyUser == null ? realUser : createProxyUser(proxyUser, realUser);
738
739      String fileLocation = System.getenv(HADOOP_TOKEN_FILE_LOCATION);
740      if (fileLocation != null) {
741        // Load the token storage file and put all of the tokens into the
742        // user. Don't use the FileSystem API for reading since it has a lock
743        // cycle (HADOOP-9212).
744        Credentials cred = Credentials.readTokenStorageFile(
745            new File(fileLocation), conf);
746        loginUser.addCredentials(cred);
747      }
748      loginUser.spawnAutoRenewalThreadForUserCreds();
749    } catch (LoginException le) {
750      LOG.debug("failure to login", le);
751      throw new IOException("failure to login: " + le.getMessage(), le);
752    }
753    if (LOG.isDebugEnabled()) {
754      LOG.debug("UGI loginUser:"+loginUser);
755    }
756  }
757
758  private static void setUserAuthenticationMethod(UserGroupInformation realUser) {
759    if (realUser.user.getAuthenticationMethod() == null) { // if the user's auth method is not set from subject
760      switch (authenticationMethod) {                      // set it from configured authenticationMethod
761        case TOKEN:
762        case CERTIFICATE:
763        case KERBEROS_SSL:
764        case PROXY:
765          throw new UnsupportedOperationException(
766              authenticationMethod + " login authentication is not supported");
767        default:
768          LOG.debug("Found no authentication principals in subject. Simple?");
769          realUser.user.setAuthenticationMethod(authenticationMethod);
770      }
771    }
772  }
773
774  @InterfaceAudience.Private
775  @InterfaceStability.Unstable
776  @VisibleForTesting
777  public synchronized static void setLoginUser(UserGroupInformation ugi) {
778    // if this is to become stable, should probably logout the currently
779    // logged in ugi if it's different
780    loginUser = ugi;
781  }
782  
783  /**
784   * Is this user logged in from a keytab file?
785   * @return true if the credentials are from a keytab file.
786   */
787  public boolean isFromKeytab() {
788    return isKeytab;
789  }
790  
791  /**
792   * Get the Kerberos TGT
793   * @return the user's TGT or null if none was found
794   */
795  private synchronized KerberosTicket getTGT() {
796    Set<KerberosTicket> tickets = subject
797        .getPrivateCredentials(KerberosTicket.class);
798    for (KerberosTicket ticket : tickets) {
799      if (SecurityUtil.isOriginalTGT(ticket)) {
800        if (LOG.isDebugEnabled()) {
801          LOG.debug("Found tgt " + ticket);
802        }
803        return ticket;
804      }
805    }
806    return null;
807  }
808  
809  private long getRefreshTime(KerberosTicket tgt) {
810    long start = tgt.getStartTime().getTime();
811    long end = tgt.getEndTime().getTime();
812    return start + (long) ((end - start) * TICKET_RENEW_WINDOW);
813  }
814
815  /**Spawn a thread to do periodic renewals of kerberos credentials*/
816  private void spawnAutoRenewalThreadForUserCreds() {
817    if (isSecurityEnabled()) {
818      //spawn thread only if we have kerb credentials
819      if (user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS &&
820          !isKeytab) {
821        Thread t = new Thread(new Runnable() {
822          
823          @Override
824          public void run() {
825            String cmd = conf.get("hadoop.kerberos.kinit.command",
826                                  "kinit");
827            KerberosTicket tgt = getTGT();
828            if (tgt == null) {
829              return;
830            }
831            long nextRefresh = getRefreshTime(tgt);
832            while (true) {
833              try {
834                long now = Time.now();
835                if(LOG.isDebugEnabled()) {
836                  LOG.debug("Current time is " + now);
837                  LOG.debug("Next refresh is " + nextRefresh);
838                }
839                if (now < nextRefresh) {
840                  Thread.sleep(nextRefresh - now);
841                }
842                Shell.execCommand(cmd, "-R");
843                if(LOG.isDebugEnabled()) {
844                  LOG.debug("renewed ticket");
845                }
846                reloginFromTicketCache();
847                tgt = getTGT();
848                if (tgt == null) {
849                  LOG.warn("No TGT after renewal. Aborting renew thread for " +
850                           getUserName());
851                  return;
852                }
853                nextRefresh = Math.max(getRefreshTime(tgt),
854                                       now + MIN_TIME_BEFORE_RELOGIN);
855              } catch (InterruptedException ie) {
856                LOG.warn("Terminating renewal thread");
857                return;
858              } catch (IOException ie) {
859                LOG.warn("Exception encountered while running the" +
860                    " renewal command. Aborting renew thread. " + ie);
861                return;
862              }
863            }
864          }
865        });
866        t.setDaemon(true);
867        t.setName("TGT Renewer for " + getUserName());
868        t.start();
869      }
870    }
871  }
872  /**
873   * Log a user in from a keytab file. Loads a user identity from a keytab
874   * file and logs them in. They become the currently logged-in user.
875   * @param user the principal name to load from the keytab
876   * @param path the path to the keytab file
877   * @throws IOException if the keytab file can't be read
878   */
879  @InterfaceAudience.Public
880  @InterfaceStability.Evolving
881  public synchronized
882  static void loginUserFromKeytab(String user,
883                                  String path
884                                  ) throws IOException {
885    if (!isSecurityEnabled())
886      return;
887
888    keytabFile = path;
889    keytabPrincipal = user;
890    Subject subject = new Subject();
891    LoginContext login; 
892    long start = 0;
893    try {
894      login = newLoginContextWithKeyTab(serviceJAASConfName,
895          subject, keytabFile, keytabPrincipal);
896      start = Time.now();
897      login.login();
898      metrics.loginSuccess.add(Time.now() - start);
899      loginUser = new UserGroupInformation(subject, serviceJAASConfName);
900      loginUser.setLogin(login);
901      // AuthenticationMethod is now computed based upon subject
902      // loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
903    } catch (LoginException le) {
904      if (start > 0) {
905        metrics.loginFailure.add(Time.now() - start);
906      }
907      throw new IOException("Login failure for " + user + " from keytab " + 
908                            path+ ": " + le, le);
909    }
910    LOG.info("Login successful for user " + keytabPrincipal
911        + " using keytab file " + keytabFile);
912  }
913  
914  /**
915   * Re-login a user from keytab if TGT is expired or is close to expiry.
916   * 
917   * @throws IOException
918   */
919  public synchronized void checkTGTAndReloginFromKeytab() throws IOException {
920    if (!isSecurityEnabled()
921        || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
922        || !isKeytab)
923      return;
924    KerberosTicket tgt = getTGT();
925    if (tgt != null && !shouldRenewImmediatelyForTests &&
926        Time.now() < getRefreshTime(tgt)) {
927      return;
928    }
929    reloginFromKeytab();
930  }
931
932  /**
933   * Re-Login a user in from a keytab file. Loads a user identity from a keytab
934   * file and logs them in. They become the currently logged-in user. This
935   * method assumes that {@link #loginUserFromKeytab(String, String)} had 
936   * happened already.
937   * The Subject field of this UserGroupInformation object is updated to have
938   * the new credentials.
939   * @throws IOException on a failure
940   */
941  @InterfaceAudience.Public
942  @InterfaceStability.Evolving
943  public synchronized void reloginFromKeytab()
944  throws IOException {
945    if (!isSecurityEnabled() ||
946         user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
947         !isKeytab)
948      return;
949    
950    long now = Time.now();
951    if (!shouldRenewImmediatelyForTests && !hasSufficientTimeElapsed(now)) {
952      return;
953    }
954
955    KerberosTicket tgt = getTGT();
956    //Return if TGT is valid and is not going to expire soon.
957    if (tgt != null && !shouldRenewImmediatelyForTests &&
958        now < getRefreshTime(tgt)) {
959      return;
960    }
961    
962    LoginContext login = getLogin();
963    if (login == null || keytabFile == null) {
964      throw new IOException("loginUserFromKeyTab must be done first");
965    }
966    
967    long start = 0;
968    // register most recent relogin attempt
969    user.setLastLogin(now);
970    try {
971      if (LOG.isDebugEnabled()) {
972        LOG.debug("Initiating logout for " + getUserName());
973      }
974      synchronized (UserGroupInformation.class) {
975        // clear up the kerberos state. But the tokens are not cleared! As per
976        // the Java kerberos login module code, only the kerberos credentials
977        // are cleared
978        login.logout();
979        // login and also update the subject field of this instance to
980        // have the new credentials (pass it to the LoginContext constructor)
981        login = newLoginContextWithKeyTab(serviceJAASConfName, getSubject(),
982                                          keytabFile, keytabPrincipal);
983        LOG.info("Initiating re-login for " + keytabPrincipal);
984        start = Time.now();
985        login.login();
986        metrics.loginSuccess.add(Time.now() - start);
987        setLogin(login);
988      }
989    } catch (LoginException le) {
990      if (start > 0) {
991        metrics.loginFailure.add(Time.now() - start);
992      }
993      throw new IOException("Login failure for " + keytabPrincipal + 
994          " from keytab " + keytabFile, le);
995    } 
996  }
997
998  /**
999   * Re-Login a user in from the ticket cache.  This
1000   * method assumes that login had happened already.
1001   * The Subject field of this UserGroupInformation object is updated to have
1002   * the new credentials.
1003   * @throws IOException on a failure
1004   */
1005  @InterfaceAudience.Public
1006  @InterfaceStability.Evolving
1007  public synchronized void reloginFromTicketCache()
1008  throws IOException {
1009    if (!isSecurityEnabled() || 
1010        user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
1011        !isKrbTkt)
1012      return;
1013    LoginContext login = getLogin();
1014    if (login == null) {
1015      throw new IOException("login must be done first");
1016    }
1017    long now = Time.now();
1018    if (!hasSufficientTimeElapsed(now)) {
1019      return;
1020    }
1021    // register most recent relogin attempt
1022    user.setLastLogin(now);
1023    try {
1024      if (LOG.isDebugEnabled()) {
1025        LOG.debug("Initiating logout for " + getUserName());
1026      }
1027      //clear up the kerberos state. But the tokens are not cleared! As per 
1028      //the Java kerberos login module code, only the kerberos credentials
1029      //are cleared
1030      login.logout();
1031      //login and also update the subject field of this instance to 
1032      //have the new credentials (pass it to the LoginContext constructor)
1033      login = newLoginContext(userJAASConfName, getSubject(), null);
1034      LOG.info("Initiating re-login for " + getUserName());
1035      login.login();
1036      setLogin(login);
1037    } catch (LoginException le) {
1038      throw new IOException("Login failure for " + getUserName(), le);
1039    } 
1040  }
1041
1042
1043  /**
1044   * Log a user in from a keytab file. Loads a user identity from a keytab
1045   * file and login them in. This new user does not affect the currently
1046   * logged-in user.
1047   * @param user the principal name to load from the keytab
1048   * @param path the path to the keytab file
1049   * @throws IOException if the keytab file can't be read
1050   */
1051  public synchronized
1052  static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
1053                                  String path
1054                                  ) throws IOException {
1055    if (!isSecurityEnabled())
1056      return UserGroupInformation.getCurrentUser();
1057    String oldKeytabFile = null;
1058    String oldKeytabPrincipal = null;
1059
1060    long start = 0;
1061    try {
1062      oldKeytabFile = keytabFile;
1063      oldKeytabPrincipal = keytabPrincipal;
1064      keytabFile = path;
1065      keytabPrincipal = user;
1066      Subject subject = new Subject();
1067      
1068      LoginContext login = newLoginContextWithKeyTab(serviceJAASConfName, subject,
1069                                                     keytabFile, keytabPrincipal);
1070       
1071      start = Time.now();
1072      login.login();
1073      metrics.loginSuccess.add(Time.now() - start);
1074      UserGroupInformation newLoginUser =
1075          new UserGroupInformation(subject, serviceJAASConfName);
1076      newLoginUser.setLogin(login);
1077      // AuthenticationMethod is now computed based upon subject
1078      // newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
1079      return newLoginUser;
1080    } catch (LoginException le) {
1081      if (start > 0) {
1082        metrics.loginFailure.add(Time.now() - start);
1083      }
1084      throw new IOException("Login failure for " + user + " from keytab " + 
1085                            path, le);
1086    } finally {
1087      if(oldKeytabFile != null) keytabFile = oldKeytabFile;
1088      if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal;
1089    }
1090  }
1091
1092  private boolean hasSufficientTimeElapsed(long now) {
1093    if (now - user.getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
1094      LOG.warn("Not attempting to re-login since the last re-login was " +
1095          "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+
1096          " before.");
1097      return false;
1098    }
1099    return true;
1100  }
1101  
1102  /**
1103   * Did the login happen via keytab
1104   * @return true or false
1105   */
1106  @InterfaceAudience.Public
1107  @InterfaceStability.Evolving
1108  public synchronized static boolean isLoginKeytabBased() throws IOException {
1109    return getLoginUser().isKeytab;
1110  }
1111
1112  /**
1113   * Did the login happen via ticket cache
1114   * @return true or false
1115   */
1116  public static boolean isLoginTicketBased()  throws IOException {
1117    return getLoginUser().isKrbTkt;
1118  }
1119
1120  /**
1121   * Create a user from a login name. It is intended to be used for remote
1122   * users in RPC, since it won't have any credentials.
1123   * @param user the full user principal name, must not be empty or null
1124   * @return the UserGroupInformation for the remote user.
1125   */
1126  @InterfaceAudience.Public
1127  @InterfaceStability.Evolving
1128  public static UserGroupInformation createRemoteUser(String user) {
1129    return createRemoteUser(user, AuthMethod.SIMPLE);
1130  }
1131  
1132  /**
1133   * Create a user from a login name. It is intended to be used for remote
1134   * users in RPC, since it won't have any credentials.
1135   * @param user the full user principal name, must not be empty or null
1136   * @return the UserGroupInformation for the remote user.
1137   */
1138  @InterfaceAudience.Public
1139  @InterfaceStability.Evolving
1140  public static UserGroupInformation createRemoteUser(String user, AuthMethod authMethod) {
1141    if (user == null || user.isEmpty()) {
1142      throw new IllegalArgumentException("Null user");
1143    }
1144    Subject subject = new Subject();
1145    subject.getPrincipals().add(new User(user));
1146    UserGroupInformation result = new UserGroupInformation(subject);
1147    result.setAuthenticationMethod(authMethod);
1148    return result;
1149  }
1150
1151  /**
1152   * existing types of authentications' methods
1153   */
1154  @InterfaceAudience.Public
1155  @InterfaceStability.Evolving
1156  public static enum AuthenticationMethod {
1157    // currently we support only one auth per method, but eventually a 
1158    // subtype is needed to differentiate, ex. if digest is token or ldap
1159    SIMPLE(false),
1160    KERBEROS(true),
1161    TOKEN(false),
1162    CERTIFICATE(true),
1163    KERBEROS_SSL(true),
1164    PROXY(false),
1165    CUSTOM(true);
1166    
1167    private final boolean allowsDelegation;
1168    
1169    private AuthenticationMethod(boolean allowsDelegation) {
1170      this.allowsDelegation = allowsDelegation;
1171    }
1172    
1173    public boolean allowsDelegation() {
1174      return allowsDelegation;
1175    }
1176  }
1177
1178  /**
1179   * Create a proxy user using username of the effective user and the ugi of the
1180   * real user.
1181   * @param user
1182   * @param realUser
1183   * @return proxyUser ugi
1184   */
1185  @InterfaceAudience.Public
1186  @InterfaceStability.Evolving
1187  public static UserGroupInformation createProxyUser(String user,
1188      UserGroupInformation realUser) {
1189    if (user == null || user.isEmpty()) {
1190      throw new IllegalArgumentException("Null user");
1191    }
1192    if (realUser == null) {
1193      throw new IllegalArgumentException("Null real user");
1194    }
1195    Subject subject = new Subject();
1196    Set<Principal> principals = subject.getPrincipals();
1197    principals.add(new User(user));
1198    principals.add(new RealUser(realUser));
1199    UserGroupInformation result =new UserGroupInformation(subject);
1200    result.setAuthenticationMethod(AuthenticationMethod.PROXY);
1201    return result;
1202  }
1203
1204  /**
1205   * get RealUser (vs. EffectiveUser)
1206   * @return realUser running over proxy user
1207   */
1208  @InterfaceAudience.Public
1209  @InterfaceStability.Evolving
1210  public UserGroupInformation getRealUser() {
1211    for (RealUser p: subject.getPrincipals(RealUser.class)) {
1212      return p.getRealUser();
1213    }
1214    return null;
1215  }
1216
1217
1218  
1219  /**
1220   * This class is used for storing the groups for testing. It stores a local
1221   * map that has the translation of usernames to groups.
1222   */
1223  private static class TestingGroups extends Groups {
1224    private final Map<String, List<String>> userToGroupsMapping = 
1225      new HashMap<String,List<String>>();
1226    private Groups underlyingImplementation;
1227    
1228    private TestingGroups(Groups underlyingImplementation) {
1229      super(new org.apache.hadoop.conf.Configuration());
1230      this.underlyingImplementation = underlyingImplementation;
1231    }
1232    
1233    @Override
1234    public List<String> getGroups(String user) throws IOException {
1235      List<String> result = userToGroupsMapping.get(user);
1236      
1237      if (result == null) {
1238        result = underlyingImplementation.getGroups(user);
1239      }
1240
1241      return result;
1242    }
1243
1244    private void setUserGroups(String user, String[] groups) {
1245      userToGroupsMapping.put(user, Arrays.asList(groups));
1246    }
1247  }
1248
1249  /**
1250   * Create a UGI for testing HDFS and MapReduce
1251   * @param user the full user principal name
1252   * @param userGroups the names of the groups that the user belongs to
1253   * @return a fake user for running unit tests
1254   */
1255  @InterfaceAudience.Public
1256  @InterfaceStability.Evolving
1257  public static UserGroupInformation createUserForTesting(String user, 
1258                                                          String[] userGroups) {
1259    ensureInitialized();
1260    UserGroupInformation ugi = createRemoteUser(user);
1261    // make sure that the testing object is setup
1262    if (!(groups instanceof TestingGroups)) {
1263      groups = new TestingGroups(groups);
1264    }
1265    // add the user groups
1266    ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1267    return ugi;
1268  }
1269
1270
1271  /**
1272   * Create a proxy user UGI for testing HDFS and MapReduce
1273   * 
1274   * @param user
1275   *          the full user principal name for effective user
1276   * @param realUser
1277   *          UGI of the real user
1278   * @param userGroups
1279   *          the names of the groups that the user belongs to
1280   * @return a fake user for running unit tests
1281   */
1282  public static UserGroupInformation createProxyUserForTesting(String user,
1283      UserGroupInformation realUser, String[] userGroups) {
1284    ensureInitialized();
1285    UserGroupInformation ugi = createProxyUser(user, realUser);
1286    // make sure that the testing object is setup
1287    if (!(groups instanceof TestingGroups)) {
1288      groups = new TestingGroups(groups);
1289    }
1290    // add the user groups
1291    ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1292    return ugi;
1293  }
1294  
1295  /**
1296   * Get the user's login name.
1297   * @return the user's name up to the first '/' or '@'.
1298   */
1299  public String getShortUserName() {
1300    if (windows && spoofUser) {
1301      return spoofedUser;
1302    }
1303
1304    for (User p: subject.getPrincipals(User.class)) {
1305      return p.getShortName();
1306    }
1307    return null;
1308  }
1309
1310  public String getPrimaryGroupName() throws IOException {
1311    String[] groups = getGroupNames();
1312    if (groups.length == 0) {
1313      throw new IOException("There is no primary group for UGI " + this);
1314    }
1315    return groups[0];
1316  }
1317
1318  /**
1319   * Get the user's full principal name.
1320   * @return the user's full principal name.
1321   */
1322  @InterfaceAudience.Public
1323  @InterfaceStability.Evolving
1324  public String getUserName() {
1325    if (windows && spoofUser) {
1326      return spoofedUser;
1327    }
1328
1329    return user.getName();
1330  }
1331
1332  /**
1333   * Add a TokenIdentifier to this UGI. The TokenIdentifier has typically been
1334   * authenticated by the RPC layer as belonging to the user represented by this
1335   * UGI.
1336   * 
1337   * @param tokenId
1338   *          tokenIdentifier to be added
1339   * @return true on successful add of new tokenIdentifier
1340   */
1341  public synchronized boolean addTokenIdentifier(TokenIdentifier tokenId) {
1342    return subject.getPublicCredentials().add(tokenId);
1343  }
1344
1345  /**
1346   * Get the set of TokenIdentifiers belonging to this UGI
1347   * 
1348   * @return the set of TokenIdentifiers belonging to this UGI
1349   */
1350  public synchronized Set<TokenIdentifier> getTokenIdentifiers() {
1351    return subject.getPublicCredentials(TokenIdentifier.class);
1352  }
1353  
1354  /**
1355   * Add a token to this UGI
1356   * 
1357   * @param token Token to be added
1358   * @return true on successful add of new token
1359   */
1360  public boolean addToken(Token<? extends TokenIdentifier> token) {
1361    return (token != null) ? addToken(token.getService(), token) : false;
1362  }
1363
1364  /**
1365   * Add a named token to this UGI
1366   * 
1367   * @param alias Name of the token
1368   * @param token Token to be added
1369   * @return true on successful add of new token
1370   */
1371  public boolean addToken(Text alias, Token<? extends TokenIdentifier> token) {
1372    synchronized (subject) {
1373      getCredentialsInternal().addToken(alias, token);
1374      return true;
1375    }
1376  }
1377  
1378  /**
1379   * Obtain the collection of tokens associated with this user.
1380   * 
1381   * @return an unmodifiable collection of tokens associated with user
1382   */
1383  public Collection<Token<? extends TokenIdentifier>> getTokens() {
1384    synchronized (subject) {
1385      return Collections.unmodifiableCollection(
1386          new ArrayList<Token<?>>(getCredentialsInternal().getAllTokens()));
1387    }
1388  }
1389
1390  /**
1391   * Obtain the tokens in credentials form associated with this user.
1392   * 
1393   * @return Credentials of tokens associated with this user
1394   */
1395  public Credentials getCredentials() {
1396    synchronized (subject) {
1397      Credentials creds = new Credentials(getCredentialsInternal());
1398      Iterator<Token<?>> iter = creds.getAllTokens().iterator();
1399      while (iter.hasNext()) {
1400        if (iter.next() instanceof Token.PrivateToken) {
1401          iter.remove();
1402        }
1403      }
1404      return creds;
1405    }
1406  }
1407  
1408  /**
1409   * Add the given Credentials to this user.
1410   * @param credentials of tokens and secrets
1411   */
1412  public void addCredentials(Credentials credentials) {
1413    synchronized (subject) {
1414      getCredentialsInternal().addAll(credentials);
1415    }
1416  }
1417
1418  private synchronized Credentials getCredentialsInternal() {
1419    final Credentials credentials;
1420    final Set<Credentials> credentialsSet =
1421      subject.getPrivateCredentials(Credentials.class);
1422    if (!credentialsSet.isEmpty()){
1423      credentials = credentialsSet.iterator().next();
1424    } else {
1425      credentials = new Credentials();
1426      subject.getPrivateCredentials().add(credentials);
1427    }
1428    return credentials;
1429  }
1430
1431  /**
1432   * Get the group names for this user.
1433   * @return the list of users with the primary group first. If the command
1434   *    fails, it returns an empty list.
1435   */
1436  public synchronized String[] getGroupNames() {
1437    ensureInitialized();
1438    try {
1439      Set<String> result = new LinkedHashSet<String>
1440        (groups.getGroups(getShortUserName()));
1441      return result.toArray(new String[result.size()]);
1442    } catch (IOException ie) {
1443      LOG.warn("No groups available for user " + getShortUserName());
1444      return new String[0];
1445    }
1446  }
1447  
1448  /**
1449   * Return the username.
1450   */
1451  @Override
1452  public String toString() {
1453    StringBuilder sb = new StringBuilder(getUserName());
1454    sb.append(" (auth:"+getAuthenticationMethod()+")");
1455    if (getRealUser() != null) {
1456      sb.append(" via ").append(getRealUser().toString());
1457    }
1458    return sb.toString();
1459  }
1460
1461  /**
1462   * Sets the authentication method in the subject
1463   * 
1464   * @param authMethod
1465   */
1466  public synchronized 
1467  void setAuthenticationMethod(AuthenticationMethod authMethod) {
1468    user.setAuthenticationMethod(authMethod);
1469  }
1470
1471  /**
1472   * Sets the authentication method in the subject
1473   * 
1474   * @param authMethod
1475   */
1476  //TODO: Delete this method once AuthMethod is deprecated.
1477  public void setAuthenticationMethod(AuthMethod authMethod) {
1478    switch (authMethod) {
1479      case SIMPLE:
1480        user.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
1481        break;
1482      case KERBEROS: {
1483        user.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
1484        break;
1485      }
1486      case DIGEST:
1487      case TOKEN: {
1488        user.setAuthenticationMethod(AuthenticationMethod.TOKEN);
1489        break;
1490      }
1491      default:
1492        user.setAuthenticationMethod(null);
1493    }
1494  }
1495
1496  /**
1497   * Get the authentication method from the subject
1498   * 
1499   * @return AuthenticationMethod in the subject, null if not present.
1500   */
1501  public synchronized AuthenticationMethod getAuthenticationMethod() {
1502    return user.getAuthenticationMethod();
1503  }
1504
1505  /**
1506   * Get the authentication method from the real user's subject.  If there
1507   * is no real user, return the given user's authentication method.
1508   * 
1509   * @return AuthenticationMethod in the subject, null if not present.
1510   */
1511  public synchronized AuthenticationMethod getRealAuthenticationMethod() {
1512    UserGroupInformation ugi = getRealUser();
1513    if (ugi == null) {
1514      ugi = this;
1515    }
1516    return ugi.getAuthenticationMethod();
1517  }
1518
1519  /**
1520   * Returns the authentication method of a ugi. If the authentication method is
1521   * PROXY, returns the authentication method of the real user.
1522   * 
1523   * @param ugi
1524   * @return AuthenticationMethod
1525   */
1526  public static AuthenticationMethod getRealAuthenticationMethod(
1527      UserGroupInformation ugi) {
1528    AuthenticationMethod authMethod = ugi.getAuthenticationMethod();
1529    if (authMethod == AuthenticationMethod.PROXY) {
1530      authMethod = ugi.getRealUser().getAuthenticationMethod();
1531    }
1532    return authMethod;
1533  }
1534
1535  /**
1536   * Compare the subjects to see if they are equal to each other.
1537   */
1538  @Override
1539  public boolean equals(Object o) {
1540    if (o == this) {
1541      return true;
1542    } else if (o == null || getClass() != o.getClass()) {
1543      return false;
1544    } else {
1545      return subject == ((UserGroupInformation) o).subject;
1546    }
1547  }
1548
1549  /**
1550   * Return the hash of the subject.
1551   */
1552  @Override
1553  public int hashCode() {
1554    return System.identityHashCode(subject);
1555  }
1556
1557  /**
1558   * Get the underlying subject from this ugi.
1559   * @return the subject that represents this user.
1560   */
1561  public Subject getSubject() {
1562    return subject;
1563  }
1564
1565  /**
1566   * Run the given action as the user.
1567   * @param <T> the return type of the run method
1568   * @param action the method to execute
1569   * @return the value from the run method
1570   */
1571  @InterfaceAudience.Public
1572  @InterfaceStability.Evolving
1573  public <T> T doAs(PrivilegedAction<T> action) {
1574    logPrivilegedAction(subject, action);
1575    return Subject.doAs(subject, action);
1576  }
1577  
1578  /**
1579   * Run the given action as the user, potentially throwing an exception.
1580   * @param <T> the return type of the run method
1581   * @param action the method to execute
1582   * @return the value from the run method
1583   * @throws IOException if the action throws an IOException
1584   * @throws Error if the action throws an Error
1585   * @throws RuntimeException if the action throws a RuntimeException
1586   * @throws InterruptedException if the action throws an InterruptedException
1587   * @throws UndeclaredThrowableException if the action throws something else
1588   */
1589  @InterfaceAudience.Public
1590  @InterfaceStability.Evolving
1591  public <T> T doAs(PrivilegedExceptionAction<T> action
1592                    ) throws IOException, InterruptedException {
1593    try {
1594      logPrivilegedAction(subject, action);
1595      return Subject.doAs(subject, action);
1596    } catch (PrivilegedActionException pae) {
1597      Throwable cause = pae.getCause();
1598      if (LOG.isDebugEnabled()) {
1599        LOG.debug("PrivilegedActionException as:" + this + " cause:" + cause);
1600      }
1601      if (cause instanceof IOException) {
1602        throw (IOException) cause;
1603      } else if (cause instanceof Error) {
1604        throw (Error) cause;
1605      } else if (cause instanceof RuntimeException) {
1606        throw (RuntimeException) cause;
1607      } else if (cause instanceof InterruptedException) {
1608        throw (InterruptedException) cause;
1609      } else {
1610        throw new UndeclaredThrowableException(cause);
1611      }
1612    }
1613  }
1614
1615  private void logPrivilegedAction(Subject subject, Object action) {
1616    if (LOG.isDebugEnabled()) {
1617      // would be nice if action included a descriptive toString()
1618      String where = new Throwable().getStackTrace()[2].toString();
1619      LOG.debug("PrivilegedAction as:"+this+" from:"+where);
1620    }
1621  }
1622
1623  private void print() throws IOException {
1624    System.out.println("User: " + getUserName());
1625    System.out.print("Group Ids: ");
1626    System.out.println();
1627    String[] groups = getGroupNames();
1628    System.out.print("Groups: ");
1629    for(int i=0; i < groups.length; i++) {
1630      System.out.print(groups[i] + " ");
1631    }
1632    System.out.println();    
1633  }
1634
1635  /**
1636   * A test method to print out the current user's UGI.
1637   * @param args if there are two arguments, read the user from the keytab
1638   * and print it out.
1639   * @throws Exception
1640   */
1641  public static void main(String [] args) throws Exception {
1642  System.out.println("Getting UGI for current user");
1643    UserGroupInformation ugi = getCurrentUser();
1644    ugi.print();
1645    System.out.println("UGI: " + ugi);
1646    System.out.println("Auth method " + ugi.user.getAuthenticationMethod());
1647    System.out.println("Keytab " + ugi.isKeytab);
1648    System.out.println("============================================================");
1649    
1650    if (args.length == 2) {
1651      System.out.println("Getting UGI from keytab....");
1652      loginUserFromKeytab(args[0], args[1]);
1653      getCurrentUser().print();
1654      System.out.println("Keytab: " + ugi);
1655      System.out.println("Auth method " + loginUser.user.getAuthenticationMethod());
1656      System.out.println("Keytab " + loginUser.isKeytab);
1657    }
1658  }
1659
1660}