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