1 /*
2 * A generic conversation function for text based applications
3 *
4 * Written by Andrew Morgan <morgan@linux.kernel.org>
5 */
6
7 #include "config.h"
8
9 #include <signal.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/types.h>
14 #include <termios.h>
15 #include <time.h>
16 #include <unistd.h>
17
18 #include <security/pam_appl.h>
19 #include <security/pam_misc.h>
20 #include "pam_inline.h"
21
22 #define INPUTSIZE PAM_MISC_CONV_BUFSIZE /* maximum length of input+1 */
23 #define CONV_ECHO_ON 1 /* types of echo state */
24 #define CONV_ECHO_OFF 0
25
26 /*
27 * external timeout definitions - these can be overridden by the
28 * application.
29 */
30
31 time_t pam_misc_conv_warn_time = 0; /* time when we warn */
32 time_t pam_misc_conv_die_time = 0; /* time when we timeout */
33
34 const char *pam_misc_conv_warn_line = N_("...Time is running out...\n");
35 const char *pam_misc_conv_die_line = N_("...Sorry, your time is up!\n");
36
37 int pam_misc_conv_died=0; /* application can probe this for timeout */
38
39 /*
40 * These functions are for binary prompt manipulation.
41 * The manner in which a binary prompt is processed is application
42 * specific, so these function pointers are provided and can be
43 * initialized by the application prior to the conversation function
44 * being used.
45 */
46
47 static void pam_misc_conv_delete_binary(void *appdata UNUSED,
48 pamc_bp_t *delete_me)
49 {
50 PAM_BP_RENEW(delete_me, 0, 0);
51 }
52
53 int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
54 void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
55 = pam_misc_conv_delete_binary;
56
57 /* the following code is used to get text input */
58
59 static volatile int expired=0;
60
61 /* return to the previous signal handling */
62 static void reset_alarm(struct sigaction *o_ptr)
63 {
64 (void) alarm(0); /* stop alarm clock - if still ticking */
65 (void) sigaction(SIGALRM, o_ptr, NULL);
66 }
67
68 /* this is where we intercept the alarm signal */
69 static void time_is_up(int ignore UNUSED)
70 {
71 expired = 1;
72 }
73
74 /* set the new alarm to hit the time_is_up() function */
75 static int set_alarm(int delay, struct sigaction *o_ptr)
76 {
77 struct sigaction new_sig;
78
79 sigemptyset(&new_sig.sa_mask);
80 new_sig.sa_flags = 0;
81 new_sig.sa_handler = time_is_up;
82 if ( sigaction(SIGALRM, &new_sig, o_ptr) ) {
83 return 1; /* setting signal failed */
84 }
85 if ( alarm(delay) ) {
86 (void) sigaction(SIGALRM, o_ptr, NULL);
87 return 1; /* failed to set alarm */
88 }
89 return 0; /* all seems to have worked */
90 }
91
92 /* return the number of seconds to next alarm. 0 = no delay, -1 = expired */
93 static int get_delay(void)
94 {
95 time_t now;
96
97 expired = 0; /* reset flag */
98 (void) time(&now);
99
100 /* has the quit time past? */
101 if (pam_misc_conv_die_time && now >= pam_misc_conv_die_time) {
102 fprintf(stderr,"%s",pam_misc_conv_die_line);
103
104 pam_misc_conv_died = 1; /* note we do not reset the die_time */
105 return -1; /* time is up */
106 }
107
108 /* has the warning time past? */
109 if (pam_misc_conv_warn_time && now >= pam_misc_conv_warn_time) {
110 fprintf(stderr, "%s", pam_misc_conv_warn_line);
111 pam_misc_conv_warn_time = 0; /* reset warn_time */
112
113 /* indicate remaining delay - if any */
114
115 return (pam_misc_conv_die_time ? pam_misc_conv_die_time - now:0 );
116 }
117
118 /* indicate possible warning delay */
119
120 if (pam_misc_conv_warn_time)
121 return (pam_misc_conv_warn_time - now);
122 else if (pam_misc_conv_die_time)
123 return (pam_misc_conv_die_time - now);
124 else
125 return 0;
126 }
127
128 /* read a line of input string, giving prompt when appropriate */
129 static int read_string(int echo, const char *prompt, char **retstr)
130 {
131 struct termios term_before, term_tmp;
132 char line[INPUTSIZE];
133 struct sigaction old_sig;
134 int delay, nc = -1, have_term = 0;
135 sigset_t oset, nset;
136
137 D(("called with echo='%s', prompt='%s'.", echo ? "ON":"OFF" , prompt));
138
139 if (isatty(STDIN_FILENO)) { /* terminal state */
140
141 /* is a terminal so record settings and flush it */
142 if ( tcgetattr(STDIN_FILENO, &term_before) != 0 ) {
143 D(("<error: failed to get terminal settings>"));
144 *retstr = NULL;
145 return -1;
146 }
147 memcpy(&term_tmp, &term_before, sizeof(term_tmp));
148 if (!echo) {
149 term_tmp.c_lflag &= ~(ECHO);
150 }
151 have_term = 1;
152
153 /*
154 * We make a simple attempt to block TTY signals from suspending
155 * the conversation without giving PAM a chance to clean up.
156 */
157
158 sigemptyset(&nset);
159 sigaddset(&nset, SIGTSTP);
160 (void) sigprocmask(SIG_BLOCK, &nset, &oset);
161
162 } else if (!echo) {
163 D(("<warning: cannot turn echo off>"));
164 }
165
166 /* set up the signal handling */
167 delay = get_delay();
168
169 /* reading the line */
170 while (delay >= 0) {
171 /* this may, or may not set echo off -- drop pending input */
172 if (have_term)
173 (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
174
175 fprintf(stderr, "%s", prompt);
176
177 if ( delay > 0 && set_alarm(delay, &old_sig) ) {
178 D(("<failed to set alarm>"));
179 break;
180 } else {
181 if (have_term)
182 nc = read(STDIN_FILENO, line, INPUTSIZE-1);
183 else /* we must read one line only */
184 for (nc = 0; nc < INPUTSIZE-1 && (nc?line[nc-1]:0) != '\n';
185 nc++) {
186 int rv;
187 if ((rv=read(STDIN_FILENO, line+nc, 1)) != 1) {
188 if (rv < 0) {
189 pam_overwrite_n(line, (unsigned int) nc);
190 nc = rv;
191 }
192 break;
193 }
194 }
195 if (have_term) {
196 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
197 if (!echo || expired) /* do we need a newline? */
198 fprintf(stderr, "\n");
199 }
200 if ( delay > 0 ) {
201 reset_alarm(&old_sig);
202 }
203 if (expired) {
204 delay = get_delay();
205 } else if (nc > 0) { /* we got some user input */
206 D(("we got some user input"));
207
208 if (line[nc-1] == '\n') { /* <NUL> terminate */
209 line[--nc] = '\0';
210 } else {
211 if (echo) {
212 fprintf(stderr, "\n");
213 }
214 line[nc] = '\0';
215 }
216 *retstr = strdup(line);
217 pam_overwrite_array(line);
218 if (!*retstr) {
219 D(("no memory for response string"));
220 nc = -1;
221 }
222
223 goto cleanexit; /* return malloc()ed string */
224
225 } else if (nc == 0) { /* Ctrl-D */
226 D(("user did not want to type anything"));
227
228 *retstr = NULL;
229 if (echo) {
230 fprintf(stderr, "\n");
231 }
232 goto cleanexit; /* return malloc()ed "" */
233 } else if (nc == -1) {
234 /* Don't loop forever if read() returns -1. */
235 D(("error reading input from the user: %m"));
236 if (echo) {
237 fprintf(stderr, "\n");
238 }
239 *retstr = NULL;
240 goto cleanexit; /* return NULL */
241 }
242 }
243 }
244
245 /* getting here implies that the timer expired */
246
247 D(("the timer appears to have expired"));
248
249 *retstr = NULL;
250 pam_overwrite_array(line);
251
252 cleanexit:
253
254 if (have_term) {
255 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
256 (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_before);
257 }
258
259 return nc;
260 }
261
262 /* end of read_string functions */
263
264 /*
265 * This conversation function is supposed to be a generic PAM one.
266 * Unfortunately, it is _not_ completely compatible with the Solaris PAM
267 * codebase.
268 *
269 * Namely, for msgm's that contain multiple prompts, this function
270 * interprets "const struct pam_message **msgm" as equivalent to
271 * "const struct pam_message *msgm[]". The Solaris module
272 * implementation interprets the **msgm object as a pointer to a
273 * pointer to an array of "struct pam_message" objects (that is, a
274 * confusing amount of pointer indirection).
275 */
276
277 int misc_conv(int num_msg, const struct pam_message **msgm,
278 struct pam_response **response, void *appdata_ptr)
279 {
280 int count=0;
281 struct pam_response *reply;
282
283 if (num_msg <= 0)
284 return PAM_CONV_ERR;
285
286 D(("allocating empty response structure array."));
287
288 reply = (struct pam_response *) calloc(num_msg,
289 sizeof(struct pam_response));
290 if (reply == NULL) {
291 D(("no memory for responses"));
292 return PAM_CONV_ERR;
293 }
294
295 D(("entering conversation function."));
296
297 for (count=0; count < num_msg; ++count) {
298 char *string=NULL;
299 int nc;
300
301 switch (msgm[count]->msg_style) {
302 case PAM_PROMPT_ECHO_OFF:
303 nc = read_string(CONV_ECHO_OFF,msgm[count]->msg, &string);
304 if (nc < 0) {
305 goto failed_conversation;
306 }
307 break;
308 case PAM_PROMPT_ECHO_ON:
309 nc = read_string(CONV_ECHO_ON,msgm[count]->msg, &string);
310 if (nc < 0) {
311 goto failed_conversation;
312 }
313 break;
314 case PAM_ERROR_MSG:
315 if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
316 goto failed_conversation;
317 }
318 break;
319 case PAM_TEXT_INFO:
320 if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
321 goto failed_conversation;
322 }
323 break;
324 case PAM_BINARY_PROMPT:
325 {
326 pamc_bp_t binary_prompt = NULL;
327
328 if (!msgm[count]->msg || !pam_binary_handler_fn) {
329 goto failed_conversation;
330 }
331
332 PAM_BP_RENEW(&binary_prompt,
333 PAM_BP_RCONTROL(msgm[count]->msg),
334 PAM_BP_LENGTH(msgm[count]->msg));
335 PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
336 PAM_BP_RDATA(msgm[count]->msg));
337
338 if (pam_binary_handler_fn(appdata_ptr,
339 &binary_prompt) != PAM_SUCCESS
340 || (binary_prompt == NULL)) {
341 goto failed_conversation;
342 }
343 string = (char *) binary_prompt;
344 binary_prompt = NULL;
345
346 break;
347 }
348 default:
349 fprintf(stderr, _("erroneous conversation (%d)\n"),
350 msgm[count]->msg_style);
351 goto failed_conversation;
352 }
353
354 if (string) { /* must add to reply array */
355 /* add string to list of responses */
356
357 reply[count].resp_retcode = 0;
358 reply[count].resp = string;
359 string = NULL;
360 }
361 }
362
363 *response = reply;
364 reply = NULL;
365
366 return PAM_SUCCESS;
367
368 failed_conversation:
369
370 D(("the conversation failed"));
371
372 if (reply) {
373 for (count=0; count<num_msg; ++count) {
374 if (reply[count].resp == NULL) {
375 continue;
376 }
377 switch (msgm[count]->msg_style) {
378 case PAM_PROMPT_ECHO_ON:
379 case PAM_PROMPT_ECHO_OFF:
380 pam_overwrite_string(reply[count].resp);
381 free(reply[count].resp);
382 break;
383 case PAM_BINARY_PROMPT:
384 {
385 void *bt_ptr = reply[count].resp;
386 pam_binary_handler_free(appdata_ptr, bt_ptr);
387 break;
388 }
389 case PAM_ERROR_MSG:
390 case PAM_TEXT_INFO:
391 /* should not actually be able to get here... */
392 free(reply[count].resp);
393 }
394 reply[count].resp = NULL;
395 }
396 /* forget reply too */
397 free(reply);
398 reply = NULL;
399 }
400
401 return PAM_CONV_ERR;
402 }