Esempio n. 1
0
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
                 int argc, const char **argv)
{
        int ctrl;
        struct module_options options;

        memset(&options, 0, sizeof(options));
        options.retry_times = CO_RETRY_TIMES;

        ctrl = _pam_parse(pamh, &options, argc, argv);
        if (ctrl < 0)
                return PAM_BUF_ERR;

        if (flags & PAM_PRELIM_CHECK) {
                /* Check for passwd dictionary
                 * We cannot do that, since the original path is compiled
                 * into the cracklib library and we don't know it.
                 */
                return PAM_SUCCESS;
        } else if (flags & PAM_UPDATE_AUTHTOK) {
                int retval;
                const void *oldtoken;
                const char *user;
                int tries;

                retval = pam_get_user(pamh, &user, NULL);
                if (retval != PAM_SUCCESS || user == NULL) {
                        if (ctrl & PAM_DEBUG_ARG)
                                pam_syslog(pamh, LOG_ERR, "Can not get username");
                        return PAM_AUTHTOK_ERR;
                }

                retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &oldtoken);
                if (retval != PAM_SUCCESS) {
                        if (ctrl & PAM_DEBUG_ARG)
                                pam_syslog(pamh, LOG_ERR, "Can not get old passwd");
                        oldtoken = NULL;
                }

                tries = 0;
                while (tries < options.retry_times) {
                        void *auxerror;
                        const char *newtoken = NULL;

                        tries++;

                        /* Planned modus operandi:
                         * Get a passwd.
                         * Verify it against libpwquality.
                         * If okay get it a second time.
                         * Check to be the same with the first one.
                         * set PAM_AUTHTOK and return
                         */

                        retval = pam_get_authtok_noverify(pamh, &newtoken, NULL);
                        if (retval != PAM_SUCCESS) {
                                pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s",
                                        pam_strerror(pamh, retval));
                                continue;
                        } else if (newtoken == NULL) { /* user aborted password change, quit */
                                return PAM_AUTHTOK_ERR;
                        }

                        /* now test this passwd against libpwquality */
                        retval = pwquality_check(options.pwq, newtoken, oldtoken, user, &auxerror);

                        if (retval < 0) {
                                const char *msg;
                                char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
                                msg = pwquality_strerror(buf, sizeof(buf), retval, auxerror);
                                if (ctrl & PAM_DEBUG_ARG)
                                        pam_syslog(pamh, LOG_DEBUG, "bad password: %s", msg);
                                pam_error(pamh, _("BAD PASSWORD: %s"), msg);

                                if (getuid() || options.enforce_for_root ||
                                    (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) {
                                        pam_set_item(pamh, PAM_AUTHTOK, NULL);
                                        retval = PAM_AUTHTOK_ERR;
                                        continue;
                                }
                        } else {
                                if (ctrl & PAM_DEBUG_ARG)
                                        pam_syslog(pamh, LOG_DEBUG, "password score: %d", retval);
                        }

                        retval = pam_get_authtok_verify(pamh, &newtoken, NULL);
                        if (retval != PAM_SUCCESS) {
                                pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s",
                                pam_strerror(pamh, retval));
                                pam_set_item(pamh, PAM_AUTHTOK, NULL);
                                continue;
                        } else if (newtoken == NULL) {      /* user aborted password change, quit */
                                return PAM_AUTHTOK_ERR;
                        }

                        return PAM_SUCCESS;
                }

                pam_set_item (pamh, PAM_AUTHTOK, NULL);

                /* if we have only one try, we can use the real reason,
                 * else say that there were too many tries. */
                if (options.retry_times > 1)
                        return PAM_MAXTRIES;
                else
                        return retval;
        } else {
                if (ctrl & PAM_DEBUG_ARG)
                        pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
        }

        return PAM_SERVICE_ERR;
}
Esempio n. 2
0
/*-
 ***********************************************************************
 *
 * pam_sm_chauthtok
 *
 ***********************************************************************
 */
