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