Logo Search packages:      
Sourcecode: tacacs+ version File versions  Download package

pwlib.c

/*
 * $Id: pwlib.c,v 1.25 2009-03-17 18:40:20 heas Exp $
 *
 * Copyright (c) 1995-1998 by Cisco systems, Inc.
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that this
 * copyright and permission notice appear on all copies of the
 * software and supporting documentation, the name of Cisco Systems,
 * Inc. not be used in advertising or publicity pertaining to
 * distribution of the program without specific prior permission, and
 * notice be given in supporting documentation that modification,
 * copying and distribution is by permission of Cisco Systems, Inc.
 *
 * Cisco Systems, Inc. makes no representations about the suitability
 * of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
 * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "tac_plus.h"
#include "expire.h"

#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif

#ifdef SHADOW_PASSWORDS
# include <shadow.h>
#endif

#if HAVE_PAM
# ifdef __APPLE__ /* MacOS X */
#  include <pam/pam_appl.h>
# else
#  include <security/pam_appl.h>
# endif
static int pam_tacacs(int, const struct pam_message **, struct pam_response **,
                  void *);
#endif

/*
 * Generic password verification routines for des, file and cleartext passwords
 */
static int etc_passwd_file_verify(char *, char *, struct authen_data *);
static int des_verify(char *, char *);
#if HAVE_PAM
static int pam_verify(char *, char *);
#endif
static int passwd_file_verify(char *, char *, struct authen_data *, char *);

/* Adjust data->status depending on whether a user has expired or not */
void
set_expiration_status(char *exp_date, struct authen_data *data)
{
    int expired;

    /* if the status is anything except pass, there's no point proceeding */
    if (data->status != TAC_PLUS_AUTHEN_STATUS_PASS) {
      return;
    }

    /* Check the expiration date, if any. If NULL, this check will return
     * PW_OK */
    expired = check_expiration(exp_date);

    switch (expired) {
    case PW_OK:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Password has not expired %s",
               exp_date ? exp_date : "<no expiry date set>");

      data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
      return;

    case PW_EXPIRING:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Password will expire soon %s",
               exp_date ? exp_date : "<no expiry date set>");
      if (data->server_msg)
          free(data->server_msg);
      data->server_msg = tac_strdup("Password will expire soon");
      data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
      return;

    case PW_EXPIRED:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Password has expired %s",
               exp_date ? exp_date : "<no expiry date set>");
      if (data->server_msg)
          free(data->server_msg);
      data->server_msg = tac_strdup("Password has expired");
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return;

    default:
      report(LOG_ERR, "%s: Bogus return value %d from check_expiration",
             session.peer, expired);
      data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
      return;
    }

    /*NOTREACHED*/
    return;
}

/*
 * Verify that this user/password is valid.  Works only for cleartext, file,
 * PAM and des passwords.  Return 1 if password is valid.
 */
int
verify(char *name, char *passwd, struct authen_data *data, int recurse)
{
    char *exp_date;
    char *cfg_passwd;
    char *p;

    if (data->type == TAC_PLUS_AUTHEN_TYPE_PAP) {
      cfg_passwd = cfg_get_pap_secret(name, recurse);
    } else {
      cfg_passwd = cfg_get_login_secret(name, recurse);
    }

    /*
     * If there is no login or pap password for this user, see if there is
     * a global password that can be used.
     */
    if (!cfg_passwd) {
      cfg_passwd = cfg_get_global_secret(name, recurse);
    }

    /*
     * If we still have no password for this user (or no user for that
     * matter) but the default authentication = file <file> statement
     * has been issued, attempt to use this password file
     */
    if (!cfg_passwd) {
      char *file = cfg_get_authen_default();
      if (file) {
          return(passwd_file_verify(name, passwd, data, file));
      }

      /* otherwise, we fail */
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return(0);
    }

    /* We have a configured password. Deal with it depending on its type */
#if HAVE_PAM
    if (strcmp(cfg_passwd, "PAM") == 0) {
      /* try to verify the password via PAM */
      if (!pam_verify(name, passwd)) {
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;

      exp_date = cfg_get_expires(name, recurse);
      set_expiration_status(exp_date, data);
      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }
#endif

    p = tac_find_substring("cleartext ", cfg_passwd);
    if (p) {
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "verify daemon %s == NAS %s", p, passwd);

      if (strcmp(passwd, p)) {
          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_DEBUG, "Password is incorrect");
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else {
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;

          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_DEBUG, "Password is correct");
      }

      exp_date = cfg_get_expires(name, recurse);
      set_expiration_status(exp_date, data);
      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }

    p = tac_find_substring("des ", cfg_passwd);
    if (p) {
      /* try to verify this des password */
      if (!des_verify(passwd, p)) {
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else {
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
      }

      exp_date = cfg_get_expires(name, recurse);
      set_expiration_status(exp_date, data);
      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }

    p = tac_find_substring("file ", cfg_passwd);
    if (p) {
      return(passwd_file_verify(name, passwd, data, p));
    }

    /* Oops. No idea what kind of password this is. This should never
     * happen as the parser should never create such passwords.
     */
    report(LOG_ERR, "%s: Error cannot identify password type %s for %s",
         session.peer,
         cfg_passwd && cfg_passwd[0] ? cfg_passwd : "<NULL>",
         name ? name : "<unknown>");

    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
    return(0);
}

