1 /*
2 * One-time password login PAM module
3 *
4 * Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/>
5 * Steven Murdoch <http://www.cl.cam.ac.uk/~sjm217/>
6 *
7 * Interface documentation:
8 *
9 * http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_modules.html
10 * http://www.cl.cam.ac.uk/~mgk25/otpw.html
11 *
12 * Inspired by pam_pwdfile.c by Charl P. Botha <cpbotha@ieee.org>
13 * and pam_unix/support.c (part of the standard PAM distribution)
14 *
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <pwd.h>
23 #include <syslog.h>
24
25 #define PAM_SM_AUTH
26 #define PAM_SM_SESSION
27 #include <security/pam_modules.h>
28 #ifdef OPENPAM
29 #include <security/pam_appl.h>
30 #endif
31
32 #include "otpw.h"
33
34 #define D(a) if (debug) { a; }
35
36 #define MODULE_NAME "pam_otpw"
37
38 /*
39 * Output logging information to syslog
40 *
41 * pamh pointer to the PAM handle
42 * priority, format, ... passed on to (v)syslog
43 *
44 * (based on _log_err in pam_unix/support.c)
45 */
log_message(int priority,pam_handle_t * pamh,const char * format,...)46 void log_message(int priority, pam_handle_t *pamh, const char *format, ...)
47 {
48 char *service = NULL;
49 char logname[80];
50 va_list args;
51
52 if (pamh)
53 pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
54 if (!service)
55 service = "";
56 snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service);
57
58 va_start(args, format);
59 openlog(logname, LOG_CONS | LOG_PID, LOG_AUTH);
60 vsyslog(priority, format, args); /* from BSD, not POSIX */
61 va_end(args);
62 closelog();
63 }
64
65 /*
66 * Wrapper around conversation function (a callback function provided by
67 * the PAM application to interact with the user)
68 *
69 * (based on converse in pam_unix/support.c)
70 */
converse(pam_handle_t * pamh,int nargs,struct pam_message ** message,struct pam_response ** response,int debug)71 static int converse(pam_handle_t *pamh, int nargs,
72 struct pam_message **message,
73 struct pam_response **response,
74 int debug)
75 {
76 int retval;
77 struct pam_conv *conv;
78
79 /* get pointer to conversation function */
80 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
81 if (retval != PAM_SUCCESS) {
82 log_message(LOG_ERR, pamh, "no conversation function: %s",
83 pam_strerror(pamh, retval));
84 return retval;
85 }
86
87 D(log_message(LOG_DEBUG, pamh, "calling conversation function"));
88
89 /* call conversation function */
90 retval = conv->conv(nargs, (const struct pam_message **) message,
91 response, conv->appdata_ptr);
92
93 D(log_message(LOG_DEBUG, pamh, "conversation function returned %d", retval));
94
95 if (retval != PAM_SUCCESS) {
96 log_message(LOG_WARNING, pamh, "conversation function failed: %s",
97 pam_strerror(pamh, retval));
98 }
99
100 return retval; /* propagate error status */
101 }
102
103 /* we register cleanup() to be called when the app calls pam_end(),
104 * to make sure that otpw_verify() gets a chance to remove locks */
cleanup(pam_handle_t * pamh,void * data,int err)105 static void cleanup(pam_handle_t *pamh, void *data, int err)
106 {
107 int debug = ((struct challenge *) data)->flags & OTPW_DEBUG;
108 D(log_message(LOG_DEBUG, pamh,"cleanup() called, data=%p, err=%d",
109 data, err));
110 if (((struct challenge *) data)->passwords)
111 otpw_verify((struct challenge *) data, "entryaborted");
112 free(data);
113 }
114
115 /*
116 * Issue password prompt with challenge and receive response from user
117 *
118 * (based on _set_auth_tok from pam_pwdfile.c, originally based
119 * on pam_unix/support.c but that no longer seems to exist)
120 */
get_response(pam_handle_t * pamh,char * challenge,int debug)121 static int get_response(pam_handle_t *pamh, char *challenge, int debug)
122 {
123 int retval;
124 volatile char *p;
125 struct pam_message msg, *pmsg[1];
126 struct pam_response *resp;
127 char message[81];
128
129 /* format password prompt */
130 snprintf(message, sizeof(message), "Password %s: ", challenge);
131
132 /* set up conversation call */
133 pmsg[0] = &msg;
134 msg.msg_style = PAM_PROMPT_ECHO_OFF;
135 msg.msg = message;
136 resp = NULL;
137
138 /* call conversation function */
139 if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) {
140 /* converse has already output a warning log message here */
141 return retval;
142 }
143
144 /* error handling (just to be safe) */
145 if (!resp) {
146 log_message(LOG_WARNING, pamh, "get_response(): resp==NULL");
147 return PAM_CONV_ERR;
148 }
149 if (!resp[0].resp) {
150 log_message(LOG_WARNING, pamh, "get_response(): resp[0].resp==NULL");
151 free(resp);
152 return PAM_CONV_ERR;
153 }
154
155 /* store response as PAM item */
156 pam_set_item(pamh, PAM_AUTHTOK, resp[0].resp);
157 /* sanitize and free buffer */
158 for (p = resp[0].resp; *p; p++)
159 *p = 0;
160 free(resp[0].resp);
161 free(resp);
162
163 return PAM_SUCCESS;
164 }
165
166 /*
167 * Display a notice (err==0) or error message (err==1) to the user
168 */
display_notice(pam_handle_t * pamh,int err,int debug,char * format,...)169 static int display_notice(pam_handle_t *pamh, int err, int debug,
170 char *format, ...)
171 {
172 int retval;
173 struct pam_message msg, *pmsg[1];
174 struct pam_response *resp;
175 char message[1024];
176 va_list args;
177
178 /* format message */
179 va_start(args, format);
180 vsnprintf(message, sizeof(message), format, args);
181 va_end(args);
182
183 /* set up conversation call */
184 pmsg[0] = &msg;
185 msg.msg_style = err ? PAM_ERROR_MSG : PAM_TEXT_INFO /* PAM_TEXT_INFO */;
186 msg.msg = message;
187 resp = NULL;
188
189 /* call conversation function */
190 if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) {
191 /* converse has already output a warning log message here */
192 return retval;
193 }
194
195 /* memory wants to be free */
196 if (resp) {
197 if (resp[0].resp)
198 free(resp[0].resp);
199 free(resp);
200 }
201
202 return PAM_SUCCESS;
203 }
204
205
206 /* provided entry point for auth service */
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)207 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
208 int argc, const char **argv)
209 {
210 int retval;
211 const char *username;
212 char *password;
213 struct otpw_pwdbuf *user;
214 struct challenge *ch = NULL;
215 int i, debug = 0, otpw_flags = 0;
216
217 /* parse option flags */
218 for (i = 0; i < argc; i++) {
219 if (!strcmp(argv[i], "debug")) {
220 debug = 1;
221 otpw_flags |= OTPW_DEBUG;
222 } else if (!strcmp(argv[i], "nolock")) {
223 otpw_flags |= OTPW_NOLOCK;
224 }
225 }
226
227 D(log_message(LOG_DEBUG, pamh, "pam_sm_authenticate called, flags=%d",
228 flags));
229
230 /* get user name */
231 retval = pam_get_user(pamh, &username, "login: ");
232 #ifdef OPENPAM
233 if (retval == PAM_CONV_ERR)
234 return PAM_CONV_ERR;
235 #else
236 if (retval == PAM_CONV_AGAIN)
237 return PAM_INCOMPLETE;
238 #endif
239 else if (retval != PAM_SUCCESS) {
240 log_message(LOG_NOTICE, pamh, "no username provided");
241 return PAM_USER_UNKNOWN;
242 }
243
244 /* DEBUG */
245 D(log_message(LOG_DEBUG, pamh, "username is %s", username));
246 D(log_message(LOG_DEBUG, pamh, "uid=%d, euid=%d, gid=%d, egid=%d",
247 getuid(), geteuid(), getgid(), getegid()));
248
249 /* consult POSIX password database (to find homedir, etc.) */
250 otpw_getpwnam(username, &user);
251 if (!user) {
252 log_message(LOG_NOTICE, pamh, "username not found");
253 return PAM_USER_UNKNOWN;
254 }
255
256 /*
257 * Make sure that otpw_verify() is always called to clean up locks,
258 * even if the connection is aborted while we are in get_response()
259 * or something else goes wrong.
260 */
261 ch = calloc(1, sizeof(struct challenge));
262 if (!ch)
263 return PAM_AUTHINFO_UNAVAIL;
264 retval = pam_set_data(pamh, MODULE_NAME":ch", ch, cleanup);
265 if (retval != PAM_SUCCESS) {
266 log_message(LOG_ERR, pamh, "pam_set_data() failed");
267 return PAM_AUTHINFO_UNAVAIL;
268 }
269
270 /* check whether a pseudo-user for owning OTPW files exist */
271 otpw_set_pseudouser(&otpw_pseudouser);
272
273 /* prepare OTPW challenge */
274 otpw_prepare(ch, &user->pwd, otpw_flags);
275 free(user);
276 if (otpw_pseudouser) {
277 free(otpw_pseudouser);
278 otpw_pseudouser = NULL;
279 }
280
281 D(log_message(LOG_DEBUG, pamh, "challenge: %s", ch->challenge));
282 if (ch->passwords < 1) {
283 /* it seems OTPW might not have been set up or has exhausted keys,
284 perhaps explain here in info msg how to "man otpw-gen" */
285 log_message(LOG_NOTICE, pamh, "OTPW not set up for user %s", username);
286 return PAM_AUTHINFO_UNAVAIL;
287 }
288
289 /* Issue challenge, get response */
290 retval = get_response(pamh, ch->challenge, debug);
291 if (retval != PAM_SUCCESS) {
292 log_message(LOG_ERR, pamh,"get_response() failed: %s",
293 pam_strerror(pamh, retval));
294 return PAM_AUTHINFO_UNAVAIL;
295 }
296
297 retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password);
298 if (retval != PAM_SUCCESS) {
299 log_message(LOG_ERR, pamh, "auth token not found");
300 return PAM_AUTHINFO_UNAVAIL;
301 }
302
303 if (!password) {
304 /* NULL passwords are checked in get_response so this
305 * point in the code should never be reached */
306 log_message(LOG_ERR, pamh, "password==NULL (should never happen)");
307 return PAM_AUTHINFO_UNAVAIL;
308 }
309
310 /* verify response */
311 retval = otpw_verify(ch, password);
312 if (retval == OTPW_OK) {
313 D(log_message(LOG_DEBUG, pamh, "password matches"));
314 return PAM_SUCCESS;
315 } else if (retval == OTPW_WRONG) {
316 log_message(LOG_NOTICE, pamh, "incorrect password from user %s", username);
317 return PAM_AUTH_ERR;
318 }
319 log_message(LOG_ERR, pamh, "OTPW error %d for user %s", retval, username);
320
321 return PAM_AUTHINFO_UNAVAIL;
322 }
323
324 /* another expected entry point */
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)325 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
326 int argc, const char **argv)
327 {
328 (void) pamh;
329 (void) flags;
330 (void) argc;
331 (void) argv;
332
333 /* NOP */
334
335 return PAM_SUCCESS;
336 }
337
338 /* this is called after the user has logged in */
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)339 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
340 int argc, const char **argv)
341 {
342 struct challenge *ch = NULL;
343 int retval;
344 int i, debug = 0;
345
346 /* parse option flags */
347 for (i = 0; i < argc; i++) {
348 if (!strcmp(argv[i], "debug"))
349 debug = 1;
350 }
351
352 D(log_message(LOG_DEBUG, pamh, "pam_sm_open_session called, flags=%d",
353 flags));
354
355 retval = pam_get_data(pamh, MODULE_NAME":ch", (const void **) &ch);
356 if (retval != PAM_SUCCESS || !ch) {
357 log_message(LOG_ERR, pamh, "pam_get_data() failed");
358 return PAM_SESSION_ERR;
359 }
360
361 if (!(flags & PAM_SILENT) && ch->entries >= 0) {
362 display_notice(pamh, 0, debug,
363 "Remaining one-time passwords: %d of %d%s",
364 ch->remaining, ch->entries,
365 (ch->remaining < ch->entries/2) || (ch->remaining < 20) ?
366 " (time to print new ones with otpw-gen)" : "");
367 }
368
369 return PAM_SUCCESS;
370 }
371
372 /* another expected entry point */
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)373 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
374 int argc, const char **argv)
375 {
376 (void) pamh;
377 (void) flags;
378 (void) argc;
379 (void) argv;
380
381 /* NOP */
382
383 return PAM_SUCCESS;
384 }
385
386 #ifdef PAM_STATIC
387 struct pam_module _pam_listfile_modstruct = {
388 MODULE_NAME,
389 pam_sm_authenticate,
390 pam_sm_setcred,
391 NULL,
392 NULL,
393 NULL,
394 NULL
395 };
396 #endif
397