1 /*
2  * pam_oath.c - a PAM module for OATH one-time passwords
3  * Copyright (C) 2009-2016 Simon Josefsson
4  *
5  * This program is free software: you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation, either version 3 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see
17  * <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include <config.h>
22 
23 #include "oath.h"
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <ctype.h>
29 
30 /* Libtool defines PIC for shared objects */
31 #ifndef PIC
32 #define PAM_STATIC
33 #endif
34 
35 /* These #defines must be present according to PAM documentation. */
36 #define PAM_SM_AUTH
37 #define PAM_SM_ACCOUNT
38 #define PAM_SM_SESSION
39 #define PAM_SM_PASSWORD
40 
41 #ifdef HAVE_SECURITY_PAM_APPL_H
42 #include <security/pam_appl.h>
43 #endif
44 #ifdef HAVE_SECURITY_PAM_MODULES_H
45 #include <security/pam_modules.h>
46 #endif
47 
48 #define D(x) do {							\
49     printf ("[%s:%s(%d)] ", __FILE__, __FUNCTION__, __LINE__);		\
50     printf x;								\
51     printf ("\n");							\
52   } while (0)
53 #define DBG(x) if (cfg.debug) { D(x); }
54 
55 #ifndef PAM_EXTERN
56 #ifdef PAM_STATIC
57 #define PAM_EXTERN static
58 #else
59 #define PAM_EXTERN extern
60 #endif
61 #endif
62 
63 #define MIN_OTP_LEN 6
64 #define MAX_OTP_LEN 8
65 
66 struct cfg
67 {
68   int debug;
69   int alwaysok;
70   int try_first_pass;
71   int use_first_pass;
72   char *usersfile;
73   unsigned digits;
74   unsigned window;
75 };
76 
77 static void
parse_cfg(int flags,int argc,const char ** argv,struct cfg * cfg)78 parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg)
79 {
80   int i;
81 
82   cfg->debug = 0;
83   cfg->alwaysok = 0;
84   cfg->try_first_pass = 0;
85   cfg->use_first_pass = 0;
86   cfg->usersfile = NULL;
87   cfg->digits = -1;
88   cfg->window = 5;
89 
90   for (i = 0; i < argc; i++)
91     {
92       if (strcmp (argv[i], "debug") == 0)
93 	cfg->debug = 1;
94       if (strcmp (argv[i], "alwaysok") == 0)
95 	cfg->alwaysok = 1;
96       if (strcmp (argv[i], "try_first_pass") == 0)
97 	cfg->try_first_pass = 1;
98       if (strcmp (argv[i], "use_first_pass") == 0)
99 	cfg->use_first_pass = 1;
100       if (strncmp (argv[i], "usersfile=", 10) == 0)
101 	cfg->usersfile = (char *) argv[i] + 10;
102       if (strncmp (argv[i], "digits=", 7) == 0)
103 	cfg->digits = atoi (argv[i] + 7);
104       if (strncmp (argv[i], "window=", 7) == 0)
105 	cfg->window = atoi (argv[i] + 7);
106     }
107 
108   if (cfg->digits != 6 && cfg->digits != 7 && cfg->digits != 8)
109     {
110       if (cfg->digits != -1)
111 	D (("only 6, 7, and 8 OTP lengths are supported: invalid value %d",
112 	    cfg->digits));
113       cfg->digits = 0;
114     }
115 
116   if (cfg->debug)
117     {
118       D (("called."));
119       D (("flags %d argc %d", flags, argc));
120       for (i = 0; i < argc; i++)
121 	D (("argv[%d]=%s", i, argv[i]));
122       D (("debug=%d", cfg->debug));
123       D (("alwaysok=%d", cfg->alwaysok));
124       D (("try_first_pass=%d", cfg->try_first_pass));
125       D (("use_first_pass=%d", cfg->use_first_pass));
126       D (("usersfile=%s", cfg->usersfile ? cfg->usersfile : "(null)"));
127       D (("digits=%d", cfg->digits));
128       D (("window=%d", cfg->window));
129     }
130 }
131 
132 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)133 pam_sm_authenticate (pam_handle_t * pamh,
134 		     int flags, int argc, const char **argv)
135 {
136   int retval, rc;
137   const char *user = NULL;
138   const char *password = NULL;
139   char otp[MAX_OTP_LEN + 1];
140   int password_len = 0;
141   struct pam_conv *conv;
142   struct pam_message *pmsg[1], msg[1];
143   struct pam_response *resp;
144   int nargs = 1;
145   struct cfg cfg;
146   char *query_prompt = NULL;
147   char *onlypasswd = strdup ("");	/* empty passwords never match */
148 
149   if (!onlypasswd)
150     {
151       retval = PAM_BUF_ERR;
152       goto done;
153     }
154 
155   parse_cfg (flags, argc, argv, &cfg);
156 
157   retval = pam_get_user (pamh, &user, NULL);
158   if (retval != PAM_SUCCESS)
159     {
160       DBG (("get user returned error: %s", pam_strerror (pamh, retval)));
161       goto done;
162     }
163   DBG (("get user returned: %s", user));
164 
165   if (cfg.try_first_pass || cfg.use_first_pass)
166     {
167       retval = pam_get_item (pamh, PAM_AUTHTOK, (const void **) &password);
168       if (retval != PAM_SUCCESS)
169 	{
170 	  DBG (("get password returned error: %s",
171 		pam_strerror (pamh, retval)));
172 	  goto done;
173 	}
174       DBG (("get password returned: %s", password));
175     }
176 
177   if (cfg.use_first_pass && password == NULL)
178     {
179       DBG (("use_first_pass set and no password, giving up"));
180       retval = PAM_AUTH_ERR;
181       goto done;
182     }
183 
184   rc = oath_init ();
185   if (rc != OATH_OK)
186     {
187       DBG (("oath_init() failed (%d)", rc));
188       retval = PAM_AUTHINFO_UNAVAIL;
189       goto done;
190     }
191 
192   if (password == NULL)
193     {
194       retval = pam_get_item (pamh, PAM_CONV, (const void **) &conv);
195       if (retval != PAM_SUCCESS)
196 	{
197 	  DBG (("get conv returned error: %s", pam_strerror (pamh, retval)));
198 	  goto done;
199 	}
200 
201       pmsg[0] = &msg[0];
202       {
203 	const char *query_template = "One-time password (OATH) for `%s': ";
204 	size_t len = strlen (query_template) + strlen (user);
205 	size_t wrote;
206 
207 	query_prompt = malloc (len);
208 	if (!query_prompt)
209 	  {
210 	    retval = PAM_BUF_ERR;
211 	    goto done;
212 	  }
213 
214 	wrote = snprintf (query_prompt, len, query_template, user);
215 	if (wrote < 0 || wrote >= len)
216 	  {
217 	    retval = PAM_BUF_ERR;
218 	    goto done;
219 	  }
220 
221 	msg[0].msg = query_prompt;
222       }
223       msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
224       resp = NULL;
225 
226       retval = conv->conv (nargs, (const struct pam_message **) pmsg,
227 			   &resp, conv->appdata_ptr);
228 
229       free (query_prompt);
230       query_prompt = NULL;
231 
232       if (retval != PAM_SUCCESS)
233 	{
234 	  DBG (("conv returned error: %s", pam_strerror (pamh, retval)));
235 	  goto done;
236 	}
237 
238       DBG (("conv returned: %s", resp->resp));
239 
240       password = resp->resp;
241     }
242 
243   if (password)
244     password_len = strlen (password);
245   else
246     {
247       DBG (("Could not read password"));
248       retval = PAM_AUTH_ERR;
249       goto done;
250     }
251 
252   if (password_len < MIN_OTP_LEN)
253     {
254       DBG (("OTP too short: %s", password));
255       retval = PAM_AUTH_ERR;
256       goto done;
257     }
258   else if (cfg.digits != 0 && password_len < cfg.digits)
259     {
260       DBG (("OTP shorter than digits=%d: %s", cfg.digits, password));
261       retval = PAM_AUTH_ERR;
262       goto done;
263     }
264   else if (cfg.digits == 0 && password_len > MAX_OTP_LEN)
265     {
266       DBG (("OTP too long (and no digits=): %s", password));
267       retval = PAM_AUTH_ERR;
268       goto done;
269     }
270   else if (cfg.digits != 0 && password_len > cfg.digits)
271     {
272       free (onlypasswd);
273       onlypasswd = strdup (password);
274       if (!onlypasswd)
275         {
276           retval = PAM_BUF_ERR;
277           goto done;
278         }
279 
280       /* user entered their system password followed by generated OTP? */
281 
282       onlypasswd[password_len - cfg.digits] = '\0';
283 
284       DBG (("Password: %s ", onlypasswd));
285 
286       memcpy (otp, password + password_len - cfg.digits, cfg.digits);
287       otp[cfg.digits] = '\0';
288 
289       retval = pam_set_item (pamh, PAM_AUTHTOK, onlypasswd);
290       if (retval != PAM_SUCCESS)
291 	{
292 	  DBG (("set_item returned error: %s", pam_strerror (pamh, retval)));
293 	  goto done;
294 	}
295     }
296   else
297     {
298       strcpy (otp, password);
299       password = NULL;
300     }
301 
302   DBG (("OTP: %s", otp ? otp : "(null)"));
303 
304   {
305     time_t last_otp;
306 
307     rc = oath_authenticate_usersfile (cfg.usersfile,
308 				      user,
309 				      otp, cfg.window, onlypasswd, &last_otp);
310     DBG (("authenticate rc %d (%s: %s) last otp %s", rc,
311 	  oath_strerror_name (rc) ? oath_strerror_name (rc) : "UNKNOWN",
312 	  oath_strerror (rc), ctime (&last_otp)));
313   }
314 
315   if (rc != OATH_OK)
316     {
317       DBG (("One-time password not authorized to login as user '%s'", user));
318       retval = PAM_AUTH_ERR;
319       goto done;
320     }
321 
322   retval = PAM_SUCCESS;
323 
324 done:
325   oath_done ();
326   free (query_prompt);
327   free (onlypasswd);
328   if (cfg.alwaysok && retval != PAM_SUCCESS)
329     {
330       DBG (("alwaysok needed (otherwise return with %d)", retval));
331       retval = PAM_SUCCESS;
332     }
333   DBG (("done. [%s]", pam_strerror (pamh, retval)));
334 
335   return retval;
336 }
337 
338 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)339 pam_sm_setcred (pam_handle_t * pamh, int flags, int argc, const char **argv)
340 {
341   return PAM_SUCCESS;
342 }
343 
344 #ifdef PAM_STATIC
345 
346 struct pam_module _pam_oath_modstruct = {
347   "pam_oath",
348   pam_sm_authenticate,
349   pam_sm_setcred,
350   NULL,
351   NULL,
352   NULL,
353   NULL
354 };
355 
356 #endif
357