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