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