]> granicus.if.org Git - linux-pam/blob - libpam_misc/misc_conv.c
Relevant BUGIDs: 124923
[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 <security/_pam_aconf.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 static void pam_misc_conv_delete_binary(void **delete_me)
41 {
42     if (delete_me && *delete_me) {
43         unsigned char *packet = *(unsigned char **)delete_me;
44         int length;
45
46         length = (packet[0]<<24)+(packet[1]<<16)+(packet[2]<<8)+packet[3];
47         memset(packet, 0, length);
48         free(packet);
49         *delete_me = packet = NULL;
50     }
51 }
52
53 /* These function pointers are for application specific binary
54    conversations.  One or both of the arguments to the first function
55    must be non-NULL.  The first function must return PAM_SUCCESS or
56    PAM_CONV_ERR.  If input is non-NULL, a response is expected, this
57    response should be malloc()'d and will eventually be free()'d by
58    the calling module. The structure of this malloc()'d response is as
59    follows:
60
61           { int length, char data[length] }
62
63    For convenience, the pointer used by the two function pointer
64    prototypes is 'void *'.
65
66    The ...free() fn pointer is used to discard a binary message that
67    is not of the default form.  It should be explicitly overwritten
68    when using some other convention for the structure of a binary
69    prompt (not recommended). */
70
71 int (*pam_binary_handler_fn)(const void *send, void **receive) = NULL;
72 void (*pam_binary_handler_free)(void **packet_p) = pam_misc_conv_delete_binary;
73
74 /* the following code is used to get text input */
75
76 volatile static int expired=0;
77
78 /* return to the previous signal handling */
79 static void reset_alarm(struct sigaction *o_ptr)
80 {
81     (void) alarm(0);                 /* stop alarm clock - if still ticking */
82     (void) sigaction(SIGALRM, o_ptr, NULL);
83 }
84
85 /* this is where we intercept the alarm signal */
86 static void time_is_up(int ignore)
87 {
88     expired = 1;
89 }
90
91 /* set the new alarm to hit the time_is_up() function */
92 static int set_alarm(int delay, struct sigaction *o_ptr)
93 {
94     struct sigaction new_sig;
95
96     sigemptyset(&new_sig.sa_mask);
97     new_sig.sa_flags = 0;
98     new_sig.sa_handler = time_is_up;
99     if ( sigaction(SIGALRM, &new_sig, o_ptr) ) {
100         return 1;         /* setting signal failed */
101     }
102     if ( alarm(delay) ) {
103         (void) sigaction(SIGALRM, o_ptr, NULL);
104         return 1;         /* failed to set alarm */
105     }
106     return 0;             /* all seems to have worked */
107 }
108
109 /* return the number of seconds to next alarm. 0 = no delay, -1 = expired */
110 static int get_delay(void)
111 {
112     time_t now;
113
114     expired = 0;                                        /* reset flag */
115     (void) time(&now);
116
117     /* has the quit time past? */
118     if (pam_misc_conv_die_time && now >= pam_misc_conv_die_time) {
119         fprintf(stderr,"%s",pam_misc_conv_die_line);
120
121         pam_misc_conv_died = 1;       /* note we do not reset the die_time */
122         return -1;                                           /* time is up */
123     }
124
125     /* has the warning time past? */
126     if (pam_misc_conv_warn_time && now >= pam_misc_conv_warn_time) {
127         fprintf(stderr, "%s", pam_misc_conv_warn_line);
128         pam_misc_conv_warn_time = 0;                    /* reset warn_time */
129
130         /* indicate remaining delay - if any */
131
132         return (pam_misc_conv_die_time ? pam_misc_conv_die_time - now:0 );
133     }
134
135     /* indicate possible warning delay */
136
137     if (pam_misc_conv_warn_time)
138         return (pam_misc_conv_warn_time - now);
139     else if (pam_misc_conv_die_time)
140         return (pam_misc_conv_die_time - now);
141     else
142         return 0;
143 }
144
145 /* read a line of input string, giving prompt when appropriate */
146 static char *read_string(int echo, const char *prompt)
147 {
148     struct termios term_before, term_tmp;
149     char line[INPUTSIZE];
150     struct sigaction old_sig;
151     int delay, nc, have_term=0;
152
153     D(("called with echo='%s', prompt='%s'.", echo ? "ON":"OFF" , prompt));
154
155     if (isatty(STDIN_FILENO)) {                      /* terminal state */
156
157         /* is a terminal so record settings and flush it */
158         if ( tcgetattr(STDIN_FILENO, &term_before) != 0 ) {
159             D(("<error: failed to get terminal settings>"));
160             return NULL;
161         }
162         memcpy(&term_tmp, &term_before, sizeof(term_tmp));
163         if (!echo) {
164             term_tmp.c_lflag &= ~(ECHO);
165         }
166         have_term = 1;
167
168     } else if (!echo) {
169         D(("<warning: cannot turn echo off>"));
170     }
171
172     /* set up the signal handling */
173     delay = get_delay();
174
175     /* reading the line */
176     while (delay >= 0) {
177
178         fprintf(stderr, "%s", prompt);
179         /* this may, or may not set echo off -- drop pending input */
180         if (have_term)
181             (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
182
183         if ( delay > 0 && set_alarm(delay, &old_sig) ) {
184             D(("<failed to set alarm>"));
185             break;
186         } else {
187             nc = read(STDIN_FILENO, line, INPUTSIZE-1);
188             if (have_term) {
189                 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
190                 if (!echo || expired)             /* do we need a newline? */
191                     fprintf(stderr,"\n");
192             }
193             if ( delay > 0 ) {
194                 reset_alarm(&old_sig);
195             }
196             if (expired) {
197                 delay = get_delay();
198             } else if (nc > 0) {                 /* we got some user input */
199                 char *input;
200
201                 if (nc > 0 && line[nc-1] == '\n') {     /* <NUL> terminate */
202                     line[--nc] = '\0';
203                 } else {
204                     line[nc] = '\0';
205                 }
206                 input = x_strdup(line);
207                 _pam_overwrite(line);
208
209                 return input;                  /* return malloc()ed string */
210             } else if (nc == 0) {                                /* Ctrl-D */
211                 D(("user did not want to type anything"));
212                 fprintf(stderr, "\n");
213                 break;
214             }
215         }
216     }
217
218     /* getting here implies that the timer expired */
219     if (have_term)
220         (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
221
222     memset(line, 0, INPUTSIZE);                      /* clean up */
223     return NULL;
224 }
225
226 /* end of read_string functions */
227
228 int misc_conv(int num_msg, const struct pam_message **msgm,
229               struct pam_response **response, void *appdata_ptr)
230 {
231     int count=0;
232     struct pam_response *reply;
233
234     if (num_msg <= 0)
235         return PAM_CONV_ERR;
236
237     D(("allocating empty response structure array."));
238
239     reply = (struct pam_response *) calloc(num_msg,
240                                            sizeof(struct pam_response));
241     if (reply == NULL) {
242         D(("no memory for responses"));
243         return PAM_CONV_ERR;
244     }
245
246     D(("entering conversation function."));
247
248     for (count=0; count < num_msg; ++count) {
249         char *string=NULL;
250
251         switch (msgm[count]->msg_style) {
252         case PAM_PROMPT_ECHO_OFF:
253             string = read_string(CONV_ECHO_OFF,msgm[count]->msg);
254             if (string == NULL) {
255                 goto failed_conversation;
256             }
257             break;
258         case PAM_PROMPT_ECHO_ON:
259             string = read_string(CONV_ECHO_ON,msgm[count]->msg);
260             if (string == NULL) {
261                 goto failed_conversation;
262             }
263             break;
264         case PAM_ERROR_MSG:
265             if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
266                 goto failed_conversation;
267             }
268             break;
269         case PAM_TEXT_INFO:
270             if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
271                 goto failed_conversation;
272             }
273             break;
274         case PAM_BINARY_PROMPT:
275         {
276             void *pack_out=NULL;
277             const void *pack_in = msgm[count]->msg;
278
279             if (!pam_binary_handler_fn
280                 || pam_binary_handler_fn(pack_in, &pack_out) != PAM_SUCCESS
281                 || pack_out == NULL) {
282                 goto failed_conversation;
283             }
284             string = (char *) pack_out;
285             pack_out = NULL;
286
287             break;
288         }
289         default:
290             fprintf(stderr, "erroneous conversation (%d)\n"
291                     ,msgm[count]->msg_style);
292             goto failed_conversation;
293         }
294
295         if (string) {                         /* must add to reply array */
296             /* add string to list of responses */
297
298             reply[count].resp_retcode = 0;
299             reply[count].resp = string;
300             string = NULL;
301         }
302     }
303
304     /* New (0.59+) behavior is to always have a reply - this is
305        compatable with the X/Open (March 1997) spec. */
306     *response = reply;
307     reply = NULL;
308
309     return PAM_SUCCESS;
310
311 failed_conversation:
312
313     if (reply) {
314         for (count=0; count<num_msg; ++count) {
315             if (reply[count].resp == NULL) {
316                 continue;
317             }
318             switch (msgm[count]->msg_style) {
319             case PAM_PROMPT_ECHO_ON:
320             case PAM_PROMPT_ECHO_OFF:
321                 _pam_overwrite(reply[count].resp);
322                 free(reply[count].resp);
323                 break;
324             case PAM_BINARY_PROMPT:
325                 pam_binary_handler_free((void **) &reply[count].resp);
326                 break;
327             case PAM_ERROR_MSG:
328             case PAM_TEXT_INFO:
329                 /* should not actually be able to get here... */
330                 free(reply[count].resp);
331             }                                            
332             reply[count].resp = NULL;
333         }
334         /* forget reply too */
335         free(reply);
336         reply = NULL;
337     }
338
339     return PAM_CONV_ERR;
340 }
341