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