int
pam_sm_chauthtok(pam_handle_t *psPamHandle, int iPamFlags, int iArgumentCount, const char **ppcArgumentVector)
{
  char               *pcDbFile = PATHWELL_DEFAULT_DB_FILE;
  const char         *pcError = NULL;
  const char         *pcNewAuthToken = NULL;
  const char         *pcOldAuthToken = NULL;
  const char         *pcUser = NULL;
  const char         *pcValue = NULL;
  int                 iBlacklist = PATHWELL_FALSE;
  int                 iBlacklisted = PATHWELL_FALSE;
  int                 iDbRequired = PATHWELL_FALSE;
  int                 iDebug = PATHWELL_FALSE;
  int                 iEncoding = PATHWELL_DEFAULT_ENCODING;
  int                 iError = 0;
  int                 iIndex = 0;
  int                 iInUse = PATHWELL_FALSE;
  int                 iLeveled = PATHWELL_FALSE;
  int                 iMaxUse = 1; // Note: This implies that leveling is enabled by default.
  int                 iMinLen = 0;
  int                 iMinLev = 0;
  int                 iMode = PW_PAM_MODE_NOT_SET;
  int                 iReturnValue = PAM_SUCCESS;
  int                 iTokenSet = PATHWELL_DEFAULT_TOKEN_SET_ID;
  int                 iUseAuthToken = PATHWELL_FALSE;
  int                 iValueIndex = 0;
  int                 iValueLength = 0;
  PW_D_CONTEXT       *psPwDContext = NULL;
  PW_L_CONTEXT       *psPwLContext = NULL;
  PW_T_CONTEXT       *psNewPwTContext = NULL;
  PW_T_CONTEXT       *psOldPwTContext = NULL;
  unsigned int       *puiUseCount = NULL;

  /*-
   ***********************************************************************
   *
   * Process module arguments.
   *
   ***********************************************************************
   */
  for (iIndex = 0; iIndex < iArgumentCount; iIndex++)
  {
    if (strcasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_BLACKLIST) == 0)
    {
      iBlacklist = PATHWELL_TRUE;
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_DB, strlen(PW_PAM_OPT_DB)) == 0)
    {
      pcDbFile = (char *)&ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_DB)];
    }
    else if (strcasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_DEBUG) == 0)
    {
      iDebug = PATHWELL_TRUE;
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_ENCODING, strlen(PW_PAM_OPT_ENCODING)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_ENCODING)];
      if (strcasecmp(pcValue, "baseN+1") == 0 || strcasecmp(pcValue, "baseNp1") == 0)
      {
        iEncoding = PATHWELL_ENCODING_BASENP1;
      }
      else if (strcasecmp(pcValue, "bitmask") == 0)
      {
        iEncoding = PATHWELL_ENCODING_BITMASK;
      }
      else
      {
        pcError = "The \"encoding\" option must be one of \"baseN+1\" or \"bitmask\".";
        pam_syslog
        (
          psPamHandle,
          LOG_ERR,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_MAXUSE, strlen(PW_PAM_OPT_MAXUSE)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_MAXUSE)];
      iValueLength = strlen(pcValue);
      for (iValueIndex = 0; iValueIndex < iValueLength; iValueIndex++)
      {
        if (!isdigit((int)pcValue[iValueIndex]))
        {
          break;
        }
      }
      if (iValueLength < 1 || iValueIndex != iValueLength)
      {
        pcError = "The \"maxuse\" option must be an integer greater than or equal to zero.";
        pam_syslog
        (
          psPamHandle,
          LOG_ERR,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
      iMaxUse = atoi(pcValue);
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_MINLEN, strlen(PW_PAM_OPT_MINLEN)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_MINLEN)];
      iValueLength = strlen(pcValue);
      for (iValueIndex = 0; iValueIndex < iValueLength; iValueIndex++)
      {
        if (!isdigit((int)pcValue[iValueIndex]))
        {
          break;
        }
      }
      if (iValueLength < 1 || iValueIndex != iValueLength)
      {
        pcError = "The \"minlen\" option must be an integer greater than or equal to zero.";
        pam_syslog
        (
          psPamHandle,
          LOG_ERR,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
      iMinLen = atoi(pcValue);
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_MINLEV, strlen(PW_PAM_OPT_MINLEV)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_MINLEV)];
      iValueLength = strlen(pcValue);
      for (iValueIndex = 0; iValueIndex < iValueLength; iValueIndex++)
      {
        if (!isdigit((int)pcValue[iValueIndex]))
        {
          break;
        }
      }
      if (iValueLength < 1 || iValueIndex != iValueLength)
      {
        pcError = "The \"minlev\" option must be an integer greater than or equal to zero.";
        pam_syslog
        (
          psPamHandle,
          LOG_DEBUG,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
      iMinLev = atoi(pcValue);
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_MODE, strlen(PW_PAM_OPT_MODE)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_MODE)];
      if (strcasecmp(pcValue, "enforce") == 0)
      {
        iMode = PW_PAM_MODE_ENFORCE;
      }
      else if (strcasecmp(pcValue, "monitor") == 0)
      {
        iMode = PW_PAM_MODE_MONITOR;
      }
      else
      {
        pcError = "The \"mode\" option must be one of \"monitor\" or \"enforce\".";
        pam_syslog
        (
          psPamHandle,
          LOG_DEBUG,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
    }
    else if (strncasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_TOKENSET, strlen(PW_PAM_OPT_TOKENSET)) == 0)
    {
      pcValue = &ppcArgumentVector[iIndex][strlen(PW_PAM_OPT_TOKENSET)];
      if (strcasecmp(pcValue, "1") == 0)
      {
        iTokenSet = PATHWELL_TOKEN_SET_ID_1;
      }
      else if (strcasecmp(pcValue, "2") == 0)
      {
        iTokenSet = PATHWELL_TOKEN_SET_ID_2;
      }
      else if (strcasecmp(pcValue, "3") == 0)
      {
        iTokenSet = PATHWELL_TOKEN_SET_ID_3;
      }
      else if (strcasecmp(pcValue, "4") == 0)
      {
        iTokenSet = PATHWELL_TOKEN_SET_ID_4;
      }
      else
      {
        pcError = "The \"tokenset\" option must be an integer in the range [1-4].";
        pam_syslog
        (
          psPamHandle,
          LOG_DEBUG,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          pcError
        );
        return PAM_SERVICE_ERR;
      }
    }
    else if (strcasecmp(ppcArgumentVector[iIndex], PW_PAM_OPT_USE_AUTHTOK) == 0)
    {
      iUseAuthToken = PATHWELL_TRUE;
    }
    else
    {
      pcError = "One or more invalid or unsupported options were specified. Refer to the documentation for a list of supported options and the correct usage syntax.";
      pam_syslog
      (
        psPamHandle,
        LOG_DEBUG,
        "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
        PwVGetReleaseString(),
        PwVGetLibraryVersion(),
        PwVGetModuleVersion(),
        iPamFlags,
        (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
        (pcUser == NULL) ? "(null)" : pcUser,
        pcError
      );
      return PAM_SERVICE_ERR;
    }
  }

  /*-
   ***********************************************************************
   *
   * If debug mode is enabled, announce that we are alive.
   *
   ***********************************************************************
   */
  if (iDebug)
  {
    pam_syslog
    (
      psPamHandle,
      LOG_DEBUG,
      "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Status='Starting pam_sm_chauthtok';",
      PwVGetReleaseString(),
      PwVGetLibraryVersion(),
      PwVGetModuleVersion(),
      iPamFlags,
      (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
      (pcUser == NULL) ? "(null)" : pcUser
    );
  }

  /*-
   ***********************************************************************
   *
   * If the mode was not set, there's nothing to do, so bail out early.
   *
   ***********************************************************************
   */
  switch (iMode)
  {
  case PW_PAM_MODE_ENFORCE:
    iDbRequired = (iMaxUse > 0 || iBlacklist == PATHWELL_TRUE) ? PATHWELL_TRUE : PATHWELL_FALSE;
    break;
  case PW_PAM_MODE_MONITOR:
    iDbRequired = PATHWELL_TRUE;
    break;
  case PW_PAM_MODE_NOT_SET:
    return PAM_IGNORE;
    break;
  default:
    pcError = "Invalid mode. That should not happen.";
    pam_syslog
    (
      psPamHandle,
      LOG_DEBUG,
      "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
      PwVGetReleaseString(),
      PwVGetLibraryVersion(),
      PwVGetModuleVersion(),
      iPamFlags,
      (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
      (pcUser == NULL) ? "(null)" : pcUser,
      pcError
    );
    return PAM_SERVICE_ERR;
    break;
  }

  /*-
   ***********************************************************************
   *
   * Conditionally perform any preliminary module checks.
   *
   ***********************************************************************
   */
  if (iPamFlags & PAM_PRELIM_CHECK)
  {
    /*-
     *******************************************************************
     *
     * Conditionally make sure the database exists and that its schema
     * checks out. If it does not exist, create and initialize it, but
     * if that should fail, simply bail out.
     *
     *******************************************************************
     */
    if (iDbRequired == PATHWELL_TRUE)
    {
      if (PwDDbFileExists(pcDbFile))
      {
        psPwDContext = PwDNewContextFromParameters(pcDbFile, SQLITE_OPEN_READWRITE, NULL, 0);
        if (!PwDContextIsValid(psPwDContext))
        {
          pcError = PwDGetError(psPwDContext);
          iReturnValue = PAM_SERVICE_ERR;
          goto PRELIM_CLEANUP;
        }
        iError = PwDVerifySchema(psPwDContext);
        if (iError != ER_OK)
        {
          pcError = PwDGetError(psPwDContext);
          iReturnValue = PAM_SERVICE_ERR;
          goto PRELIM_CLEANUP;
        }
      }
      else
      {
        psPwDContext = PwDNewContextFromParameters(pcDbFile, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL, 0);
        if (!PwDContextIsValid(psPwDContext))
        {
          pcError = PwDGetError(psPwDContext);
          iReturnValue = PAM_SERVICE_ERR;
          goto PRELIM_CLEANUP;
        }
        iError = PwDInitializeDatabase(psPwDContext);
        if (iError != ER_OK)
        {
          pcError = PwDGetError(psPwDContext);
          iReturnValue = PAM_SERVICE_ERR;
          goto PRELIM_CLEANUP;
        }
      }
    }

PRELIM_CLEANUP:
    /*-
     *******************************************************************
     *
     * Clean up.
     *
     *******************************************************************
     */
    if (pcError != NULL)
    {
      pam_syslog
      (
        psPamHandle,
        LOG_DEBUG,
        "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
        PwVGetReleaseString(),
        PwVGetLibraryVersion(),
        PwVGetModuleVersion(),
        iPamFlags,
        (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
        (pcUser == NULL) ? "(null)" : pcUser,
        pcError
      );
    }
    PwDFreeContext(psPwDContext);
    return iReturnValue;
  }

  /*-
   ***********************************************************************
   *
   * Conditionally acquire, verify, and update the user's password.
   *
   ***********************************************************************
   */
  if (iPamFlags & PAM_UPDATE_AUTHTOK)
  {
    /*-
     *******************************************************************
     *
     * Acquire the user's name and old password. Note that while the
     * user's name should always be defined, the password may or may
     * not be defined.
     *
     *******************************************************************
     */
    iError = pam_get_item(psPamHandle, PAM_USER, (const void **)&pcUser);
    if (iError != PAM_SUCCESS || pcUser == NULL)
    {
      pcError = "Failed to obtain username via pam_get_item().";
      iReturnValue = (pcUser == NULL) ? PAM_USER_UNKNOWN : iError;
      goto UPDATE_CLEANUP;
    }
    iError = pam_get_item(psPamHandle, PAM_OLDAUTHTOK, (const void **)&pcOldAuthToken);
    if (iError != PAM_SUCCESS)
    {
      pcError = "Failed to obtain old password via pam_get_item().";
      iReturnValue = iError;
      goto UPDATE_CLEANUP;
    }
    if (pcOldAuthToken == NULL)
    {
//FIXME This is OK if the passwd is being set by the superuser (e.g., root runs 'passwd <user>'). Otherwise, it should be treated like an error.
    }

    /*-
     *******************************************************************
     *
     * Conditionally acquire/verify the user's new password. If it's
     * already defined, assume that it's also been verified. Note that
     * the new password must be defined if use_authtok option is set.
     *
     *******************************************************************
     */
    iError = pam_get_item(psPamHandle, PAM_AUTHTOK, (const void **)&pcNewAuthToken);
    if (iError != PAM_SUCCESS)
    {
      pcError = "Failed to obtain new password via pam_get_item().";
      iReturnValue = iError;
      goto UPDATE_CLEANUP;
    }
    if (pcNewAuthToken == NULL)
    {
      if (iUseAuthToken == PATHWELL_TRUE)
      {
        pcError = "The new password must be defined by a previously stacked module when the \"use_authtok\" option is enabled.";
        iReturnValue = PAM_AUTHTOK_RECOVERY_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = pam_get_authtok_noverify(psPamHandle, &pcNewAuthToken, NULL);
      if (iError != PAM_SUCCESS)
      {
        pcError = "Failed to obtain new password via pam_get_authtok_noverify().";
        iReturnValue = iError;
        goto UPDATE_CLEANUP;
      }
      if (pcNewAuthToken == NULL)
      {
        pcError = "The new password is not defined, so there is nothing that can be done.";
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = pam_get_authtok_verify(psPamHandle, &pcNewAuthToken, NULL);
      if (iError != PAM_SUCCESS)
      {
        pcError = "Failed to verify new password via pam_get_authtok_verify().";
        iReturnValue = iError;
        goto UPDATE_CLEANUP;
      }
      if (pcNewAuthToken == NULL)
      {
        pcError = "The new password is not defined, so there is nothing that can be done.";
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }

    /*-
     *******************************************************************
     *
     * Convert the old/new passwords to topologies and then to IDs.
     *
     *******************************************************************
     */
    if (pcOldAuthToken != NULL)
    {
      psOldPwTContext = PwTNewContextFromParameters(iEncoding, iTokenSet, (char *)pcOldAuthToken, NULL, NULL);
      if (!PwTContextIsValid(psOldPwTContext))
      {
        pcError = PwTGetError(psOldPwTContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = PwTPasswordToTopology(psOldPwTContext); // Note: This is considered an authentication token manipulation.
      if (iError != ER_OK)
      {
        pcError = PwTGetError(psOldPwTContext);
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = PwTTopologyToId(psOldPwTContext); // Note: This is considered an authentication token manipulation.
      if (iError != ER_OK)
      {
        pcError = PwTGetError(psOldPwTContext);
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }
    if (pcNewAuthToken != NULL)
    {
      psNewPwTContext = PwTNewContextFromParameters(iEncoding, iTokenSet, (char *)pcNewAuthToken, NULL, NULL);
      if (!PwTContextIsValid(psNewPwTContext))
      {
        pcError = PwTGetError(psNewPwTContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = PwTPasswordToTopology(psNewPwTContext); // Note: This is considered an authentication token manipulation.
      if (iError != ER_OK)
      {
        pcError = PwTGetError(psNewPwTContext);
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = PwTTopologyToId(psNewPwTContext); // Note: This is considered an authentication token manipulation.
      if (iError != ER_OK)
      {
        pcError = PwTGetError(psNewPwTContext);
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }

    /*-
     *******************************************************************
     *
     * Conditionally reject passwords that are too short.
     *
     *******************************************************************
     */
    if (iMode == PW_PAM_MODE_ENFORCE && iMinLen > 0 && psNewPwTContext != NULL)
    {
      if (strlen(PwTGetPassword(psNewPwTContext)) < iMinLen)
      {
        pcError = "The chosen password does not meet the minimum length requirement.";
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }

    /*-
     *******************************************************************
     *
     * Conditionally reject passwords having a blacklisted topology.
     *
     *******************************************************************
     */
    if (iMode == PW_PAM_MODE_ENFORCE && iBlacklist == PATHWELL_TRUE && psNewPwTContext != NULL)
    {
      psPwDContext = PwDNewContextFromParameters(pcDbFile, SQLITE_OPEN_READWRITE, NULL, 0);
      if (!PwDContextIsValid(psPwDContext))
      {
        pcError = PwDGetError(psPwDContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      iBlacklisted = PwDTopologyIsBlacklisted(psPwDContext, (char *)PwTGetTopology(psNewPwTContext));
      if (iBlacklisted == PATHWELL_INDETERMINATE)
      {
        pcError = PwDGetError(psPwDContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      if (iBlacklisted == PATHWELL_TRUE)
      {
        pcError = "The topology associated with the chosen password has been blacklisted.";
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }

    /*-
     *******************************************************************
     *
     * Conditionally reject passwords that are too similar.
     *
     *******************************************************************
     */
    if (iMode == PW_PAM_MODE_ENFORCE && iMinLev > 0 && psOldPwTContext != NULL && psNewPwTContext != NULL)
    {
      psPwLContext = PwLNewContextFromParameters(psOldPwTContext, psNewPwTContext);
      if (!PwLContextIsValid(psPwLContext))
      {
        pcError = PwLGetError(psPwLContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      iLeveled = PwLCheckLevDistance(psPwLContext, iMinLev);
      if (iLeveled == PATHWELL_INDETERMINATE)
      {
        pcError = PwLGetError(psPwLContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      if (iLeveled != PATHWELL_TRUE)
      {
        pcError = "The topology associated with the chosen password does not meet the minimum required Lev distance.";
        iReturnValue = PAM_AUTHTOK_ERR;
        goto UPDATE_CLEANUP;
      }
    }

    /*-
     *******************************************************************
     *
     * Conditionally reject passwords whose topology use count would
     * exceed the specified limit. Note that PwDGetUseCount() returns
     * NULL if the use count is not yet defined in the database, and
     * under those circumstances, a NULL by itself is not considered
     * an error. Any true error will be accompanied by a corresponding
     * error message, which can be retrieved via PwDGetError().
     *
     *******************************************************************
     */
    if (iMode == PW_PAM_MODE_ENFORCE && iMaxUse > 0 && psNewPwTContext != NULL)
    {
      psPwDContext = PwDNewContextFromParameters(pcDbFile, SQLITE_OPEN_READWRITE, NULL, 0);
      if (!PwDContextIsValid(psPwDContext))
      {
        pcError = PwDGetError(psPwDContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      puiUseCount = PwDGetUseCount(psPwDContext, psNewPwTContext);
      if (puiUseCount == NULL)
      {
        pcError = PwDGetError(psPwDContext);
        if (pcError != NULL)
        {
          iReturnValue = PAM_SERVICE_ERR;
          goto UPDATE_CLEANUP;
        }
        else
        {
          /* The use count has not yet been defined in the database, and that's OK. */
        }
      }
      else
      {
        if (*puiUseCount >= iMaxUse)
        {
          pcError = "The topology associated with the chosen password would exceed the maximum allowed use count.";
          iReturnValue = PAM_AUTHTOK_ERR;
          goto UPDATE_CLEANUP;
        }
      }
    }

    /*-
     *******************************************************************
     *
     * Conditionally increment the topology use count that corresponds
     * to the chosen password.
     *
     *******************************************************************
     */
    if (iMode == PW_PAM_MODE_MONITOR && psNewPwTContext != NULL)
    {
      psPwDContext = PwDNewContextFromParameters(pcDbFile, SQLITE_OPEN_READWRITE, NULL, 0);
      if (!PwDContextIsValid(psPwDContext))
      {
        pcError = PwDGetError(psPwDContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
      iError = PwDIncrementUseCount(psPwDContext, psNewPwTContext);
      if (iError != ER_OK)
      {
        pcError = PwDGetError(psPwDContext);
        iReturnValue = PAM_SERVICE_ERR;
        goto UPDATE_CLEANUP;
      }
    }

UPDATE_CLEANUP:
    /*-
     *******************************************************************
     *
     * Clean up.
     *
     *******************************************************************
     */
    if (pcError != NULL)
    {
      pam_syslog
      (
        psPamHandle,
        LOG_DEBUG,
        "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Error='%s';",
        PwVGetReleaseString(),
        PwVGetLibraryVersion(),
        PwVGetModuleVersion(),
        iPamFlags,
        (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
        (pcUser == NULL) ? "(null)" : pcUser,
        pcError
      );
      if (pcNewAuthToken != NULL)
      {
        pam_set_item(psPamHandle, PAM_AUTHTOK, NULL);
      }
    }
    else
    {
      if (iDebug)
      {
        pam_syslog
        (
          psPamHandle,
          LOG_DEBUG,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Status='Success'; Topology='%s'; Id='%" PRId64 "';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser,
          PwTGetTopology(psNewPwTContext),
          (unsigned long long) PwTGetId(psNewPwTContext)
        );
      }
      else
      {
        pam_syslog
        (
          psPamHandle,
          LOG_DEBUG,
          "Release='%s'; Library='%s'; Module='%s'; PamFlags='0x%08x'; Mode='%s'; User='******'; Status='Success';",
          PwVGetReleaseString(),
          PwVGetLibraryVersion(),
          PwVGetModuleVersion(),
          iPamFlags,
          (iMode == PW_PAM_MODE_ENFORCE) ? "enforce" : (iMode == PW_PAM_MODE_MONITOR) ? "monitor" : "",
          (pcUser == NULL) ? "(null)" : pcUser
        );
      }
    }
    PwDFreeContext(psPwDContext);
    PwLFreeContext(psPwLContext);
    PwTFreeContext(psNewPwTContext);
    PwTFreeContext(psOldPwTContext);
    if (puiUseCount != NULL)
    {
      free(puiUseCount);
    }
    return iReturnValue;
  }

  return PAM_IGNORE;
}