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