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