/*
 * Verify that this user/password is valid for the matching password data,
 * such as "cleartext foopwd".  Works only for cleartext, des and file
 * passwords and is used only for or by enable().
 * Return 1 if password is valid.  The caller needs to check any expiration
 * dates itself.
 */
int
verify_pwd(char *username, char *passwd, struct authen_data *data,
         char *cfg_passwd)
{
    char *p;

    /* Deal with the cfg_passwd depending on its type */
    p = tac_find_substring("cleartext ", cfg_passwd);
    if (p) {
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "verify daemon %s == NAS %s", p, passwd);

      if (strcmp(passwd, p)) {
          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_DEBUG, "Password is incorrect");
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else {
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;

          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_DEBUG, "Password is correct");
      }

      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }

    p = tac_find_substring("des ", cfg_passwd);
    if (p) {
      /* try to verify this des password */
      if (!des_verify(passwd, p)) {
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else {
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
      }

      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }

    p = tac_find_substring("file ", cfg_passwd);
    if (p) {
      if (!passwd_file_verify(username, passwd, data, p)) {
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      } else {
          data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
      }

      return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
    }

    /* Oops. No idea what kind of password this is. This should never
     * happen as the parser should never create such passwords.
     */
    report(LOG_ERR, "%s: Error cannot identify password type %s for %s",
         session.peer,
         cfg_passwd && cfg_passwd[0] ? cfg_passwd : "<NULL>",
         username ? username : "<unknown>");

    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
    return(0);
}

/* verify that this user/password is valid per /etc/passwd.  Return 0 if
 * invalid.
 */
static int
etc_passwd_file_verify(char *user, char *supplied_passwd,
                   struct authen_data *data)
{
    struct passwd *pw;
    char *exp_date;
    char *cfg_passwd;
#ifdef SHADOW_PASSWORDS
    char buf[12];
#endif /* SHADOW_PASSWORDS */

    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;

    setpwent();
    pw = getpwnam(user);
    endpwent();

    if (pw == NULL) {
      /* no entry exists */
      return(0);
    }

    if (*pw->pw_passwd == '\0' ||
      supplied_passwd == NULL ||
      *supplied_passwd == '\0') {
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return(0);
    }
    cfg_passwd = pw->pw_passwd;
    exp_date = pw->pw_shell;

#ifdef SHADOW_PASSWORDS
    if (STREQ(pw->pw_passwd, "x")) {
      struct spwd *spwd = getspnam(user);

      if (!spwd) {
          if (debug & DEBUG_PASSWD_FLAG) {
            report(LOG_DEBUG, "No entry for %s in shadow file", user);
          }
          data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
          return(0);
      }
      if (debug & DEBUG_PASSWD_FLAG) {
          report(LOG_DEBUG, "Found entry for %s in shadow file", user);
      }
      cfg_passwd = spwd->sp_pwdp;

      /*
       * Sigh. The Solaris shadow password file contains its own
       * expiry date as the number of days after the epoch
       * (January 1, 1970) when the password expires.
       * Convert this to ascii so that the traditional tacacs
       * password expiration routines work correctly.
       */

      if (spwd->sp_expire > 0) {
          long secs = spwd->sp_expire * 24 * 60 * 60;
          char *p = ctime(&secs);
          memcpy(buf, p + 4, 7);
          memcpy(buf + 7, p + 20, 4);
          buf[11] = '\0';
          exp_date = buf;
      }
    }
#endif /* SHADOW_PASSWORDS */

