1 package ch.odi.jaaspam;
2
3 import java.io.IOException;
4 import java.util.Map;
5 import java.util.Set;
6
7 import javax.security.auth.Subject;
8 import javax.security.auth.callback.Callback;
9 import javax.security.auth.callback.CallbackHandler;
10 import javax.security.auth.callback.ChoiceCallback;
11 import javax.security.auth.callback.ConfirmationCallback;
12 import javax.security.auth.callback.LanguageCallback;
13 import javax.security.auth.callback.NameCallback;
14 import javax.security.auth.callback.PasswordCallback;
15 import javax.security.auth.callback.TextInputCallback;
16 import javax.security.auth.callback.TextOutputCallback;
17 import javax.security.auth.callback.UnsupportedCallbackException;
18 import javax.security.auth.login.AccountExpiredException;
19 import javax.security.auth.login.CredentialExpiredException;
20 import javax.security.auth.login.FailedLoginException;
21 import javax.security.auth.login.LoginException;
22 import javax.security.auth.spi.LoginModule;
23
24 import ch.odi.pam.Pam;
25 import ch.odi.pam.PamCallback;
26 import ch.odi.pam.PamConstants;
27 import ch.odi.pam.PamError;
28 import ch.odi.pam.PamMessage;
29 import ch.odi.pam.PamResponse;
30
31 /**
32 * JAAS login module that defers the actual authentication
33 * to PAM. This login module must set the PAM service name
34 * in the JAAS config file. The service name is passed as
35 * the value to the option named <code>service</code>.
36 * <p>Sample configuration:</p>
37 * <code>
38 * pam-sample {
39 * ch.odi.jaaspam.PamLoginModule required service=login;
40 * };
41 * </code>
42 *
43 * @author Ortwin Gl?ck
44 */
45 public class PamLoginModule implements LoginModule {
46 private Pam pam;
47 private CallbackHandler ch;
48 private Subject subject;
49 private String userPrompt;
50 private String pamUsername;
51 private boolean logged_in = false;
52 private PamPrincipal principal = null;
53
54 /**
55 *
56 */
57 public PamLoginModule() {
58 }
59
60 /**
61 * Initializes PAM with the service name.
62 *
63 * @param subject The subject to authenticate. Can be empty to make PAM ask for a username.
64 */
65 public void initialize(Subject subject, CallbackHandler callbackHandler,
66 Map sharedState, Map options) {
67 this.subject = subject;
68 String serviceName = (String) options.get("service");
69 if (serviceName == null) throw new IllegalArgumentException("service option not set in JAAS config for PamLoginModule");
70
71 pam = new Pam(serviceName, null, new JaasPamCallback());
72 this.ch = callbackHandler;
73 }
74
75 public boolean login() throws LoginException {
76 if (ch == null) throw new LoginException("Error: no CallbackHandler available " +
77 "to garner authentication information from the user");
78
79 logged_in = false;
80 this.userPrompt = pam.getItem(PamConstants.PAM_USER_PROMPT);
81 if (this.userPrompt == null) {
82 this.userPrompt = PamConstants.PAM_DEFAULT_PROMPT;
83 pam.setItem(PamConstants.PAM_USER_PROMPT, this.userPrompt);
84 }
85 int status = pam.authenticate();
86 String statusMsg = pam.getError(status);
87 switch (status) {
88 case PamConstants.PAM_USER_UNKNOWN:
89 case PamConstants.PAM_AUTH_ERR:
90 freePam();
91 throw new FailedLoginException(statusMsg);
92
93 case PamConstants.PAM_CRED_INSUFFICIENT:
94 freePam();
95 throw new CredentialsInsufficientException(statusMsg);
96
97 case PamConstants.PAM_AUTHINFO_UNAVAIL:
98 freePam();
99 throw new AuthInfoUnavailableException(statusMsg);
100
101 case PamConstants.PAM_MAXTRIES:
102 freePam();
103 throw new MaxRetriesException(statusMsg);
104
105 case PamConstants.PAM_ABORT:
106 freePam();
107 throw new PamModuleLoadingException(statusMsg);
108
109 case PamConstants.PAM_SUCCESS:
110
111 break;
112
113 default:
114 freePam();
115 throw new LoginException(statusMsg + " (pam_authenticate)");
116 }
117
118 status = pam.accountManagement();
119 statusMsg = pam.getError(status);
120 switch (status) {
121 case PamConstants.PAM_AUTHTOK_EXPIRED:
122 freePam();
123 throw new CredentialExpiredException(statusMsg);
124
125 case PamConstants.PAM_ACCT_EXPIRED:
126 freePam();
127 throw new AccountExpiredException(statusMsg);
128
129 case PamConstants.PAM_AUTH_ERR:
130 freePam();
131 throw new LoginException(statusMsg);
132
133 case PamConstants.PAM_PERM_DENIED:
134 freePam();
135 throw new PermissionDeniedException(statusMsg);
136
137 case PamConstants.PAM_USER_UNKNOWN:
138 freePam();
139 return false;
140
141 case PamConstants.PAM_AUTHINFO_UNAVAIL:
142
143 break;
144
145 case PamConstants.PAM_SUCCESS:
146
147 break;
148
149
150 }
151
152 logged_in = true;
153 return true;
154 }
155
156 public boolean commit() throws LoginException {
157 freePam();
158 if (!logged_in) return false;
159 principal = new PamPrincipal(pamUsername);
160 Set principals = subject.getPrincipals();
161 if (!principals.contains(principal)) principals.add(principal);
162
163 logged_in = false;
164 pamUsername = null;
165 return true;
166 }
167
168 public boolean abort() throws LoginException {
169 reset();
170 if (!logged_in) return false;
171 if (principal != null) {
172 logout();
173 } else {
174 freePam();
175 pamUsername = null;
176 }
177 return false;
178 }
179
180 public boolean logout() throws LoginException {
181 if (subject.isReadOnly()) return false;
182 Set principals = subject.getPrincipals();
183 if (!principals.contains(principal)) return false;
184 principals.remove(principal);
185 principal = null;
186 freePam();
187 pamUsername = null;
188 return true;
189 }
190
191 private void reset() {
192 this.principal = null;
193 this.pamUsername = null;
194 this.logged_in = false;
195 }
196
197 private void freePam() {
198 if (pam != null) {
199 pam.end();
200 pam = null;
201 }
202 }
203
204 private class JaasPamCallback implements PamCallback {
205
206 public int handle(PamMessage[] messages, PamResponse[] responses) {
207 Callback[] callbacks = toCallbacks(messages);
208 try {
209 ch.handle(callbacks);
210 answer(callbacks, responses);
211 return PamConstants.PAM_SUCCESS;
212 } catch (IOException e) {
213
214 e.printStackTrace();
215 } catch (UnsupportedCallbackException e) {
216
217 e.printStackTrace();
218 }
219 return PamConstants.PAM_CONV_ERR;
220 }
221
222 /**
223 * @param callbacks
224 * @param responses
225 */
226 private void answer(Callback[] callbacks, PamResponse[] responses) {
227 String text = null;
228 for (int i = 0; i < callbacks.length; i++) {
229 PamResponse response = null;
230 Callback callback = callbacks[i];
231 if (callback instanceof NameCallback) {
232 pamUsername = ((NameCallback) callback).getName();
233 response = new PamResponse(pamUsername);
234 } else if (callback instanceof TextInputCallback) {
235 text = ((TextInputCallback) callback).getText();
236 response = new PamResponse(text);
237 } else if (callback instanceof PasswordCallback) {
238 response = new PamResponse(String.valueOf(((PasswordCallback) callback).getPassword()));
239 } else if (callback instanceof TextOutputCallback) {
240 response = new PamResponse("");
241 } else if (callback instanceof LanguageCallback) {
242 response = new PamResponse(((LanguageCallback) callback).getLocale().toString());
243 } else if (callback instanceof ConfirmationCallback) {
244 ConfirmationCallback cb = (ConfirmationCallback) callback;
245 String option = cb.getOptions()[cb.getSelectedIndex()];
246 response = new PamResponse(option);
247 } else if (callback instanceof ChoiceCallback) {
248
249 throw new PamError("Unsupported: ChoiceCallback. Please implement a custom login module");
250 }
251 responses[i] = response;
252 }
253 if (pamUsername == null) pamUsername = text;
254 }
255
256 private Callback[] toCallbacks(PamMessage[] messages) {
257 Callback[] callbacks = new Callback[messages.length];
258 for (int i = 0; i < messages.length; i++) {
259 PamMessage message = messages[i];
260 Callback callback = null;
261 switch (message.getMsg_style()) {
262 case PamConstants.PAM_PROMPT_ECHO_ON:
263 if (userPrompt.equals(message.getMsg())) {
264 callback = new NameCallback(message.getMsg());
265 } else {
266 callback = new TextInputCallback(message.getMsg());
267 }
268 break;
269
270 case PamConstants.PAM_PROMPT_ECHO_OFF:
271
272 callback = new PasswordCallback(message.getMsg(), false);
273 break;
274
275 case PamConstants.PAM_TEXT_INFO:
276 callback = new TextOutputCallback(TextOutputCallback.INFORMATION, message.getMsg());
277 break;
278
279 case PamConstants.PAM_ERROR_MSG:
280 callback = new TextOutputCallback(TextOutputCallback.ERROR, message.getMsg());
281 break;
282
283 default:
284 throw new PamError("message style "+ message.getMsg_style() +" unknown");
285 }
286 callbacks[i] = callback;
287 }
288 return callbacks;
289 }
290 }
291 }