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