    /* try to verify the password */
    if (!des_verify(supplied_passwd, cfg_passwd)) {
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return(0);
    } else {
      data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
    }

    /* password ok. Check expiry field */
    set_expiration_status(exp_date, data);

    return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}

/*
 * verify that this user/password is valid per a passwd(5) style database.
 * Return 0 if invalid.
 */
static int
passwd_file_verify(char *user, char *supplied_passwd, struct authen_data *data,
               char *filename)
{
    struct passwd *pw;
    char *exp_date;
    char *cfg_passwd;

    data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;

    if (filename && STREQ(filename, "/etc/passwd")) {
      return(etc_passwd_file_verify(user, supplied_passwd, data));
    }

    /* an alternate filename */
    if (!(access(filename, R_OK) == 0)) {
      report(LOG_ERR, "%s %s: Cannot access %s for user %s -- %s",
             session.peer, session.port, filename, user, strerror(errno));
      return(0);
    }

    pw = tac_passwd_lookup(user, filename);

    if (pw == NULL)
      /* no entry exists */
      return(0);

    if (*pw->pw_passwd == '\0' ||
      supplied_passwd == NULL ||
      *supplied_passwd == '\0') {
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return(0);
    }
    cfg_passwd = pw->pw_passwd;
    exp_date = pw->pw_shell;

    /* try to verify the password */
    if (!des_verify(supplied_passwd, cfg_passwd)) {
      data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
      return(0);
    } else {
      data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
    }

    /* password ok. Check expiry field */
    set_expiration_status(exp_date, data);
    return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}

/*
 * verify a provided password against a des encrypted one.  return 1 if
 * verified, 0 otherwise.
 */
static int
des_verify(char *users_passwd, char *encrypted_passwd)
{
    char *ep;

    if (debug & DEBUG_PASSWD_FLAG)
      report(LOG_DEBUG, "verify %s %s", users_passwd, encrypted_passwd);

    if (users_passwd == NULL ||
      *users_passwd == '\0' ||
      encrypted_passwd == NULL ||
      *encrypted_passwd == '\0') {
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "verify returns 0");
      return(0);
    }

    ep = (char *) crypt(users_passwd, encrypted_passwd);

    if (debug & DEBUG_PASSWD_FLAG)
      report(LOG_DEBUG, "%s encrypts to %s", users_passwd, ep);

    if (strcmp(ep, encrypted_passwd) == 0) {
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Password is correct");
      return(1);
    }

    if (debug & DEBUG_PASSWD_FLAG)
      report(LOG_DEBUG, "Password is incorrect");

    return(0);
}

