A PAM module or application can communicate with a user in a number of ways: command line, dialog box, and so on. As a result, the designer of a PAM consumer that communicates with users needs to write aconversation function. A conversation function passes messages between the user and module independently of the means of communication. A conversation function derives the message type from themsg_style parameter in the conversation function callbackpam_message parameter. Seepam_start(3PAM).
Developers should make no assumptions about how PAM is to communicate with users.Rather, the application should exchange messages with the user until the operation iscomplete. Applications should display the message strings for the conversation functionwithout interpretation or modification. An individual message can contain multiplelines, control characters, or extra blank spaces. Note that service modules areresponsible for localizing any strings sent to the conversation function.
A sample conversation function,pam_tty_conv(), is providedbelow. Thepam_tty_conv() takes the following arguments:
num_msg – The number of messages that are beingpassed to the function.
**mess – A pointer to the buffer that holds themessages from the user.
**resp – A pointer to the buffer that holds theresponses to the user.
*my_data – Pointer to the applicationdata.
The sample function gets user input fromstdin. The routine needsto allocate memory for the response buffer. A maximum, PAM_MAX_NUM_MSG, can be set tolimit the number of messages. If the conversation function returns an error, theconversation function is responsible for clearing and freeing any memory that has beenallocated for responses. In addition, the conversation function must set the responsepointer to NULL. Note that clearing memory should be accomplished using a zero fillapproach. The caller of the conversation function is responsible for freeing anyresponses that have been returned to the caller. To conduct the conversation, thefunction loops through the messages from the user application. Valid messages arewritten tostdout, and any errors are written tostderr.
Example 6 PAM Conversation Function/* * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. */#pragma ident"@(#)pam_tty_conv.c1.405/02/12 SMI" #define__EXTENSIONS__/* to expose flockfile and friends in stdio.h */#include <errno.h>#include <libgen.h>#include <malloc.h>#include <signal.h>#include <stdio.h>#include <stdlib.h>#include <strings.h>#include <stropts.h>#include <unistd.h>#include <termio.h>#include <security/pam_appl.h>static int ctl_c; /* was the conversation interrupted? *//* ARGSUSED 1 */static voidinterrupt(int x) { ctl_c = 1;}/* getinput -- read user input from stdin abort on ^C *Entrynoecho == TRUE, don't echo input. *ExitUser's input. *If interrupted, send SIGINT to caller for processing. */static char *getinput(int noecho) { struct termio tty; unsigned short tty_flags; char input[PAM_MAX_RESP_SIZE]; int c; int i = 0; void (*sig)(int); ctl_c = 0; sig = signal(SIGINT, interrupt); if (noecho) { (void) ioctl(fileno(stdin), TCGETA, &tty); tty_flags = tty.c_lflag; tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); (void) ioctl(fileno(stdin), TCSETAF, &tty); } /* go to end, but don't overflow PAM_MAX_RESP_SIZE */ flockfile(stdin); while (ctl_c == 0 && (c = getchar_unlocked()) != '\n' && c != '\r' && c != EOF) { if (i < PAM_MAX_RESP_SIZE) { input[i++] = (char) c; } } funlockfile(stdin); input[i] = '\0'; if (noecho) { tty.c_lflag = tty_flags; (void) ioctl(fileno(stdin), TCSETAW, &tty); (void) fputc('\n', stdout); } (void) signal(SIGINT, sig); if (ctl_c == 1) (void) kill(getpid(), SIGINT); return (strdup(input));}/* Service modules do not clean up responses if an error is returned. * Free responses here. */static voidfree_resp(int num_msg, struct pam_response *pr) { int i; struct pam_response *r = pr; if (pr == NULL) return; for (i = 0; i < num_msg; i++, r++) { if (r->resp) { /* clear before freeing -- may be a password */ bzero(r->resp, strlen(r->resp)); free(r->resp); r->resp = NULL; } } free(pr);}/* ARGSUSED */intpam_tty_conv(int num_msg, struct pam_message **mess, struct pam_response **resp, void *my_data) { struct pam_message *m = *mess; struct pam_response *r; int i; if (num_msg <= 0 || num_msg >= PAM_MAX_NUM_MSG) { (void) fprintf(stderr, "bad number of messages %d " "<= 0 || >= %d\n", num_msg, PAM_MAX_NUM_MSG); *resp = NULL; return (PAM_CONV_ERR); } if ((*resp = r = calloc(num_msg, sizeof (struct pam_response))) == NULL) return (PAM_BUF_ERR); errno = 0; /* don't propagate possible EINTR */ /* Loop through messages */ for (i = 0; i < num_msg; i++) { int echo_off; /* bad message from service module */ if (m->msg == NULL) { (void) fprintf(stderr, "message[%d]: %d/NULL\n", i, m->msg_style); goto err; } /* * fix up final newline: * removed for prompts * added back for messages */ if (m->msg[strlen(m->msg)] == '\n') m->msg[strlen(m->msg)] = '\0'; r->resp = NULL; r->resp_retcode = 0; echo_off = 0; switch (m->msg_style) { case PAM_PROMPT_ECHO_OFF: echo_off = 1; /*FALLTHROUGH*/ case PAM_PROMPT_ECHO_ON: (void) fputs(m->msg, stdout); r->resp = getinput(echo_off); break; case PAM_ERROR_MSG: (void) fputs(m->msg, stderr); (void) fputc('\n', stderr); break; case PAM_TEXT_INFO: (void) fputs(m->msg, stdout); (void) fputc('\n', stdout); break; default: (void) fprintf(stderr, "message[%d]: unknown type " "%d/val=\"%s\"\n", i, m->msg_style, m->msg); /* error, service module won't clean up */ goto err; } if (errno == EINTR) goto err; /* next message/response */ m++; r++; } return (PAM_SUCCESS);err: free_resp(i, r); *resp = NULL; return (PAM_CONV_ERR);}