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