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