2 * A generic conversation function for text based applications
4 * Written by Andrew Morgan <morgan@linux.kernel.org>
13 #include <sys/types.h>
18 #include <security/pam_appl.h>
19 #include <security/pam_misc.h>
21 #define INPUTSIZE PAM_MAX_MSG_SIZE /* maximum length of input+1 */
22 #define CONV_ECHO_ON 1 /* types of echo state */
23 #define CONV_ECHO_OFF 0
26 * external timeout definitions - these can be overriden by the
30 time_t pam_misc_conv_warn_time = 0; /* time when we warn */
31 time_t pam_misc_conv_die_time = 0; /* time when we timeout */
33 const char *pam_misc_conv_warn_line = N_("...Time is running out...\n");
34 const char *pam_misc_conv_die_line = N_("...Sorry, your time is up!\n");
36 int pam_misc_conv_died=0; /* application can probe this for timeout */
39 * These functions are for binary prompt manipulation.
40 * The manner in which a binary prompt is processed is application
41 * specific, so these function pointers are provided and can be
42 * initialized by the application prior to the conversation function
46 static void pam_misc_conv_delete_binary(void *appdata UNUSED,
49 PAM_BP_RENEW(delete_me, 0, 0);
52 int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
53 void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
54 = pam_misc_conv_delete_binary;
56 /* the following code is used to get text input */
58 static volatile int expired=0;
60 /* return to the previous signal handling */
61 static void reset_alarm(struct sigaction *o_ptr)
63 (void) alarm(0); /* stop alarm clock - if still ticking */
64 (void) sigaction(SIGALRM, o_ptr, NULL);
67 /* this is where we intercept the alarm signal */
68 static void time_is_up(int ignore UNUSED)
73 /* set the new alarm to hit the time_is_up() function */
74 static int set_alarm(int delay, struct sigaction *o_ptr)
76 struct sigaction new_sig;
78 sigemptyset(&new_sig.sa_mask);
80 new_sig.sa_handler = time_is_up;
81 if ( sigaction(SIGALRM, &new_sig, o_ptr) ) {
82 return 1; /* setting signal failed */
85 (void) sigaction(SIGALRM, o_ptr, NULL);
86 return 1; /* failed to set alarm */
88 return 0; /* all seems to have worked */
91 /* return the number of seconds to next alarm. 0 = no delay, -1 = expired */
92 static int get_delay(void)
96 expired = 0; /* reset flag */
99 /* has the quit time past? */
100 if (pam_misc_conv_die_time && now >= pam_misc_conv_die_time) {
101 fprintf(stderr,"%s",pam_misc_conv_die_line);
103 pam_misc_conv_died = 1; /* note we do not reset the die_time */
104 return -1; /* time is up */
107 /* has the warning time past? */
108 if (pam_misc_conv_warn_time && now >= pam_misc_conv_warn_time) {
109 fprintf(stderr, "%s", pam_misc_conv_warn_line);
110 pam_misc_conv_warn_time = 0; /* reset warn_time */
112 /* indicate remaining delay - if any */
114 return (pam_misc_conv_die_time ? pam_misc_conv_die_time - now:0 );
117 /* indicate possible warning delay */
119 if (pam_misc_conv_warn_time)
120 return (pam_misc_conv_warn_time - now);
121 else if (pam_misc_conv_die_time)
122 return (pam_misc_conv_die_time - now);
127 /* read a line of input string, giving prompt when appropriate */
128 static int read_string(int echo, const char *prompt, char **retstr)
130 struct termios term_before, term_tmp;
131 char line[INPUTSIZE];
132 struct sigaction old_sig;
133 int delay, nc = -1, have_term = 0;
136 D(("called with echo='%s', prompt='%s'.", echo ? "ON":"OFF" , prompt));
138 if (isatty(STDIN_FILENO)) { /* terminal state */
140 /* is a terminal so record settings and flush it */
141 if ( tcgetattr(STDIN_FILENO, &term_before) != 0 ) {
142 D(("<error: failed to get terminal settings>"));
146 memcpy(&term_tmp, &term_before, sizeof(term_tmp));
148 term_tmp.c_lflag &= ~(ECHO);
153 * We make a simple attempt to block TTY signals from suspending
154 * the conversation without giving PAM a chance to clean up.
158 sigaddset(&nset, SIGTSTP);
159 (void) sigprocmask(SIG_BLOCK, &nset, &oset);
162 D(("<warning: cannot turn echo off>"));
165 /* set up the signal handling */
168 /* reading the line */
170 /* this may, or may not set echo off -- drop pending input */
172 (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
174 fprintf(stderr, "%s", prompt);
176 if ( delay > 0 && set_alarm(delay, &old_sig) ) {
177 D(("<failed to set alarm>"));
181 nc = read(STDIN_FILENO, line, INPUTSIZE-1);
182 else /* we must read one line only */
183 for (nc = 0; nc < INPUTSIZE-1 && (nc?line[nc-1]:0) != '\n';
186 if ((rv=read(STDIN_FILENO, line+nc, 1)) != 1) {
193 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
194 if (!echo || expired) /* do we need a newline? */
195 fprintf(stderr,"\n");
198 reset_alarm(&old_sig);
202 } else if (nc > 0) { /* we got some user input */
203 D(("we got some user input"));
205 if (nc > 0 && line[nc-1] == '\n') { /* <NUL> terminate */
209 fprintf(stderr, "\n");
213 *retstr = x_strdup(line);
214 _pam_overwrite(line);
216 goto cleanexit; /* return malloc()ed string */
218 } else if (nc == 0) { /* Ctrl-D */
219 D(("user did not want to type anything"));
223 fprintf(stderr, "\n");
225 goto cleanexit; /* return malloc()ed "" */
226 } else if (nc == -1) {
227 /* Don't loop forever if read() returns -1. */
228 D(("error reading input from the user: %m"));
230 fprintf(stderr, "\n");
233 goto cleanexit; /* return NULL */
238 /* getting here implies that the timer expired */
240 D(("the timer appears to have expired"));
243 _pam_overwrite(line);
248 (void) sigprocmask(SIG_SETMASK, &oset, NULL);
249 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
255 /* end of read_string functions */
258 * This conversation function is supposed to be a generic PAM one.
259 * Unfortunately, it is _not_ completely compatible with the Solaris PAM
262 * Namely, for msgm's that contain multiple prompts, this function
263 * interprets "const struct pam_message **msgm" as equivalent to
264 * "const struct pam_message *msgm[]". The Solaris module
265 * implementation interprets the **msgm object as a pointer to a
266 * pointer to an array of "struct pam_message" objects (that is, a
267 * confusing amount of pointer indirection).
270 int misc_conv(int num_msg, const struct pam_message **msgm,
271 struct pam_response **response, void *appdata_ptr)
274 struct pam_response *reply;
279 D(("allocating empty response structure array."));
281 reply = (struct pam_response *) calloc(num_msg,
282 sizeof(struct pam_response));
284 D(("no memory for responses"));
288 D(("entering conversation function."));
290 for (count=0; count < num_msg; ++count) {
294 switch (msgm[count]->msg_style) {
295 case PAM_PROMPT_ECHO_OFF:
296 nc = read_string(CONV_ECHO_OFF,msgm[count]->msg, &string);
298 goto failed_conversation;
301 case PAM_PROMPT_ECHO_ON:
302 nc = read_string(CONV_ECHO_ON,msgm[count]->msg, &string);
304 goto failed_conversation;
308 if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
309 goto failed_conversation;
313 if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
314 goto failed_conversation;
317 case PAM_BINARY_PROMPT:
319 pamc_bp_t binary_prompt = NULL;
321 if (!msgm[count]->msg || !pam_binary_handler_fn) {
322 goto failed_conversation;
325 PAM_BP_RENEW(&binary_prompt,
326 PAM_BP_RCONTROL(msgm[count]->msg),
327 PAM_BP_LENGTH(msgm[count]->msg));
328 PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
329 PAM_BP_RDATA(msgm[count]->msg));
331 if (pam_binary_handler_fn(appdata_ptr,
332 &binary_prompt) != PAM_SUCCESS
333 || (binary_prompt == NULL)) {
334 goto failed_conversation;
336 string = (char *) binary_prompt;
337 binary_prompt = NULL;
342 fprintf(stderr, _("erroneous conversation (%d)\n"),
343 msgm[count]->msg_style);
344 goto failed_conversation;
347 if (string) { /* must add to reply array */
348 /* add string to list of responses */
350 reply[count].resp_retcode = 0;
351 reply[count].resp = string;
363 D(("the conversation failed"));
366 for (count=0; count<num_msg; ++count) {
367 if (reply[count].resp == NULL) {
370 switch (msgm[count]->msg_style) {
371 case PAM_PROMPT_ECHO_ON:
372 case PAM_PROMPT_ECHO_OFF:
373 _pam_overwrite(reply[count].resp);
374 free(reply[count].resp);
376 case PAM_BINARY_PROMPT:
378 void *bt_ptr = reply[count].resp;
379 pam_binary_handler_free(appdata_ptr, bt_ptr);
384 /* should not actually be able to get here... */
385 free(reply[count].resp);
387 reply[count].resp = NULL;
389 /* forget reply too */