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