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