[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

A PAM OPIE module



/* pam_opie.c - A module to interface the Linux-PAM Pluggable
 * Authentication Module to the OPIE one-time-password system.
 *
 * You need both the NRL OPIE and Linux-PAM packages for this to be
 * useful.
 *
 * Copyright (C) 1999 by Mark Atwood <mra@pobox.com>
 * You may redistribute this module under the GNU Public License version 2.
 *
 * Based on pam_skey.c, which is
 * Copyright (C) 1997 Pharos Intellectual Property Pty Ltd.
 * by Martin Pool <m.pool@pharos.com.au>
 * and is licensed under the GNU Public License version 2.
 */

/* NO WARRANTY: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */

/* The following options are permitted in the pam.conf file:

   debug = enable debugging output to syslog(AUTHPRIV.DEBUG)

   noecho = don't echo the password back.  This is not on by default
   because OPIE passwords are long, and because the system design assumes
   that they may be snooped anyhow.  Turn it on if you wish.

   use_first_pass and try_first_pass are not implemented because it is
   unlikely that the OPIE password would be the same as that of
   another PAM.

 */

#include <stdio.h>
#include <syslog.h>
#include <string.h>

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD
#include <security/pam_modules.h>
#include <security/pam_misc.h>

#include <opie.h>

/* Prototypes */
int read_options(int argc, const char ** argv);
char *xstrdup(const char *x);

/* Module-globals representing command-line flags */
int debug=0, noecho=0;

/* The guts of PAM-OPIE authentication.  Returns a PAM_* status code,
 * depending on whether the user was successfully authenticated or
 * not.  This function attempts to map the error codes returned by OPIE
 * into the corresponding PAM codes: they ought to be close but
 * probably not perfect. */
int _pam_auth_opie(pam_handle_t *pamh,
		   int flags,
		   int argc,
		   const char **argv)
{

  char *username;

  char challenge[OPIE_CHALLENGE_MAX+1+2];
  char response[OPIE_RESPONSE_MAX+1];

  int rc;
  int knownuser;

  struct opie op;
  struct pam_conv *conv;

  struct pam_message msg[1];
  const struct pam_message *pmsg[1];
  struct pam_response *presponse=NULL;

  /* get the user's name */
  /* it looks like "/bin/login" ignores our prompt string, and uses it's
     own instead, but we'll still set it, just to be correct. */
  rc = pam_get_user(pamh, &username, "OPIE login: ");
  if (rc != PAM_SUCCESS) {
    syslog(LOG_AUTHPRIV|LOG_ERR,
	   "pam_opie: fail pam_get_user: %s",
	   pam_strerror(pamh, rc));
    return rc;
  }
  if (debug)
    syslog(LOG_AUTHPRIV|LOG_NOTICE,
	   "pam_opie: login attempt for user '%s'",
	   username);

  /* get the string with which to challenge the user */
  rc = opiechallenge(&op, username, challenge);

  /* the OPIE source claims that now that a challange has been issued, an
     opieverify MUST be called to clear locks and state, even if an error
     occurs. we dont do that, but it seems to work anyway. */

  if (rc < 0) {
    /* a system error of some sort */
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: fail opiechallange. rc=%d err=%m", rc);
    return PAM_SYSTEM_ERR;
  } else if (rc > 1) {
    /* a system error of some sort */
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: fail opiechallange. rc=%d user %s locked?",
	   rc, username);
    return PAM_AUTHINFO_UNAVAIL;
  } else if (rc == 1) {
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: user '%s' in not OPIE database. faking it.",
	   username);
    knownuser = 0;
  } else {
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: user '%s' in OPIE database", username);
    knownuser = 1;
  }

  if (debug)
    syslog(LOG_AUTHPRIV|LOG_NOTICE,
	   "pam_opie: challenge is '%s'",
	   challenge);

  /* Get the conversation function.
     This function prompts the user for their token */
  rc = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
  if (rc != PAM_SUCCESS) {
    syslog(LOG_AUTHPRIV|LOG_ERR,
	   "pam_opie: fail pam_get_item PAM_CONV: %s",
	   pam_strerror(pamh, rc));
    return rc;
  }

  /* marshall the args to the conversation function */
  pmsg[0] = &msg[0];
  msg[0].msg_style = noecho ? PAM_PROMPT_ECHO_OFF : PAM_PROMPT_ECHO_ON;
  /* here is why the challange chararray was declared with 2 extra bytes */
  msg[0].msg = strcat(challenge, ": ");
  presponse = NULL;
  
  /* Prompt the user with the challenge, and get their response */
  rc = conv->conv(1,
		  (const struct pam_message **) &pmsg,
		  &presponse,
		  conv->appdata_ptr);
  if (rc != PAM_SUCCESS) {
    syslog(LOG_AUTHPRIV|LOG_ERR,
	   "pam_opie: fail conversation: %s",
	   pam_strerror(pamh, rc));
    return rc;
  }

  strncpy(response, presponse[0].resp, OPIE_RESPONSE_MAX+1);

  /* stash the AUTHTOKen away, in case we are stacked with another
     auth module that is trying the "use_first_pass" or
     "try_first_pass" options. What this means is, if the user enters
     an ordinary UNIX password, and we are properly stacked on top of
     pam_unix, even tho we will fail, the pam_unix can take the
     already entered password and validate it without having to prompt
     the user again. */
  
  pam_set_item(pamh, PAM_AUTHTOK, xstrdup(response));
 
  /* a bit insecure, as this will record the password attemts in syslog */
  if (debug > 1)
    syslog(LOG_AUTHPRIV|LOG_NOTICE,
	   "pam_opie: got response '%s'",
	   response);
  
  if (!knownuser) {
      syslog(LOG_AUTHPRIV|LOG_WARNING,
	     "pam_opie: user '%s' authentication failed. "
	     "unknown user",
	     username);
      /* you would thing that we should return USER_UNKNOWN, but if we
	 do, PAM immediate quits, rather than falling thru to the next
	 module */
      return PAM_AUTH_ERR;
      return PAM_USER_UNKNOWN;
  }
  
  /* Check the response with OPIE.
     OPIE already knows what username because it was told in the call
     to opiechallange */

  rc = opieverify(&op, response);

  if (rc < 0) {
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: fail opieverify. rc=%d err=%m", rc);
    return PAM_SYSTEM_ERR;
  } else if (rc == 0) {
    if (debug)
      syslog(LOG_AUTHPRIV|LOG_NOTICE,
	     "pam_opie: user '%s' authentication OK",
	     username);
    return PAM_SUCCESS;
  } else {
    syslog(LOG_AUTHPRIV|LOG_WARNING,
	   "pam_opie: user '%s' authentication failed. rc=%d reason=%s",
	   username, rc, "bad response");
    return PAM_AUTH_ERR;
  }
}

