misc_conv.c 10 KB

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