1/*
2 * Copyright (c) 2000, 2013, 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
26package com.sun.security.auth.module;
27
28import java.util.*;
29import java.io.IOException;
30import javax.security.auth.*;
31import javax.security.auth.callback.*;
32import javax.security.auth.login.*;
33import javax.security.auth.spi.*;
34import java.security.Principal;
35import com.sun.security.auth.NTUserPrincipal;
36import com.sun.security.auth.NTSidUserPrincipal;
37import com.sun.security.auth.NTDomainPrincipal;
38import com.sun.security.auth.NTSidDomainPrincipal;
39import com.sun.security.auth.NTSidPrimaryGroupPrincipal;
40import com.sun.security.auth.NTSidGroupPrincipal;
41import com.sun.security.auth.NTNumericCredential;
42
43/**
44 * This {@code LoginModule}
45 * renders a user's NT security information as some number of
46 * {@code Principal}s
47 * and associates them with a {@code Subject}.
48 *
49 * <p> This LoginModule recognizes the debug option.
50 * If set to true in the login Configuration,
51 * debug messages will be output to the output stream, System.out.
52 *
53 * <p> This LoginModule also recognizes the debugNative option.
54 * If set to true in the login Configuration,
55 * debug messages from the native component of the module
56 * will be output to the output stream, System.out.
57 *
58 * @see javax.security.auth.spi.LoginModule
59 */
60public class NTLoginModule implements LoginModule {
61
62    private NTSystem ntSystem;
63
64    // initial state
65    private Subject subject;
66    private CallbackHandler callbackHandler;
67    private Map<String, ?> sharedState;
68    private Map<String, ?> options;
69
70    // configurable option
71    private boolean debug = false;
72    private boolean debugNative = false;
73
74    // the authentication status
75    private boolean succeeded = false;
76    private boolean commitSucceeded = false;
77
78    private NTUserPrincipal userPrincipal;              // user name
79    private NTSidUserPrincipal userSID;                 // user SID
80    private NTDomainPrincipal userDomain;               // user domain
81    private NTSidDomainPrincipal domainSID;             // domain SID
82    private NTSidPrimaryGroupPrincipal primaryGroup;    // primary group
83    private NTSidGroupPrincipal[] groups;               // supplementary groups
84    private NTNumericCredential iToken;                 // impersonation token
85
86    /**
87     * Initialize this {@code LoginModule}.
88     *
89     * @param subject the {@code Subject} to be authenticated.
90     *
91     * @param callbackHandler a {@code CallbackHandler} for communicating
92     *          with the end user (prompting for usernames and
93     *          passwords, for example). This particular LoginModule only
94     *          extracts the underlying NT system information, so this
95     *          parameter is ignored.
96     *
97     * @param sharedState shared {@code LoginModule} state.
98     *
99     * @param options options specified in the login
100     *                  {@code Configuration} for this particular
101     *                  {@code LoginModule}.
102     */
103    public void initialize(Subject subject, CallbackHandler callbackHandler,
104                           Map<String,?> sharedState,
105                           Map<String,?> options)
106    {
107
108        this.subject = subject;
109        this.callbackHandler = callbackHandler;
110        this.sharedState = sharedState;
111        this.options = options;
112
113        // initialize any configured options
114        debug = "true".equalsIgnoreCase((String)options.get("debug"));
115        debugNative="true".equalsIgnoreCase((String)options.get("debugNative"));
116
117        if (debugNative == true) {
118            debug = true;
119        }
120    }
121
122    /**
123     * Import underlying NT system identity information.
124     *
125     * @return true in all cases since this {@code LoginModule}
126     *          should not be ignored.
127     *
128     * @exception FailedLoginException if the authentication fails.
129     *
130     * @exception LoginException if this {@code LoginModule}
131     *          is unable to perform the authentication.
132     */
133    public boolean login() throws LoginException {
134
135        succeeded = false; // Indicate not yet successful
136
137        try {
138            ntSystem = new NTSystem(debugNative);
139        } catch (UnsatisfiedLinkError ule) {
140            if (debug) {
141                System.out.println("\t\t[NTLoginModule] " +
142                                   "Failed in NT login");
143            }
144            throw new FailedLoginException
145                ("Failed in attempt to import the " +
146                 "underlying NT system identity information" +
147                 " on " + System.getProperty("os.name"));
148        }
149
150        if (ntSystem.getName() == null) {
151            throw new FailedLoginException
152                ("Failed in attempt to import the " +
153                 "underlying NT system identity information");
154        }
155        userPrincipal = new NTUserPrincipal(ntSystem.getName());
156        if (debug) {
157            System.out.println("\t\t[NTLoginModule] " +
158                               "succeeded importing info: ");
159            System.out.println("\t\t\tuser name = " +
160                userPrincipal.getName());
161        }
162
163        if (ntSystem.getUserSID() != null) {
164            userSID = new NTSidUserPrincipal(ntSystem.getUserSID());
165            if (debug) {
166                System.out.println("\t\t\tuser SID = " +
167                        userSID.getName());
168            }
169        }
170        if (ntSystem.getDomain() != null) {
171            userDomain = new NTDomainPrincipal(ntSystem.getDomain());
172            if (debug) {
173                System.out.println("\t\t\tuser domain = " +
174                        userDomain.getName());
175            }
176        }
177        if (ntSystem.getDomainSID() != null) {
178            domainSID =
179                new NTSidDomainPrincipal(ntSystem.getDomainSID());
180            if (debug) {
181                System.out.println("\t\t\tuser domain SID = " +
182                        domainSID.getName());
183            }
184        }
185        if (ntSystem.getPrimaryGroupID() != null) {
186            primaryGroup =
187                new NTSidPrimaryGroupPrincipal(ntSystem.getPrimaryGroupID());
188            if (debug) {
189                System.out.println("\t\t\tuser primary group = " +
190                        primaryGroup.getName());
191            }
192        }
193        if (ntSystem.getGroupIDs() != null &&
194            ntSystem.getGroupIDs().length > 0) {
195
196            String[] groupSIDs = ntSystem.getGroupIDs();
197            groups = new NTSidGroupPrincipal[groupSIDs.length];
198            for (int i = 0; i < groupSIDs.length; i++) {
199                groups[i] = new NTSidGroupPrincipal(groupSIDs[i]);
200                if (debug) {
201                    System.out.println("\t\t\tuser group = " +
202                        groups[i].getName());
203                }
204            }
205        }
206        if (ntSystem.getImpersonationToken() != 0) {
207            iToken = new NTNumericCredential(ntSystem.getImpersonationToken());
208            if (debug) {
209                System.out.println("\t\t\timpersonation token = " +
210                        ntSystem.getImpersonationToken());
211            }
212        }
213
214        succeeded = true;
215        return succeeded;
216    }
217
218    /**
219     * This method is called if the LoginContext's
220     * overall authentication succeeded
221     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
222     * succeeded).
223     *
224     * <p> If this LoginModule's own authentication attempt
225     * succeeded (checked by retrieving the private state saved by the
226     * {@code login} method), then this method associates some
227     * number of various {@code Principal}s
228     * with the {@code Subject} located in the
229     * {@code LoginModuleContext}.  If this LoginModule's own
230     * authentication attempted failed, then this method removes
231     * any state that was originally saved.
232     *
233     * @exception LoginException if the commit fails.
234     *
235     * @return true if this LoginModule's own login and commit
236     *          attempts succeeded, or false otherwise.
237     */
238    public boolean commit() throws LoginException {
239        if (succeeded == false) {
240            if (debug) {
241                System.out.println("\t\t[NTLoginModule]: " +
242                    "did not add any Principals to Subject " +
243                    "because own authentication failed.");
244            }
245            return false;
246        }
247        if (subject.isReadOnly()) {
248            throw new LoginException ("Subject is ReadOnly");
249        }
250        Set<Principal> principals = subject.getPrincipals();
251
252        // we must have a userPrincipal - everything else is optional
253        if (!principals.contains(userPrincipal)) {
254            principals.add(userPrincipal);
255        }
256        if (userSID != null && !principals.contains(userSID)) {
257            principals.add(userSID);
258        }
259
260        if (userDomain != null && !principals.contains(userDomain)) {
261            principals.add(userDomain);
262        }
263        if (domainSID != null && !principals.contains(domainSID)) {
264            principals.add(domainSID);
265        }
266
267        if (primaryGroup != null && !principals.contains(primaryGroup)) {
268            principals.add(primaryGroup);
269        }
270        for (int i = 0; groups != null && i < groups.length; i++) {
271            if (!principals.contains(groups[i])) {
272                principals.add(groups[i]);
273            }
274        }
275
276        Set<Object> pubCreds = subject.getPublicCredentials();
277        if (iToken != null && !pubCreds.contains(iToken)) {
278            pubCreds.add(iToken);
279        }
280        commitSucceeded = true;
281        return true;
282    }
283
284
285    /**
286     * This method is called if the LoginContext's
287     * overall authentication failed.
288     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
289     * did not succeed).
290     *
291     * <p> If this LoginModule's own authentication attempt
292     * succeeded (checked by retrieving the private state saved by the
293     * {@code login} and {@code commit} methods),
294     * then this method cleans up any state that was originally saved.
295     *
296     * @exception LoginException if the abort fails.
297     *
298     * @return false if this LoginModule's own login and/or commit attempts
299     *          failed, and true otherwise.
300     */
301    public boolean abort() throws LoginException {
302        if (debug) {
303            System.out.println("\t\t[NTLoginModule]: " +
304                "aborted authentication attempt");
305        }
306
307        if (succeeded == false) {
308            return false;
309        } else if (succeeded == true && commitSucceeded == false) {
310            ntSystem = null;
311            userPrincipal = null;
312            userSID = null;
313            userDomain = null;
314            domainSID = null;
315            primaryGroup = null;
316            groups = null;
317            iToken = null;
318            succeeded = false;
319        } else {
320            // overall authentication succeeded and commit succeeded,
321            // but someone else's commit failed
322            logout();
323        }
324        return succeeded;
325    }
326
327    /**
328     * Logout the user.
329     *
330     * <p> This method removes the {@code NTUserPrincipal},
331     * {@code NTDomainPrincipal}, {@code NTSidUserPrincipal},
332     * {@code NTSidDomainPrincipal}, {@code NTSidGroupPrincipal}s,
333     * and {@code NTSidPrimaryGroupPrincipal}
334     * that may have been added by the {@code commit} method.
335     *
336     * @exception LoginException if the logout fails.
337     *
338     * @return true in all cases since this {@code LoginModule}
339     *          should not be ignored.
340     */
341    public boolean logout() throws LoginException {
342
343        if (subject.isReadOnly()) {
344            throw new LoginException ("Subject is ReadOnly");
345        }
346        Set<Principal> principals = subject.getPrincipals();
347        if (principals.contains(userPrincipal)) {
348            principals.remove(userPrincipal);
349        }
350        if (principals.contains(userSID)) {
351            principals.remove(userSID);
352        }
353        if (principals.contains(userDomain)) {
354            principals.remove(userDomain);
355        }
356        if (principals.contains(domainSID)) {
357            principals.remove(domainSID);
358        }
359        if (principals.contains(primaryGroup)) {
360            principals.remove(primaryGroup);
361        }
362        for (int i = 0; groups != null && i < groups.length; i++) {
363            if (principals.contains(groups[i])) {
364                principals.remove(groups[i]);
365            }
366        }
367
368        Set<Object> pubCreds = subject.getPublicCredentials();
369        if (pubCreds.contains(iToken)) {
370            pubCreds.remove(iToken);
371        }
372
373        succeeded = false;
374        commitSucceeded = false;
375        userPrincipal = null;
376        userDomain = null;
377        userSID = null;
378        domainSID = null;
379        groups = null;
380        primaryGroup = null;
381        iToken = null;
382        ntSystem = null;
383
384        if (debug) {
385                System.out.println("\t\t[NTLoginModule] " +
386                                "completed logout processing");
387        }
388        return true;
389    }
390}
391