1 /*
   2  * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.security.auth.module;
  27 
  28 import java.util.*;
  29 import java.io.IOException;
  30 import javax.security.auth.*;
  31 import javax.security.auth.callback.*;
  32 import javax.security.auth.login.*;
  33 import javax.security.auth.spi.*;
  34 import com.sun.security.auth.UnixPrincipal;
  35 import com.sun.security.auth.UnixNumericUserPrincipal;
  36 import com.sun.security.auth.UnixNumericGroupPrincipal;
  37 
  38 /**
  39  * <p> This <code>LoginModule</code> imports a user's Unix
  40  * <code>Principal</code> information (<code>UnixPrincipal</code>,
  41  * <code>UnixNumericUserPrincipal</code>,
  42  * and <code>UnixNumericGroupPrincipal</code>)
  43  * and associates them with the current <code>Subject</code>.
  44  *
  45  * <p> This LoginModule recognizes the debug option.
  46  * If set to true in the login Configuration,
  47  * debug messages will be output to the output stream, System.out.
  48  *
  49  */
  50 public class UnixLoginModule implements LoginModule {
  51 
  52     // initial state
  53     private Subject subject;
  54     private CallbackHandler callbackHandler;
  55     private Map<String, ?> sharedState;
  56     private Map<String, ?> options;
  57 
  58     // configurable option
  59     private boolean debug = true;
  60 
  61     // UnixSystem to retrieve underlying system info
  62     private UnixSystem ss;
  63 
  64     // the authentication status
  65     private boolean succeeded = false;
  66     private boolean commitSucceeded = false;
  67 
  68     // Underlying system info
  69     private UnixPrincipal userPrincipal;
  70     private UnixNumericUserPrincipal UIDPrincipal;
  71     private UnixNumericGroupPrincipal GIDPrincipal;
  72     private LinkedList<UnixNumericGroupPrincipal> supplementaryGroups =
  73                 new LinkedList<>();
  74 
  75     /**
  76      * Initialize this <code>LoginModule</code>.
  77      *
  78      * <p>
  79      *
  80      * @param subject the <code>Subject</code> to be authenticated. <p>
  81      *
  82      * @param callbackHandler a <code>CallbackHandler</code> for communicating
  83      *                  with the end user (prompting for usernames and
  84      *                  passwords, for example). <p>
  85      *
  86      * @param sharedState shared <code>LoginModule</code> state. <p>
  87      *
  88      * @param options options specified in the login
  89      *                  <code>Configuration</code> for this particular
  90      *                  <code>LoginModule</code>.
  91      */
  92     public void initialize(Subject subject, CallbackHandler callbackHandler,
  93                            Map<String,?> sharedState,
  94                            Map<String,?> options) {
  95 
  96         this.subject = subject;
  97         this.callbackHandler = callbackHandler;
  98         this.sharedState = sharedState;
  99         this.options = options;
 100 
 101         // initialize any configured options
 102         debug = "true".equalsIgnoreCase((String)options.get("debug"));
 103     }
 104 
 105     /**
 106      * Authenticate the user (first phase).
 107      *
 108      * <p> The implementation of this method attempts to retrieve the user's
 109      * Unix <code>Subject</code> information by making a native Unix
 110      * system call.
 111      *
 112      * <p>
 113      *
 114      * @exception FailedLoginException if attempts to retrieve the underlying
 115      *          system information fail.
 116      *
 117      * @return true in all cases (this <code>LoginModule</code>
 118      *          should not be ignored).
 119      */
 120     public boolean login() throws LoginException {
 121 
 122         long[] unixGroups = null;
 123 
 124         ss = new UnixSystem();
 125 
 126         if (ss == null) {
 127             succeeded = false;
 128             throw new FailedLoginException
 129                                 ("Failed in attempt to import " +
 130                                 "the underlying system identity information");
 131         } else {
 132             userPrincipal = new UnixPrincipal(ss.getUsername());
 133             UIDPrincipal = new UnixNumericUserPrincipal(ss.getUid());
 134             GIDPrincipal = new UnixNumericGroupPrincipal(ss.getGid(), true);
 135             if (ss.getGroups() != null && ss.getGroups().length > 0) {
 136                 unixGroups = ss.getGroups();
 137                 for (int i = 0; i < unixGroups.length; i++) {
 138                     UnixNumericGroupPrincipal ngp =
 139                         new UnixNumericGroupPrincipal
 140                         (unixGroups[i], false);
 141                     if (!ngp.getName().equals(GIDPrincipal.getName()))
 142                         supplementaryGroups.add(ngp);
 143                 }
 144             }
 145             if (debug) {
 146                 System.out.println("\t\t[UnixLoginModule]: " +
 147                         "succeeded importing info: ");
 148                 System.out.println("\t\t\tuid = " + ss.getUid());
 149                 System.out.println("\t\t\tgid = " + ss.getGid());
 150                 unixGroups = ss.getGroups();
 151                 for (int i = 0; i < unixGroups.length; i++) {
 152                     System.out.println("\t\t\tsupp gid = " + unixGroups[i]);
 153                 }
 154             }
 155             succeeded = true;
 156             return true;
 157         }
 158     }
 159 
 160     /**
 161      * Commit the authentication (second phase).
 162      *
 163      * <p> This method is called if the LoginContext's
 164      * overall authentication succeeded
 165      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
 166      * succeeded).
 167      *
 168      * <p> If this LoginModule's own authentication attempt
 169      * succeeded (the importing of the Unix authentication information
 170      * succeeded), then this method associates the Unix Principals
 171      * with the <code>Subject</code> currently tied to the
 172      * <code>LoginModule</code>.  If this LoginModule's
 173      * authentication attempted failed, then this method removes
 174      * any state that was originally saved.
 175      *
 176      * <p>
 177      *
 178      * @exception LoginException if the commit fails
 179      *
 180      * @return true if this LoginModule's own login and commit attempts
 181      *          succeeded, or false otherwise.
 182      */
 183     public boolean commit() throws LoginException {
 184         if (succeeded == false) {
 185             if (debug) {
 186                 System.out.println("\t\t[UnixLoginModule]: " +
 187                     "did not add any Principals to Subject " +
 188                     "because own authentication failed.");
 189             }
 190             return false;
 191         } else {
 192             if (subject.isReadOnly()) {
 193                 throw new LoginException
 194                     ("commit Failed: Subject is Readonly");
 195             }
 196             if (!subject.getPrincipals().contains(userPrincipal))
 197                 subject.getPrincipals().add(userPrincipal);
 198             if (!subject.getPrincipals().contains(UIDPrincipal))
 199                 subject.getPrincipals().add(UIDPrincipal);
 200             if (!subject.getPrincipals().contains(GIDPrincipal))
 201                 subject.getPrincipals().add(GIDPrincipal);
 202             for (int i = 0; i < supplementaryGroups.size(); i++) {
 203                 if (!subject.getPrincipals().contains
 204                     (supplementaryGroups.get(i)))
 205                     subject.getPrincipals().add(supplementaryGroups.get(i));
 206             }
 207 
 208             if (debug) {
 209                 System.out.println("\t\t[UnixLoginModule]: " +
 210                     "added UnixPrincipal,");
 211                 System.out.println("\t\t\t\tUnixNumericUserPrincipal,");
 212                 System.out.println("\t\t\t\tUnixNumericGroupPrincipal(s),");
 213                 System.out.println("\t\t\t to Subject");
 214             }
 215 
 216             commitSucceeded = true;
 217             return true;
 218         }
 219     }
 220 
 221     /**
 222      * Abort the authentication (second phase).
 223      *
 224      * <p> This method is called if the LoginContext's
 225      * overall authentication failed.
 226      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
 227      * did not succeed).
 228      *
 229      * <p> This method cleans up any state that was originally saved
 230      * as part of the authentication attempt from the <code>login</code>
 231      * and <code>commit</code> methods.
 232      *
 233      * <p>
 234      *
 235      * @exception LoginException if the abort fails
 236      *
 237      * @return false if this LoginModule's own login and/or commit attempts
 238      *          failed, and true otherwise.
 239      */
 240     public boolean abort() throws LoginException {
 241         if (debug) {
 242             System.out.println("\t\t[UnixLoginModule]: " +
 243                 "aborted authentication attempt");
 244         }
 245 
 246         if (succeeded == false) {
 247             return false;
 248         } else if (succeeded == true && commitSucceeded == false) {
 249 
 250             // Clean out state
 251             succeeded = false;
 252             ss = null;
 253             userPrincipal = null;
 254             UIDPrincipal = null;
 255             GIDPrincipal = null;
 256             supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
 257         } else {
 258             // overall authentication succeeded and commit succeeded,
 259             // but someone else's commit failed
 260             logout();
 261         }
 262         return true;
 263     }
 264 
 265     /**
 266      * Logout the user
 267      *
 268      * <p> This method removes the Principals associated
 269      * with the <code>Subject</code>.
 270      *
 271      * <p>
 272      *
 273      * @exception LoginException if the logout fails
 274      *
 275      * @return true in all cases (this <code>LoginModule</code>
 276      *          should not be ignored).
 277      */
 278     public boolean logout() throws LoginException {
 279 
 280         if (subject.isReadOnly()) {
 281                 throw new LoginException
 282                     ("logout Failed: Subject is Readonly");
 283             }
 284         // remove the added Principals from the Subject
 285         subject.getPrincipals().remove(userPrincipal);
 286         subject.getPrincipals().remove(UIDPrincipal);
 287         subject.getPrincipals().remove(GIDPrincipal);
 288         for (int i = 0; i < supplementaryGroups.size(); i++) {
 289             subject.getPrincipals().remove(supplementaryGroups.get(i));
 290         }
 291 
 292         // clean out state
 293         ss = null;
 294         succeeded = false;
 295         commitSucceeded = false;
 296         userPrincipal = null;
 297         UIDPrincipal = null;
 298         GIDPrincipal = null;
 299         supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
 300 
 301         if (debug) {
 302             System.out.println("\t\t[UnixLoginModule]: " +
 303                 "logged out Subject");
 304         }
 305         return true;
 306     }
 307 }