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          if (LOG.isDebugEnabled()) {
953            LOG.debug("Initiating logout for " + getUserName());
954          }
955          synchronized (UserGroupInformation.class) {
956            // clear up the kerberos state. But the tokens are not cleared! As per
957            // the Java kerberos login module code, only the kerberos credentials
958            // are cleared
959            login.logout();
960            // login and also update the subject field of this instance to
961            // have the new credentials (pass it to the LoginContext constructor)
962            login = newLoginContext(
963                HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, getSubject(),
964                new HadoopConfiguration());
965            if (LOG.isDebugEnabled()) {
966              LOG.debug("Initiating re-login for " + keytabPrincipal);
967            }
968            start = Time.now();
969            login.login();
970            metrics.loginSuccess.add(Time.now() - start);
971            setLogin(login);
972          }
973        } catch (LoginException le) {
974          if (start > 0) {
975            metrics.loginFailure.add(Time.now() - start);
976          }
977          throw new IOException("Login failure for " + keytabPrincipal + 
978              " from keytab " + keytabFile, le);
979        } 
980      }
981    
982      /**
983       * Re-Login a user in from the ticket cache.  This
984       * method assumes that login had happened already.
985       * The Subject field of this UserGroupInformation object is updated to have
986       * the new credentials.
987       * @throws IOException on a failure
988       */
989      @InterfaceAudience.Public
990      @InterfaceStability.Evolving
991      public synchronized void reloginFromTicketCache()
992      throws IOException {
993        if (!isSecurityEnabled() || 
994            user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
995            !isKrbTkt)
996          return;
997        LoginContext login = getLogin();
998        if (login == null) {
999          throw new IOException("login must be done first");
1000        }
1001        long now = Time.now();
1002        if (!hasSufficientTimeElapsed(now)) {
1003          return;
1004        }
1005        // register most recent relogin attempt
1006        user.setLastLogin(now);
1007        try {
1008          if (LOG.isDebugEnabled()) {
1009            LOG.debug("Initiating logout for " + getUserName());
1010          }
1011          //clear up the kerberos state. But the tokens are not cleared! As per 
1012          //the Java kerberos login module code, only the kerberos credentials
1013          //are cleared
1014          login.logout();
1015          //login and also update the subject field of this instance to 
1016          //have the new credentials (pass it to the LoginContext constructor)
1017          login = 
1018            newLoginContext(HadoopConfiguration.USER_KERBEROS_CONFIG_NAME, 
1019                getSubject(), new HadoopConfiguration());
1020          if (LOG.isDebugEnabled()) {
1021            LOG.debug("Initiating re-login for " + getUserName());
1022          }
1023          login.login();
1024          setLogin(login);
1025        } catch (LoginException le) {
1026          throw new IOException("Login failure for " + getUserName(), le);
1027        } 
1028      }
1029    
1030    
1031      /**
1032       * Log a user in from a keytab file. Loads a user identity from a keytab
1033       * file and login them in. This new user does not affect the currently
1034       * logged-in user.
1035       * @param user the principal name to load from the keytab
1036       * @param path the path to the keytab file
1037       * @throws IOException if the keytab file can't be read
1038       */
1039      public synchronized
1040      static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
1041                                      String path
1042                                      ) throws IOException {
1043        if (!isSecurityEnabled())
1044          return UserGroupInformation.getCurrentUser();
1045        String oldKeytabFile = null;
1046        String oldKeytabPrincipal = null;
1047    
1048        long start = 0;
1049        try {
1050          oldKeytabFile = keytabFile;
1051          oldKeytabPrincipal = keytabPrincipal;
1052          keytabFile = path;
1053          keytabPrincipal = user;
1054          Subject subject = new Subject();
1055          
1056          LoginContext login = newLoginContext(
1057              HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, subject,
1058              new HadoopConfiguration());
1059           
1060          start = Time.now();
1061          login.login();
1062          metrics.loginSuccess.add(Time.now() - start);
1063          UserGroupInformation newLoginUser = new UserGroupInformation(subject);
1064          newLoginUser.setLogin(login);
1065          newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
1066          
1067          return newLoginUser;
1068        } catch (LoginException le) {
1069          if (start > 0) {
1070            metrics.loginFailure.add(Time.now() - start);
1071          }
1072          throw new IOException("Login failure for " + user + " from keytab " + 
1073                                path, le);
1074        } finally {
1075          if(oldKeytabFile != null) keytabFile = oldKeytabFile;
1076          if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal;
1077        }
1078      }
1079    
1080      private boolean hasSufficientTimeElapsed(long now) {
1081        if (now - user.getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
1082          LOG.warn("Not attempting to re-login since the last re-login was " +
1083              "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+
1084              " before.");
1085          return false;
1086        }
1087        return true;
1088      }
1089      
1090      /**
1091       * Did the login happen via keytab
1092       * @return true or false
1093       */
1094      @InterfaceAudience.Public
1095      @InterfaceStability.Evolving
1096      public synchronized static boolean isLoginKeytabBased() throws IOException {
1097        return getLoginUser().isKeytab;
1098      }
1099    
1100      /**
1101       * Create a user from a login name. It is intended to be used for remote
1102       * users in RPC, since it won't have any credentials.
1103       * @param user the full user principal name, must not be empty or null
1104       * @return the UserGroupInformation for the remote user.
1105       */
1106      @InterfaceAudience.Public
1107      @InterfaceStability.Evolving
1108      public static UserGroupInformation createRemoteUser(String user) {
1109        if (user == null || user.isEmpty()) {
1110          throw new IllegalArgumentException("Null user");
1111        }
1112        Subject subject = new Subject();
1113        subject.getPrincipals().add(new User(user));
1114        UserGroupInformation result = new UserGroupInformation(subject);
1115        result.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
1116        return result;
1117      }
1118    
1119      /**
1120       * existing types of authentications' methods
1121       */
1122      @InterfaceAudience.Public
1123      @InterfaceStability.Evolving
1124      public static enum AuthenticationMethod {
1125        // currently we support only one auth per method, but eventually a 
1126        // subtype is needed to differentiate, ex. if digest is token or ldap
1127        SIMPLE(AuthMethod.SIMPLE,
1128            HadoopConfiguration.SIMPLE_CONFIG_NAME),
1129        KERBEROS(AuthMethod.KERBEROS,
1130            HadoopConfiguration.USER_KERBEROS_CONFIG_NAME),
1131        TOKEN(AuthMethod.TOKEN),
1132        CERTIFICATE(null),
1133        KERBEROS_SSL(null),
1134        PROXY(null);
1135        
1136        private final AuthMethod authMethod;
1137        private final String loginAppName;
1138        
1139        private AuthenticationMethod(AuthMethod authMethod) {
1140          this(authMethod, null);
1141        }
1142        private AuthenticationMethod(AuthMethod authMethod, String loginAppName) {
1143          this.authMethod = authMethod;
1144          this.loginAppName = loginAppName;
1145        }
1146        
1147        public AuthMethod getAuthMethod() {
1148          return authMethod;
1149        }
1150        
1151        String getLoginAppName() {
1152          if (loginAppName == null) {
1153            throw new UnsupportedOperationException(
1154                this + " login authentication is not supported");
1155          }
1156          return loginAppName;
1157        }
1158        
1159        public static AuthenticationMethod valueOf(AuthMethod authMethod) {
1160          for (AuthenticationMethod value : values()) {
1161            if (value.getAuthMethod() == authMethod) {
1162              return value;
1163            }
1164          }
1165          throw new IllegalArgumentException(
1166              "no authentication method for " + authMethod);
1167        }
1168      };
1169    
1170      /**
1171       * Create a proxy user using username of the effective user and the ugi of the
1172       * real user.
1173       * @param user
1174       * @param realUser
1175       * @return proxyUser ugi
1176       */
1177      @InterfaceAudience.Public
1178      @InterfaceStability.Evolving
1179      public static UserGroupInformation createProxyUser(String user,
1180          UserGroupInformation realUser) {
1181        if (user == null || user.isEmpty()) {
1182          throw new IllegalArgumentException("Null user");
1183        }
1184        if (realUser == null) {
1185          throw new IllegalArgumentException("Null real user");
1186        }
1187        Subject subject = new Subject();
1188        Set<Principal> principals = subject.getPrincipals();
1189        principals.add(new User(user));
1190        principals.add(new RealUser(realUser));
1191        UserGroupInformation result =new UserGroupInformation(subject);
1192        result.setAuthenticationMethod(AuthenticationMethod.PROXY);
1193        return result;
1194      }
1195    
1196      /**
1197       * get RealUser (vs. EffectiveUser)
1198       * @return realUser running over proxy user
1199       */
1200      @InterfaceAudience.Public
1201      @InterfaceStability.Evolving
1202      public UserGroupInformation getRealUser() {
1203        for (RealUser p: subject.getPrincipals(RealUser.class)) {
1204          return p.getRealUser();
1205        }
1206        return null;
1207      }
1208    
1209    
1210      
1211      /**
1212       * This class is used for storing the groups for testing. It stores a local
1213       * map that has the translation of usernames to groups.
1214       */
1215      private static class TestingGroups extends Groups {
1216        private final Map<String, List<String>> userToGroupsMapping = 
1217          new HashMap<String,List<String>>();
1218        private Groups underlyingImplementation;
1219        
1220        private TestingGroups(Groups underlyingImplementation) {
1221          super(new org.apache.hadoop.conf.Configuration());
1222          this.underlyingImplementation = underlyingImplementation;
1223        }
1224        
1225        @Override
1226        public List<String> getGroups(String user) throws IOException {
1227          List<String> result = userToGroupsMapping.get(user);
1228          
1229          if (result == null) {
1230            result = underlyingImplementation.getGroups(user);
1231          }
1232    
1233          return result;
1234        }
1235    
1236        private void setUserGroups(String user, String[] groups) {
1237          userToGroupsMapping.put(user, Arrays.asList(groups));
1238        }
1239      }
1240    
1241      /**
1242       * Create a UGI for testing HDFS and MapReduce
1243       * @param user the full user principal name
1244       * @param userGroups the names of the groups that the user belongs to
1245       * @return a fake user for running unit tests
1246       */
1247      @InterfaceAudience.Public
1248      @InterfaceStability.Evolving
1249      public static UserGroupInformation createUserForTesting(String user, 
1250                                                              String[] userGroups) {
1251        ensureInitialized();
1252        UserGroupInformation ugi = createRemoteUser(user);
1253        // make sure that the testing object is setup
1254        if (!(groups instanceof TestingGroups)) {
1255          groups = new TestingGroups(groups);
1256        }
1257        // add the user groups
1258        ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1259        return ugi;
1260      }
1261    
1262    
1263      /**
1264       * Create a proxy user UGI for testing HDFS and MapReduce
1265       * 
1266       * @param user
1267       *          the full user principal name for effective user
1268       * @param realUser
1269       *          UGI of the real user
1270       * @param userGroups
1271       *          the names of the groups that the user belongs to
1272       * @return a fake user for running unit tests
1273       */
1274      public static UserGroupInformation createProxyUserForTesting(String user,
1275          UserGroupInformation realUser, String[] userGroups) {
1276        ensureInitialized();
1277        UserGroupInformation ugi = createProxyUser(user, realUser);
1278        // make sure that the testing object is setup
1279        if (!(groups instanceof TestingGroups)) {
1280          groups = new TestingGroups(groups);
1281        }
1282        // add the user groups
1283        ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1284        return ugi;
1285      }
1286      
1287      /**
1288       * Get the user's login name.
1289       * @return the user's name up to the first '/' or '@'.
1290       */
1291      public String getShortUserName() {
1292        for (User p: subject.getPrincipals(User.class)) {
1293          return p.getShortName();
1294        }
1295        return null;
1296      }
1297    
1298      public String getPrimaryGroupName() throws IOException {
1299        String[] groups = getGroupNames();
1300        if (groups.length == 0) {
1301          throw new IOException("There is no primary group for UGI " + this);
1302        }
1303        return groups[0];
1304      }
1305    
1306      /**
1307       * Get the user's full principal name.
1308       * @return the user's full principal name.
1309       */
1310      @InterfaceAudience.Public
1311      @InterfaceStability.Evolving
1312      public String getUserName() {
1313        return user.getName();
1314      }
1315    
1316      /**
1317       * Add a TokenIdentifier to this UGI. The TokenIdentifier has typically been
1318       * authenticated by the RPC layer as belonging to the user represented by this
1319       * UGI.
1320       * 
1321       * @param tokenId
1322       *          tokenIdentifier to be added
1323       * @return true on successful add of new tokenIdentifier
1324       */
1325      public synchronized boolean addTokenIdentifier(TokenIdentifier tokenId) {
1326        return subject.getPublicCredentials().add(tokenId);
1327      }
1328    
1329      /**
1330       * Get the set of TokenIdentifiers belonging to this UGI
1331       * 
1332       * @return the set of TokenIdentifiers belonging to this UGI
1333       */
1334      public synchronized Set<TokenIdentifier> getTokenIdentifiers() {
1335        return subject.getPublicCredentials(TokenIdentifier.class);
1336      }
1337      
1338      /**
1339       * Add a token to this UGI
1340       * 
1341       * @param token Token to be added
1342       * @return true on successful add of new token
1343       */
1344      public synchronized boolean addToken(Token<? extends TokenIdentifier> token) {
1345        return (token != null) ? addToken(token.getService(), token) : false;
1346      }
1347    
1348      /**
1349       * Add a named token to this UGI
1350       * 
1351       * @param alias Name of the token
1352       * @param token Token to be added
1353       * @return true on successful add of new token
1354       */
1355      public synchronized boolean addToken(Text alias,
1356                                           Token<? extends TokenIdentifier> token) {
1357        getCredentialsInternal().addToken(alias, token);
1358        return true;
1359      }
1360      
1361      /**
1362       * Obtain the collection of tokens associated with this user.
1363       * 
1364       * @return an unmodifiable collection of tokens associated with user
1365       */
1366      public synchronized
1367      Collection<Token<? extends TokenIdentifier>> getTokens() {
1368        return Collections.unmodifiableCollection(
1369            getCredentialsInternal().getAllTokens());
1370      }
1371    
1372      /**
1373       * Obtain the tokens in credentials form associated with this user.
1374       * 
1375       * @return Credentials of tokens associated with this user
1376       */
1377      public synchronized Credentials getCredentials() {
1378        Credentials creds = new Credentials(getCredentialsInternal());
1379        Iterator<Token<?>> iter = creds.getAllTokens().iterator();
1380        while (iter.hasNext()) {
1381          if (iter.next() instanceof Token.PrivateToken) {
1382            iter.remove();
1383          }
1384        }
1385        return creds;
1386      }
1387      
1388      /**
1389       * Add the given Credentials to this user.
1390       * @param credentials of tokens and secrets
1391       */
1392      public synchronized void addCredentials(Credentials credentials) {
1393        getCredentialsInternal().addAll(credentials);
1394      }
1395    
1396      private synchronized Credentials getCredentialsInternal() {
1397        final Credentials credentials;
1398        final Set<Credentials> credentialsSet =
1399          subject.getPrivateCredentials(Credentials.class);
1400        if (!credentialsSet.isEmpty()){
1401          credentials = credentialsSet.iterator().next();
1402        } else {
1403          credentials = new Credentials();
1404          subject.getPrivateCredentials().add(credentials);
1405        }
1406        return credentials;
1407      }
1408    
1409      /**
1410       * Get the group names for this user.
1411       * @return the list of users with the primary group first. If the command
1412       *    fails, it returns an empty list.
1413       */
1414      public synchronized String[] getGroupNames() {
1415        ensureInitialized();
1416        try {
1417          List<String> result = groups.getGroups(getShortUserName());
1418          return result.toArray(new String[result.size()]);
1419        } catch (IOException ie) {
1420          LOG.warn("No groups available for user " + getShortUserName());
1421          return new String[0];
1422        }
1423      }
1424      
1425      /**
1426       * Return the username.
1427       */
1428      @Override
1429      public String toString() {
1430        StringBuilder sb = new StringBuilder(getUserName());
1431        sb.append(" (auth:"+getAuthenticationMethod()+")");
1432        if (getRealUser() != null) {
1433          sb.append(" via ").append(getRealUser().toString());
1434        }
1435        return sb.toString();
1436      }
1437    
1438      /**
1439       * Sets the authentication method in the subject
1440       * 
1441       * @param authMethod
1442       */
1443      public synchronized 
1444      void setAuthenticationMethod(AuthenticationMethod authMethod) {
1445        user.setAuthenticationMethod(authMethod);
1446      }
1447    
1448      /**
1449       * Sets the authentication method in the subject
1450       * 
1451       * @param authMethod
1452       */
1453      public void setAuthenticationMethod(AuthMethod authMethod) {
1454        user.setAuthenticationMethod(AuthenticationMethod.valueOf(authMethod));
1455      }
1456    
1457      /**
1458       * Get the authentication method from the subject
1459       * 
1460       * @return AuthenticationMethod in the subject, null if not present.
1461       */
1462      public synchronized AuthenticationMethod getAuthenticationMethod() {
1463        return user.getAuthenticationMethod();
1464      }
1465    
1466      /**
1467       * Get the authentication method from the real user's subject.  If there
1468       * is no real user, return the given user's authentication method.
1469       * 
1470       * @return AuthenticationMethod in the subject, null if not present.
1471       */
1472      public synchronized AuthenticationMethod getRealAuthenticationMethod() {
1473        UserGroupInformation ugi = getRealUser();
1474        if (ugi == null) {
1475          ugi = this;
1476        }
1477        return ugi.getAuthenticationMethod();
1478      }
1479    
1480      /**
1481       * Returns the authentication method of a ugi. If the authentication method is
1482       * PROXY, returns the authentication method of the real user.
1483       * 
1484       * @param ugi
1485       * @return AuthenticationMethod
1486       */
1487      public static AuthenticationMethod getRealAuthenticationMethod(
1488          UserGroupInformation ugi) {
1489        AuthenticationMethod authMethod = ugi.getAuthenticationMethod();
1490        if (authMethod == AuthenticationMethod.PROXY) {
1491          authMethod = ugi.getRealUser().getAuthenticationMethod();
1492        }
1493        return authMethod;
1494      }
1495    
1496      /**
1497       * Compare the subjects to see if they are equal to each other.
1498       */
1499      @Override
1500      public boolean equals(Object o) {
1501        if (o == this) {
1502          return true;
1503        } else if (o == null || getClass() != o.getClass()) {
1504          return false;
1505        } else {
1506          return subject == ((UserGroupInformation) o).subject;
1507        }
1508      }
1509    
1510      /**
1511       * Return the hash of the subject.
1512       */
1513      @Override
1514      public int hashCode() {
1515        return System.identityHashCode(subject);
1516      }
1517    
1518      /**
1519       * Get the underlying subject from this ugi.
1520       * @return the subject that represents this user.
1521       */
1522      protected Subject getSubject() {
1523        return subject;
1524      }
1525    
1526      /**
1527       * Run the given action as the user.
1528       * @param <T> the return type of the run method
1529       * @param action the method to execute
1530       * @return the value from the run method
1531       */
1532      @InterfaceAudience.Public
1533      @InterfaceStability.Evolving
1534      public <T> T doAs(PrivilegedAction<T> action) {
1535        logPrivilegedAction(subject, action);
1536        return Subject.doAs(subject, action);
1537      }
1538      
1539      /**
1540       * Run the given action as the user, potentially throwing an exception.
1541       * @param <T> the return type of the run method
1542       * @param action the method to execute
1543       * @return the value from the run method
1544       * @throws IOException if the action throws an IOException
1545       * @throws Error if the action throws an Error
1546       * @throws RuntimeException if the action throws a RuntimeException
1547       * @throws InterruptedException if the action throws an InterruptedException
1548       * @throws UndeclaredThrowableException if the action throws something else
1549       */
1550      @InterfaceAudience.Public
1551      @InterfaceStability.Evolving
1552      public <T> T doAs(PrivilegedExceptionAction<T> action
1553                        ) throws IOException, InterruptedException {
1554        try {
1555          logPrivilegedAction(subject, action);
1556          return Subject.doAs(subject, action);
1557        } catch (PrivilegedActionException pae) {
1558          Throwable cause = pae.getCause();
1559          if (LOG.isDebugEnabled()) {
1560            LOG.debug("PrivilegedActionException as:" + this + " cause:" + cause);
1561          }
1562          if (cause instanceof IOException) {
1563            throw (IOException) cause;
1564          } else if (cause instanceof Error) {
1565            throw (Error) cause;
1566          } else if (cause instanceof RuntimeException) {
1567            throw (RuntimeException) cause;
1568          } else if (cause instanceof InterruptedException) {
1569            throw (InterruptedException) cause;
1570          } else {
1571            throw new UndeclaredThrowableException(cause);
1572          }
1573        }
1574      }
1575    
1576      private void logPrivilegedAction(Subject subject, Object action) {
1577        if (LOG.isDebugEnabled()) {
1578          // would be nice if action included a descriptive toString()
1579          String where = new Throwable().getStackTrace()[2].toString();
1580          LOG.debug("PrivilegedAction as:"+this+" from:"+where);
1581        }
1582      }
1583    
1584      private void print() throws IOException {
1585        System.out.println("User: " + getUserName());
1586        System.out.print("Group Ids: ");
1587        System.out.println();
1588        String[] groups = getGroupNames();
1589        System.out.print("Groups: ");
1590        for(int i=0; i < groups.length; i++) {
1591          System.out.print(groups[i] + " ");
1592        }
1593        System.out.println();    
1594      }
1595    
1596      /**
1597       * A test method to print out the current user's UGI.
1598       * @param args if there are two arguments, read the user from the keytab
1599       * and print it out.
1600       * @throws Exception
1601       */
1602      public static void main(String [] args) throws Exception {
1603      System.out.println("Getting UGI for current user");
1604        UserGroupInformation ugi = getCurrentUser();
1605        ugi.print();
1606        System.out.println("UGI: " + ugi);
1607        System.out.println("Auth method " + ugi.user.getAuthenticationMethod());
1608        System.out.println("Keytab " + ugi.isKeytab);
1609        System.out.println("============================================================");
1610        
1611        if (args.length == 2) {
1612          System.out.println("Getting UGI from keytab....");
1613          loginUserFromKeytab(args[0], args[1]);
1614          getCurrentUser().print();
1615          System.out.println("Keytab: " + ugi);
1616          System.out.println("Auth method " + loginUser.user.getAuthenticationMethod());
1617          System.out.println("Keytab " + loginUser.isKeytab);
1618        }
1619      }
1620    
1621    }