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