#if HAVE_PAM
static int
pam_tacacs(int nmsg, const struct pam_message **pmpp, struct pam_response
         **prpp, void *appdata_ptr)
{
    int i;
    struct authen_cont *acp;
    u_char *reply, *rp;

    if (debug & DEBUG_PASSWD_FLAG)
      report(LOG_DEBUG, "pam_tacacs received %d pam_messages", nmsg);

    if (nmsg <= 0 || nmsg > PAM_MAX_NUM_MSG)
      return(PAM_CONV_ERR);
    if ((*prpp = (struct pam_response *)
             tac_malloc(nmsg * sizeof(struct pam_response))) == NULL)
      return(PAM_BUF_ERR);
    memset((struct pam_repsonse *) *prpp, 0,
         nmsg * sizeof(struct pam_response));

    for (i = 0; i < nmsg; ++i) {
      switch (pmpp[i]->msg_style) {
      case PAM_PROMPT_ECHO_OFF:
          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_ERR, "%s %s: PAM_PROMPT_ECHO_OFF", session.peer,
                   session.port);

          send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETPASS,
                        (char *)pmpp[i]->msg,
                        pmpp[i]->msg ? strlen(pmpp[i]->msg) : 0,
                        NULL, 0, TAC_PLUS_AUTHEN_FLAG_NOECHO);
          reply = get_authen_continue();
          if (!reply) {
            /* Typically due to a premature connection close */
            report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
                   session.peer, session.port);
            goto fail;
          }
          acp = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);

          rp = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
          /*
           * A response to our GETDATA/GETPASS request. Create a
           * null-terminated string for authen_data.
           */
          prpp[i]->resp = (char *) tac_malloc(acp->user_msg_len + 1);
          memcpy(prpp[i]->resp, rp, acp->user_msg_len);
          prpp[i]->resp[acp->user_msg_len] = '\0';

          free(reply);
          break;
      case PAM_PROMPT_ECHO_ON:
          if (debug & DEBUG_PASSWD_FLAG)
            report(LOG_ERR, "%s %s: PAM_PROMPT_ECHO_ON", session.peer,
                   session.port);

          send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETDATA,
                        (char *)pmpp[i]->msg,
                        pmpp[i]->msg ? strlen(pmpp[i]->msg) : 0,
                        NULL, 0, 0);
          reply = get_authen_continue();
          if (!reply) {
            /* Typically due to a premature connection close */
            report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
                   session.peer, session.port);
            goto fail;
          }
          acp = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);

          rp = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
          /*
           * A response to our GETDATA/GETPASS request. Create a
           * null-terminated string for authen_data.
           */
          prpp[i]->resp = (char *) tac_malloc(acp->user_msg_len + 1);
          memcpy(prpp[i]->resp, rp, acp->user_msg_len);
          prpp[i]->resp[acp->user_msg_len] = '\0';

          free(reply);
          break;
      case PAM_ERROR_MSG:
          send_authen_error((char *)pmpp[i]->msg);
          break;
      case PAM_TEXT_INFO:
#ifdef PAM_MSG_NOCONF
      case PAM_MSG_NOCONF:
#endif
          /* so we should not receive these with PAM_SILENT set */
          break;
#ifdef PAM_CONV_INTERRUPT
      case PAM_CONV_INTERRUPT:
          return(PAM_SUCCESS);
#endif
      default:
          report(LOG_ERR, "%s %s: unknown pam_conv message type %d",
               session.peer, session.port, pmpp[i]->msg_style);
          goto fail;
      }
    }

    return(PAM_SUCCESS);
fail:
    for (i = 0; i < nmsg; ++i) {
      if ((*prpp)[i].resp != NULL) {
          memset((*prpp)[i].resp, 0, strlen((*prpp)[i].resp));
          free((*prpp)[i].resp);
      }
    }
    memset(*prpp, 0, nmsg * sizeof(struct pam_response));
    free(*prpp);
    *prpp = NULL;
    return(PAM_CONV_ERR);
}

/*
 * verify a provided user/password via PAM.
 * return 1 if verified, 0 otherwise.
 */
static int
pam_verify(char *user, char *passwd)
{
    int                 err;
    int                 pam_flag;
    struct pam_conv     conv = { pam_tacacs, NULL };
    pam_handle_t  *pamh = NULL;

    if (debug & DEBUG_PASSWD_FLAG)
      report(LOG_DEBUG, "pam_verify %s %s", user, passwd);

    if (user == NULL /* XXX || passwd == NULL || *passwd == '\0'*/) {
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "pam_verify returns 0");
      return(0);
    }

    if ((err = pam_start("tac_plus", user, &conv, &pamh)) != PAM_SUCCESS) {
      report(LOG_ERR, "pam_start failed: %s", pam_strerror(pamh, err));
      pam_end(pamh, err);
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "pam_verify returns 0");
      return(0);
    }

    /* don't ignore PAM messages if password debugging is on */
    pam_flag = (debug & DEBUG_PASSWD_FLAG) ? 0 : PAM_SILENT;

    switch ((err = pam_authenticate(pamh, pam_flag))) {
    case PAM_SUCCESS:
      pam_end(pamh, err);
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "pam_verify returns 1");
      return(1);
      break;
    case PAM_USER_UNKNOWN:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Unknown user");
      break;
    case PAM_AUTH_ERR:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "Password is incorrect");
      break;
    default:
      if (debug & DEBUG_PASSWD_FLAG)
          report(LOG_DEBUG, "pam_authenticate() returned unknown value %d",
               err);
      break;
    }

    pam_end(pamh, err);
    return(0);
}
#endif

Generated by  Doxygen 1.6.0   Back to index