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