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