1 /*
2 * $Id: misc_conv.c,v 1.1 2000/09/12 20:19:41 will Exp $
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 "pam_misc.h"
25
26 #ifdef DEBUG
27 #define D(x) printf x
28 #else
29 #define D(x)
30 #endif
31
32 #define INPUTSIZE PAM_MAX_MSG_SIZE /* maximum length of input+1 */
33 #define CONV_ECHO_ON 1 /* types of echo state */
34 #define CONV_ECHO_OFF 0
35
36 /*
37 * external timeout definitions - these can be overriden by the
38 * application.
39 */
40
41 time_t pam_misc_conv_warn_time = 0; /* time when we warn */
42 time_t pam_misc_conv_die_time = 0; /* time when we timeout */
43
44 const char *pam_misc_conv_warn_line = "..\a.Time is running out...\n";
45 const char *pam_misc_conv_die_line = "..\a.Sorry, your time is up!\n";
46
47 int pam_misc_conv_died=0; /* application can probe this for timeout */
48
49
50 static void
pam_overwrite(char * str)51 pam_overwrite(char *str)
52 {
53 memset(str, ' ', strlen(str));
54 }
55
56 static char *
x_strdup(char * s)57 x_strdup(char *s)
58 {
59 return ((s) ? strdup(s) : NULL);
60 }
61
pam_misc_conv_delete_binary(void ** delete_me)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 = (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 */
reset_alarm(struct sigaction * o_ptr)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 */
time_is_up(int ignore)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 */
set_alarm(int delay,struct sigaction * o_ptr)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 */
get_delay(void)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 */
read_string(int echo,const char * prompt)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 }
188 have_term = 1;
189
190 } else if (!echo) {
191 D(("<warning: cannot turn echo off>"));
192 }
193
194 /* set up the signal handling */
195 delay = get_delay();
196
197 /* reading the line */
198 while (delay >= 0) {
199
200 fprintf(stderr, "%s", prompt);
201 /* this may, or may not set echo off -- drop pending input */
202 if (have_term)
203 (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
204
205 if ( delay > 0 && set_alarm(delay, &old_sig) ) {
206 D(("<failed to set alarm>"));
207 break;
208 } else {
209 nc = read(STDIN_FILENO, line, INPUTSIZE-1);
210 if (have_term) {
211 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
212 if (!echo || expired) /* do we need a newline? */
213 fprintf(stderr,"\n");
214 }
215 if ( delay > 0 ) {
216 reset_alarm(&old_sig);
217 }
218 if (expired) {
219 delay = get_delay();
220 } else if (nc > 0) { /* we got some user input */
221 char *input;
222
223 if (nc > 0 && line[nc-1] == '\n') { /* <NUL> terminate */
224 line[--nc] = '\0';
225 } else {
226 line[nc] = '\0';
227 }
228 input = x_strdup(line);
229 pam_overwrite(line);
230
231 return input; /* return malloc()ed string */
232 } else if (nc == 0) { /* Ctrl-D */
233 D(("user did not want to type anything"));
234 fprintf(stderr, "\n");
235 break;
236 }
237 }
238 }
239
240 /* getting here implies that the timer expired */
241 if (have_term)
242 (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
243
244 memset(line, 0, INPUTSIZE); /* clean up */
245 return NULL;
246 }
247
248 /* end of read_string functions */
249
misc_conv(int num_msg,const struct pam_message ** msgm,struct pam_response ** response,void * appdata_ptr)250 int misc_conv(int num_msg, const struct pam_message **msgm,
251 struct pam_response **response, void *appdata_ptr)
252 {
253 int count=0;
254 struct pam_response *reply;
255
256 if (num_msg <= 0)
257 return PAM_CONV_ERR;
258
259 D(("allocating empty response structure array."));
260
261 reply = (struct pam_response *) calloc(num_msg,
262 sizeof(struct pam_response));
263 if (reply == NULL) {
264 D(("no memory for responses"));
265 return PAM_CONV_ERR;
266 }
267
268 D(("entering conversation function."));
269
270 for (count=0; count < num_msg; ++count) {
271 char *string=NULL;
272
273 switch (msgm[count]->msg_style) {
274 case PAM_PROMPT_ECHO_OFF:
275 string = read_string(CONV_ECHO_OFF,msgm[count]->msg);
276 if (string == NULL) {
277 goto failed_conversation;
278 }
279 break;
280 case PAM_PROMPT_ECHO_ON:
281 string = read_string(CONV_ECHO_ON,msgm[count]->msg);
282 if (string == NULL) {
283 goto failed_conversation;
284 }
285 break;
286 case PAM_ERROR_MSG:
287 if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
288 goto failed_conversation;
289 }
290 break;
291 case PAM_TEXT_INFO:
292 if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
293 goto failed_conversation;
294 }
295 break;
296 #ifdef PAM_BINARY_PROMPT
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 #endif
313 default:
314 fprintf(stderr, "erroneous conversation (%d)\n"
315 ,msgm[count]->msg_style);
316 goto failed_conversation;
317 }
318
319 if (string) { /* must add to reply array */
320 /* add string to list of responses */
321
322 reply[count].resp_retcode = 0;
323 reply[count].resp = string;
324 string = NULL;
325 }
326 }
327
328 /* New (0.59+) behavior is to always have a reply - this is
329 compatable with the X/Open (March 1997) spec. */
330 *response = reply;
331 reply = NULL;
332
333 return PAM_SUCCESS;
334
335 failed_conversation:
336
337 if (reply) {
338 for (count=0; count<num_msg; ++count) {
339 if (reply[count].resp == NULL) {
340 continue;
341 }
342 switch (msgm[count]->msg_style) {
343 case PAM_PROMPT_ECHO_ON:
344 case PAM_PROMPT_ECHO_OFF:
345 pam_overwrite(reply[count].resp);
346 free(reply[count].resp);
347 break;
348 #ifdef PAM_BINARY_PROMPT
349 case PAM_BINARY_PROMPT:
350 pam_binary_handler_free((void **) &reply[count].resp);
351 break;
352 #endif
353 case PAM_ERROR_MSG:
354 case PAM_TEXT_INFO:
355 /* should not actually be able to get here... */
356 free(reply[count].resp);
357 }
358 reply[count].resp = NULL;
359 }
360 /* forget reply too */
361 free(reply);
362 reply = NULL;
363 }
364
365 return PAM_CONV_ERR;
366 }
367
368