1 /*-
2 * Copyright (c) 2014,2018 Stefan Grundmann
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <syslog.h>
32 #include <time.h>
33 #include <string.h>
34
35 #include <security/pam_modules.h>
36 #include <security/pam_appl.h>
37 #ifndef __linux__
38 #include <security/openpam.h>
39 #endif
40
41 #include "ocra.h"
42
43 #ifndef PAM_EXTERN
44 #define PAM_EXTERN
45 #endif
46
47 static int
adjust_return(const char * nodata,int ret)48 adjust_return(const char *nodata, int ret)
49 {
50 if (PAM_SUCCESS != ret && (PAM_AUTHINFO_UNAVAIL == ret ||
51 PAM_NO_MODULE_DATA == ret)) {
52 /* Change the return code, if requested */
53 if (NULL != nodata) {
54 if (strcmp(nodata, "succeed") == 0) {
55 ret = PAM_SUCCESS;
56 } else if (strcmp(nodata, "ignore") == 0) {
57 ret = PAM_IGNORE;
58 } else if (strcmp(nodata, "fail") != 0) {
59 syslog(LOG_ERR, "Unknown \"nodata\" value");
60 ret = PAM_SERVICE_ERR;
61 }
62 }
63 /*
64 * PAM_NO_MODULE_DATA is the result when a fake prompt is
65 * displayed. If not handled above, treat it like an
66 * authentication failure.
67 */
68 if (PAM_NO_MODULE_DATA == ret) {
69 ret = PAM_AUTH_ERR;
70 }
71 }
72 return ret;
73 }
74
75 static void
fmt_prompt(char * mbuf,int msize,const char * questions,const char * pmsg)76 fmt_prompt(char *mbuf, int msize, const char *questions, const char *pmsg)
77 {
78 char *mptr = mbuf;
79 const char *pptr = pmsg;
80 int mrsize = 0;
81 time_t epoch_seconds;
82 struct tm *now;
83 char fmt[] = "%.Ns ";
84 int N, i, j, ql;
85
86 if (NULL == pmsg) {
87 if (msize)
88 *mptr = '\0';
89 return;
90 }
91 msize--; /* Ensure we always have room for
92 * trailing '\0' */
93
94 ql = strlen(questions);
95
96 while ((mrsize < msize) && '\0' != *pptr) {
97 /* Copy over the first part of the string */
98 while ((mrsize < msize) && '\0' != *pptr) {
99 if ('%' != *pptr) {
100 *mptr++ = *pptr++;
101 mrsize++;
102 } else {
103 pptr++;
104 break;
105 }
106 }
107
108 /*
109 * Handle the conversion character. If not understood,
110 * the '%' will be quietly dropped.
111 */
112 switch (*pptr) {
113 case '%': /* Literal '%' */
114 *mptr++ = '%';
115 mrsize++;
116 pptr++;
117 break;
118
119 case 'u': /* UTC time */
120 time(&epoch_seconds);
121 now = gmtime(&epoch_seconds);
122 strftime(mptr, msize - mrsize,
123 "%Y-%m-%dT%H:%M:%SZ %Z", now);
124 mrsize = strlen(mbuf);
125 mptr = &mbuf[mrsize];
126 pptr++;
127 break;
128
129 case 'l': /* Local time */
130 time(&epoch_seconds);
131 now = localtime(&epoch_seconds);
132 strftime(mptr, msize - mrsize,
133 "%Y-%m-%dT%H:%M:%S%z %Z", now);
134 mrsize = strlen(mbuf);
135 mptr = &mbuf[mrsize];
136 pptr++;
137 break;
138
139 case 'c': /* Challenge question */
140 snprintf(mptr, msize - mrsize,
141 "%s", questions);
142 mrsize = strlen(mbuf);
143 mptr = &mbuf[mrsize];
144 pptr++;
145 break;
146 default: /* Maybe %Nc */
147 if ('0' < *pptr && '9' >= *pptr && 'c' == *(pptr + 1)) {
148 fmt[2] = *pptr; /* set precision in fmt to N */
149 N = *pptr - '0'; /* int value */
150
151 /* print with fmt */
152 for (i = 0, j = ql % N ? ql / N : ql / N - 1;
153 0 < j; i += N, j--) {
154 snprintf(mptr, msize - mrsize,
155 fmt, questions + i);
156 mrsize = strlen(mbuf);
157 mptr = &mbuf[mrsize];
158 }
159
160 /* print remaining */
161 snprintf(mptr, msize - mrsize, "%s",
162 questions + i);
163 mrsize = strlen(mbuf);
164 mptr = &mbuf[mrsize];
165 pptr += 2;
166 }
167 break;
168 }
169 }
170
171 /* Terminate the prompt string */
172 *mptr = '\0';
173 }
174
175 static void
make_prompt(char * buf,int bsize,const char * questions,const char * cmsg,const char * rmsg)176 make_prompt(char *buf, int bsize, const char *questions,
177 const char *cmsg, const char *rmsg)
178 {
179 char cbuf[512];
180 char rbuf[512];
181
182 /* Create the default prompt strings, if necessary */
183 if (NULL == cmsg && NULL == rmsg) {
184 cmsg = "OCRA Challenge: %4c";
185 rmsg = "OCRA Response: ";
186 }
187 /* Generate each prompt */
188 fmt_prompt(cbuf, sizeof(cbuf), questions, cmsg);
189 fmt_prompt(rbuf, sizeof(rbuf), questions, rmsg);
190
191 /* Concatenate them to the final prompt */
192 if (NULL != cmsg && NULL != rmsg) {
193 snprintf(buf, bsize, "%s\n%s", cbuf, rbuf);
194 } else if (NULL != cmsg) {
195 snprintf(buf, bsize, "%s\n", cbuf);
196 } else {
197 snprintf(buf, bsize, "%s", rbuf);
198 }
199 }
200
201
202 static int
get_response(pam_handle_t * pamh,char * prompt,char ** response)203 get_response(pam_handle_t *pamh, char *prompt, char **response)
204 {
205 int ret;
206 struct pam_message msg;
207 const struct pam_message *msgp = &msg;
208 const struct pam_conv *conv = NULL;
209 struct pam_response *presponse = NULL;
210
211 pam_get_item(pamh, PAM_CONV, (const void **)&conv);
212 pam_set_item(pamh, PAM_AUTHTOK, NULL);
213
214 msg.msg_style = PAM_PROMPT_ECHO_ON;
215 msg.msg = prompt;
216
217 ret = (*conv->conv) (1, &msgp, &presponse, conv->appdata_ptr);
218
219 if (NULL != presponse) {
220 if (PAM_SUCCESS == ret) {
221 *response = presponse->resp;
222 presponse->resp = NULL;
223 }
224 }
225 return ret;
226 }
227
228 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char * argv[])229 pam_sm_authenticate(pam_handle_t *pamh, int flags,
230 int argc, const char *argv[])
231 {
232 int qret;
233 int ret;
234 const char *dir = NULL;
235 const char *fake_suite = NULL;
236 const char *nodata = NULL;
237 const char *cmsg = NULL;
238 const char *rmsg = NULL;
239 char *questions;
240 const char *user;
241 char *response = NULL;
242 char fmt[512];
243
244 (void)flags;
245
246 /* Get options */
247 #ifdef __linux__
248 while (argc--) {
249 if (0 == strncmp(*argv, "fake_prompt=", 12))
250 fake_suite = *argv + 12;
251 else if (0 == strncmp(*argv, "dir=", 4))
252 dir = *argv + 4;
253 else if (0 == strncmp(*argv, "nodata=", 7))
254 nodata = *argv + 7;
255 else if (0 == strncmp(*argv, "cmsg=", 5))
256 cmsg = *argv + 5;
257 else if (0 == strncmp(*argv, "rmsg=", 5))
258 rmsg = *argv + 5;
259 argv++;
260 }
261 #else
262 (void)argc;
263 (void)argv;
264
265 fake_suite = openpam_get_option(pamh, "fake_prompt");
266 dir = openpam_get_option(pamh, "dir");
267 nodata = openpam_get_option(pamh, "nodata");
268 cmsg = openpam_get_option(pamh, "cmsg");
269 rmsg = openpam_get_option(pamh, "rmsg");
270 #endif
271 pam_get_item(pamh, PAM_USER, (const void **)&user);
272
273 openlog("pam_ocra", 0, LOG_AUTHPRIV);
274
275 /*
276 * Generate the challenge "question". If the user doesn't have any
277 * OCRA data, a fake challenge may be generated.
278 *
279 * Valid expected return codes are:
280 * PAM_SUCCESS - User has OCRA data and a valid challenge
281 * was generated. A valid user response
282 * will be expected.
283 * PAM_AUTHINFO_UNAVAIL - User does not have OCRA data and no fake
284 * challenge was generated. This result
285 * may be modified based on the "nodata"
286 * setting.
287 * PAM_NO_MODULE_DATA - User does not have OCRA data and a fake
288 * challenge was generated. The ultimate
289 * return code after user input will be
290 * based on the "nodata" setting.
291 *
292 * Any other return code will be returned as-is.
293 */
294 qret = challenge(dir, user, &questions, nodata, fake_suite);
295
296 /* Only continue if there is a user prompt to display */
297 if (PAM_SUCCESS != qret && PAM_NO_MODULE_DATA != qret) {
298 ret = qret;
299 goto end;
300 }
301 /* Generate the prompt */
302 make_prompt(fmt, sizeof(fmt), questions, cmsg, rmsg);
303
304 if (PAM_SUCCESS != (ret = get_response(pamh, fmt, &response)))
305 goto end;
306
307 if (PAM_SUCCESS != qret) {
308 /*
309 * There was no OCRA data, so don't bother checking the
310 * response
311 */
312 ret = qret;
313 } else {
314 ret = verify(dir, user, questions, response);
315 }
316
317 free(response);
318 end:
319 closelog();
320 return adjust_return(nodata, ret);
321 }
322
323 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char * argv[])324 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[])
325 {
326 (void)pamh;
327 (void)flags;
328 (void)argc;
329 (void)argv;
330
331 return PAM_SUCCESS;
332 }
333
334 PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char * argv[])335 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[])
336 {
337 (void)pamh;
338 (void)flags;
339 (void)argc;
340 (void)argv;
341
342 return PAM_SUCCESS;
343 }
344
345 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char * argv[])346 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[])
347 {
348 (void)pamh;
349 (void)flags;
350 (void)argc;
351 (void)argv;
352
353 return PAM_SUCCESS;
354 }
355
356 PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])357 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[])
358 {
359 (void)pamh;
360 (void)flags;
361 (void)argc;
362 (void)argv;
363
364 return PAM_SUCCESS;
365 }
366
367 PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])368 pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[])
369 {
370 (void)pamh;
371 (void)flags;
372 (void)argc;
373 (void)argv;
374
375 return PAM_SUCCESS;
376 }
377
378 #ifdef PAM_MODULE_ENTRY
379 PAM_MODULE_ENTRY("pam_ocra");
380 #endif
381