int read_options(int argc, const char **argv)
{
  int i;
  for (i=0; i<argc; i++) {
    if (!strcmp(argv[i], "debug"))
      debug++;
    else if (!strcmp(argv[i], "noecho"))
      noecho = 1;
    else  /* PAM spec says unknown options must be ignored */
      syslog(LOG_AUTHPRIV|LOG_WARNING,
	     "pam_opie: unknown argument '%s'",
	     argv[i]);
  }
  return PAM_SUCCESS;
}

/* Entry point for a PAM request to validate a user. */
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,
				   int flags,
				   int argc,
				   const char **argv)
{
  int rc;
  rc = read_options(argc, argv);
  if (rc != PAM_SUCCESS)  {
    syslog(LOG_AUTHPRIV|LOG_ERR,
	   "pam_opie: fail pam read_options: %s",
	   pam_strerror(pamh, rc));
    return rc;
  }
  return _pam_auth_opie(pamh, flags, argc, argv);
}

/* Is there anything this can usefully do? */
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,
			      int flags,
			      int argc,
			      const char **argv)
{
  return PAM_SUCCESS;
}


PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,
				int flags,
				int argc,
				const char **argv)
{
  syslog(LOG_AUTHPRIV|LOG_ERR,
	 "pam_opie: unimplemented pam_sm_acct_mgmt");
  return PAM_SERVICE_ERR;
}

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,
				   int flags,
				   int argc,
				   const char **argv)
{
  syslog(LOG_AUTHPRIV|LOG_ERR,
	 "pam_opie: unimplemented pam_sm_open_session");
  return PAM_SERVICE_ERR;
}

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,
				   int flags,
				   int argc,
				   const char **argv)
{
  syslog(LOG_AUTHPRIV|LOG_ERR,
	 "pam_opie: unimplemented pam_sm_close_session");
  return PAM_SERVICE_ERR;
}

PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,
				int flags,
				int argc,
				const char **argv)
{
  syslog(LOG_AUTHPRIV|LOG_ERR,
	 "pam_opie: unimplemented pam_sm_chauthtok");
  return PAM_SERVICE_ERR;
}

char *xstrdup(const char *x)
{
  register char *new=NULL;

  if (x != NULL) {
    register int i;

    for (i=0; x[i]; ++i);  /* length of string */
    if ((new = malloc(++i)) == NULL) {
      i = 0;
    } else {
      while (i-- > 0) {
	new[i] = x[i];
      }
    }
    x = NULL;
  }
  return new;  /* return the duplicate or NULL on error */
}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index] []