(root)/
Linux-PAM-1.5.3/
libpam_misc/
misc_conv.c
       1  /*
       2   * A generic conversation function for text based applications
       3   *
       4   * Written by Andrew Morgan <morgan@linux.kernel.org>
       5   */
       6  
       7  #include "config.h"
       8  
       9  #include <signal.h>
      10  #include <stdio.h>
      11  #include <stdlib.h>
      12  #include <string.h>
      13  #include <sys/types.h>
      14  #include <termios.h>
      15  #include <time.h>
      16  #include <unistd.h>
      17  
      18  #include <security/pam_appl.h>
      19  #include <security/pam_misc.h>
      20  #include "pam_inline.h"
      21  
      22  #define INPUTSIZE PAM_MISC_CONV_BUFSIZE      /* maximum length of input+1 */
      23  #define CONV_ECHO_ON  1                            /* types of echo state */
      24  #define CONV_ECHO_OFF 0
      25  
      26  /*
      27   * external timeout definitions - these can be overridden by the
      28   * application.
      29   */
      30  
      31  time_t pam_misc_conv_warn_time = 0;                  /* time when we warn */
      32  time_t pam_misc_conv_die_time  = 0;               /* time when we timeout */
      33  
      34  const char *pam_misc_conv_warn_line = N_("...Time is running out...\n");
      35  const char *pam_misc_conv_die_line  = N_("...Sorry, your time is up!\n");
      36  
      37  int pam_misc_conv_died=0;       /* application can probe this for timeout */
      38  
      39  /*
      40   * These functions are for binary prompt manipulation.
      41   * The manner in which a binary prompt is processed is application
      42   * specific, so these function pointers are provided and can be
      43   * initialized by the application prior to the conversation function
      44   * being used.
      45   */
      46  
      47  static void pam_misc_conv_delete_binary(void *appdata UNUSED,
      48  					pamc_bp_t *delete_me)
      49  {
      50      PAM_BP_RENEW(delete_me, 0, 0);
      51  }
      52  
      53  int (*pam_binary_handler_fn)(void *appdata, pamc_bp_t *prompt_p) = NULL;
      54  void (*pam_binary_handler_free)(void *appdata, pamc_bp_t *prompt_p)
      55        = pam_misc_conv_delete_binary;
      56  
      57  /* the following code is used to get text input */
      58  
      59  static volatile int expired=0;
      60  
      61  /* return to the previous signal handling */
      62  static void reset_alarm(struct sigaction *o_ptr)
      63  {
      64      (void) alarm(0);                 /* stop alarm clock - if still ticking */
      65      (void) sigaction(SIGALRM, o_ptr, NULL);
      66  }
      67  
      68  /* this is where we intercept the alarm signal */
      69  static void time_is_up(int ignore UNUSED)
      70  {
      71      expired = 1;
      72  }
      73  
      74  /* set the new alarm to hit the time_is_up() function */
      75  static int set_alarm(int delay, struct sigaction *o_ptr)
      76  {
      77      struct sigaction new_sig;
      78  
      79      sigemptyset(&new_sig.sa_mask);
      80      new_sig.sa_flags = 0;
      81      new_sig.sa_handler = time_is_up;
      82      if ( sigaction(SIGALRM, &new_sig, o_ptr) ) {
      83  	return 1;         /* setting signal failed */
      84      }
      85      if ( alarm(delay) ) {
      86  	(void) sigaction(SIGALRM, o_ptr, NULL);
      87  	return 1;         /* failed to set alarm */
      88      }
      89      return 0;             /* all seems to have worked */
      90  }
      91  
      92  /* return the number of seconds to next alarm. 0 = no delay, -1 = expired */
      93  static int get_delay(void)
      94  {
      95      time_t now;
      96  
      97      expired = 0;                                        /* reset flag */
      98      (void) time(&now);
      99  
     100      /* has the quit time past? */
     101      if (pam_misc_conv_die_time && now >= pam_misc_conv_die_time) {
     102  	fprintf(stderr,"%s",pam_misc_conv_die_line);
     103  
     104  	pam_misc_conv_died = 1;       /* note we do not reset the die_time */
     105  	return -1;                                           /* time is up */
     106      }
     107  
     108      /* has the warning time past? */
     109      if (pam_misc_conv_warn_time && now >= pam_misc_conv_warn_time) {
     110  	fprintf(stderr, "%s", pam_misc_conv_warn_line);
     111  	pam_misc_conv_warn_time = 0;                    /* reset warn_time */
     112  
     113  	/* indicate remaining delay - if any */
     114  
     115  	return (pam_misc_conv_die_time ? pam_misc_conv_die_time - now:0 );
     116      }
     117  
     118      /* indicate possible warning delay */
     119  
     120      if (pam_misc_conv_warn_time)
     121  	return (pam_misc_conv_warn_time - now);
     122      else if (pam_misc_conv_die_time)
     123  	return (pam_misc_conv_die_time - now);
     124      else
     125  	return 0;
     126  }
     127  
     128  /* read a line of input string, giving prompt when appropriate */
     129  static int read_string(int echo, const char *prompt, char **retstr)
     130  {
     131      struct termios term_before, term_tmp;
     132      char line[INPUTSIZE];
     133      struct sigaction old_sig;
     134      int delay, nc = -1, have_term = 0;
     135      sigset_t oset, nset;
     136  
     137      D(("called with echo='%s', prompt='%s'.", echo ? "ON":"OFF" , prompt));
     138  
     139      if (isatty(STDIN_FILENO)) {                      /* terminal state */
     140  
     141  	/* is a terminal so record settings and flush it */
     142  	if ( tcgetattr(STDIN_FILENO, &term_before) != 0 ) {
     143  	    D(("<error: failed to get terminal settings>"));
     144  	    *retstr = NULL;
     145  	    return -1;
     146  	}
     147  	memcpy(&term_tmp, &term_before, sizeof(term_tmp));
     148  	if (!echo) {
     149  	    term_tmp.c_lflag &= ~(ECHO);
     150  	}
     151  	have_term = 1;
     152  
     153  	/*
     154  	 * We make a simple attempt to block TTY signals from suspending
     155  	 * the conversation without giving PAM a chance to clean up.
     156  	 */
     157  
     158  	sigemptyset(&nset);
     159  	sigaddset(&nset, SIGTSTP);
     160  	(void) sigprocmask(SIG_BLOCK, &nset, &oset);
     161  
     162      } else if (!echo) {
     163  	D(("<warning: cannot turn echo off>"));
     164      }
     165  
     166      /* set up the signal handling */
     167      delay = get_delay();
     168  
     169      /* reading the line */
     170      while (delay >= 0) {
     171  	/* this may, or may not set echo off -- drop pending input */
     172  	if (have_term)
     173  	    (void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_tmp);
     174  
     175  	fprintf(stderr, "%s", prompt);
     176  
     177  	if ( delay > 0 && set_alarm(delay, &old_sig) ) {
     178  	    D(("<failed to set alarm>"));
     179  	    break;
     180  	} else {
     181  	    if (have_term)
     182  		nc = read(STDIN_FILENO, line, INPUTSIZE-1);
     183  	    else                             /* we must read one line only */
     184  		for (nc = 0; nc < INPUTSIZE-1 && (nc?line[nc-1]:0) != '\n';
     185  		     nc++) {
     186  		    int rv;
     187  		    if ((rv=read(STDIN_FILENO, line+nc, 1)) != 1) {
     188  			if (rv < 0) {
     189  			    pam_overwrite_n(line, (unsigned int) nc);
     190  			    nc = rv;
     191  			}
     192  			break;
     193  		    }
     194  		}
     195  	    if (have_term) {
     196  		(void) tcsetattr(STDIN_FILENO, TCSADRAIN, &term_before);
     197  		if (!echo || expired)             /* do we need a newline? */
     198  		    fprintf(stderr, "\n");
     199  	    }
     200  	    if ( delay > 0 ) {
     201  		reset_alarm(&old_sig);
     202  	    }
     203  	    if (expired) {
     204  		delay = get_delay();
     205  	    } else if (nc > 0) {                 /* we got some user input */
     206  		D(("we got some user input"));
     207  
     208  		if (line[nc-1] == '\n') {     /* <NUL> terminate */
     209  		    line[--nc] = '\0';
     210  		} else {
     211  		    if (echo) {
     212  			fprintf(stderr, "\n");
     213  		    }
     214  		    line[nc] = '\0';
     215  		}
     216  		*retstr = strdup(line);
     217  		pam_overwrite_array(line);
     218  		if (!*retstr) {
     219  		    D(("no memory for response string"));
     220  		    nc = -1;
     221  		}
     222  
     223  		goto cleanexit;                /* return malloc()ed string */
     224  
     225  	    } else if (nc == 0) {                                /* Ctrl-D */
     226  		D(("user did not want to type anything"));
     227  
     228  		*retstr = NULL;
     229  		if (echo) {
     230  		    fprintf(stderr, "\n");
     231  		}
     232  		goto cleanexit;                /* return malloc()ed "" */
     233  	    } else if (nc == -1) {
     234  		/* Don't loop forever if read() returns -1. */
     235  		D(("error reading input from the user: %m"));
     236  		if (echo) {
     237  		    fprintf(stderr, "\n");
     238  		}
     239  		*retstr = NULL;
     240  		goto cleanexit;                /* return NULL */
     241  	    }
     242  	}
     243      }
     244  
     245      /* getting here implies that the timer expired */
     246  
     247      D(("the timer appears to have expired"));
     248  
     249      *retstr = NULL;
     250      pam_overwrite_array(line);
     251  
     252   cleanexit:
     253  
     254      if (have_term) {
     255  	(void) sigprocmask(SIG_SETMASK, &oset, NULL);
     256  	(void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &term_before);
     257      }
     258  
     259      return nc;
     260  }
     261  
     262  /* end of read_string functions */
     263  
     264  /*
     265   * This conversation function is supposed to be a generic PAM one.
     266   * Unfortunately, it is _not_ completely compatible with the Solaris PAM
     267   * codebase.
     268   *
     269   * Namely, for msgm's that contain multiple prompts, this function
     270   * interprets "const struct pam_message **msgm" as equivalent to
     271   * "const struct pam_message *msgm[]". The Solaris module
     272   * implementation interprets the **msgm object as a pointer to a
     273   * pointer to an array of "struct pam_message" objects (that is, a
     274   * confusing amount of pointer indirection).
     275   */
     276  
     277  int misc_conv(int num_msg, const struct pam_message **msgm,
     278  	      struct pam_response **response, void *appdata_ptr)
     279  {
     280      int count=0;
     281      struct pam_response *reply;
     282  
     283      if (num_msg <= 0)
     284  	return PAM_CONV_ERR;
     285  
     286      D(("allocating empty response structure array."));
     287  
     288      reply = (struct pam_response *) calloc(num_msg,
     289  					   sizeof(struct pam_response));
     290      if (reply == NULL) {
     291  	D(("no memory for responses"));
     292  	return PAM_CONV_ERR;
     293      }
     294  
     295      D(("entering conversation function."));
     296  
     297      for (count=0; count < num_msg; ++count) {
     298  	char *string=NULL;
     299  	int nc;
     300  
     301  	switch (msgm[count]->msg_style) {
     302  	case PAM_PROMPT_ECHO_OFF:
     303  	    nc = read_string(CONV_ECHO_OFF,msgm[count]->msg, &string);
     304  	    if (nc < 0) {
     305  		goto failed_conversation;
     306  	    }
     307  	    break;
     308  	case PAM_PROMPT_ECHO_ON:
     309  	    nc = read_string(CONV_ECHO_ON,msgm[count]->msg, &string);
     310  	    if (nc < 0) {
     311  		goto failed_conversation;
     312  	    }
     313  	    break;
     314  	case PAM_ERROR_MSG:
     315  	    if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
     316  		goto failed_conversation;
     317  	    }
     318  	    break;
     319  	case PAM_TEXT_INFO:
     320  	    if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
     321  		goto failed_conversation;
     322  	    }
     323  	    break;
     324  	case PAM_BINARY_PROMPT:
     325  	{
     326  	    pamc_bp_t binary_prompt = NULL;
     327  
     328  	    if (!msgm[count]->msg || !pam_binary_handler_fn) {
     329  		goto failed_conversation;
     330  	    }
     331  
     332  	    PAM_BP_RENEW(&binary_prompt,
     333  			 PAM_BP_RCONTROL(msgm[count]->msg),
     334  			 PAM_BP_LENGTH(msgm[count]->msg));
     335  	    PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
     336  			PAM_BP_RDATA(msgm[count]->msg));
     337  
     338  	    if (pam_binary_handler_fn(appdata_ptr,
     339  				      &binary_prompt) != PAM_SUCCESS
     340  		|| (binary_prompt == NULL)) {
     341  		goto failed_conversation;
     342  	    }
     343  	    string = (char *) binary_prompt;
     344  	    binary_prompt = NULL;
     345  
     346  	    break;
     347  	}
     348  	default:
     349  	    fprintf(stderr, _("erroneous conversation (%d)\n"),
     350  		   msgm[count]->msg_style);
     351  	    goto failed_conversation;
     352  	}
     353  
     354  	if (string) {                         /* must add to reply array */
     355  	    /* add string to list of responses */
     356  
     357  	    reply[count].resp_retcode = 0;
     358  	    reply[count].resp = string;
     359  	    string = NULL;
     360  	}
     361      }
     362  
     363      *response = reply;
     364      reply = NULL;
     365  
     366      return PAM_SUCCESS;
     367  
     368  failed_conversation:
     369  
     370      D(("the conversation failed"));
     371  
     372      if (reply) {
     373  	for (count=0; count<num_msg; ++count) {
     374  	    if (reply[count].resp == NULL) {
     375  		continue;
     376  	    }
     377  	    switch (msgm[count]->msg_style) {
     378  	    case PAM_PROMPT_ECHO_ON:
     379  	    case PAM_PROMPT_ECHO_OFF:
     380  		pam_overwrite_string(reply[count].resp);
     381  		free(reply[count].resp);
     382  		break;
     383  	    case PAM_BINARY_PROMPT:
     384  	      {
     385  		void *bt_ptr = reply[count].resp;
     386  		pam_binary_handler_free(appdata_ptr, bt_ptr);
     387  		break;
     388  	      }
     389  	    case PAM_ERROR_MSG:
     390  	    case PAM_TEXT_INFO:
     391  		/* should not actually be able to get here... */
     392  		free(reply[count].resp);
     393  	    }
     394  	    reply[count].resp = NULL;
     395  	}
     396  	/* forget reply too */
     397  	free(reply);
     398  	reply = NULL;
     399      }
     400  
     401      return PAM_CONV_ERR;
     402  }