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