/*rexx */ /*********************************************************************/ /* Get the variables passed to us from ICHPWX01. Note that the order */ /* is important, and matches the order in which ICHPWX01 builds its */ /* parameter list for the AXREXX macro. */ /*********************************************************************/ parse ARG RexxRc,RexxReason,ExitCaller,CPPLaddr,CmdImage,newPwd, , pwdInterval,userID,workAddr,oldPwd,chgDate,ACEEaddr, , userName,instData,groupName,instAddr,pwdHist,pwdFormat, , mixedCase,outputMsg /*********************************************************************/ /*********************************************************************/ /* PROPRIETARY STATEMENT */ /* */ /* Licensed Materials - Property of IBM */ /* 5650-ZOS */ /* Copyright IBM Corp. 2008, 2021 */ /* */ /* Status = HRF77C0 */ /* */ /* END_OF_PROPRIETARY_STATEMENT */ /*-------------------------------------------------------------------*/ /* */ /* *01* EXTERNAL CLASSIFICATION: OTHER */ /* *01* END OF EXTERNAL CLASSIFICATION: */ /* */ /*-------------------------------------------------------------------*/ /* */ /* IRRPWREX: A sample REXX exec which works in concert with a sample */ /* new-password-exit ICHPWX01 to check the quality */ /* of a new password. */ /* */ irrpwrex_version = "V5" /* */ /* Function: */ /* -------- */ /* IRRPWREX gets control from ICHPWX01 using System REXX. It */ /* receives every parameter that ICHPWX01 itself receives from */ /* RACF, as well as a few others. */ /* */ /* Input arguments: */ /* --------------- */ /* See the RACF System Programmer's Guide for detail on the */ /* parameters passed to ICHPWX01. */ /* */ /* Not all parameters are meaningful for all functions (i.e. all */ /* values of ExitCaller). When a string value is not applicable, */ /* its length is passed in as 0. All address fields could have */ /* a value of 0. */ /* */ /* ExitCaller - Code for PASSWORD, ALTUSER, or RACINIT. */ /* CPPLaddr - Address of TSO Command Processor Parameter List. */ /* CmdImage - Command image from the CPPL, truncated at 512 */ /* characters. */ /* newPwd - The new value of the password. */ /* pwdInterval- The new value of the password interval. */ /* This will have a value of 0 if not specified. */ /* userID - The user ID whose password is being changed. */ /* workAddr - The address of the RACINIT exit work area. */ /* oldPwd - The current (old) value of the password. */ /* chgDate - The date of last password change in string form. */ /* This is in the format yyyyddd. If the password is */ /* expired, PASSWORD will set the value to '1900000', */ /* but RACINIT will set the value to '0000000'. */ /* ALTUSER does not pass in this argument. */ /* ACEEaddr - The address of the ACEE of the command issuer (for */ /* ALTUSER and PASWORD) or of the user being verified */ /* (RACINIT). For RACINIT, the ACEE is not initialized.*/ /* userName - The name of the user whose password is being */ /* changed. */ /* instData - The installation data of the user whose password */ /* is being changed. */ /* Note that this is the data which currently exists */ /* in the target user's ACEE or USER profile. If we */ /* are being called for an ALTUSER command */ /* which specifies the DATA operand in addition to */ /* PHRASE, and you wish to use the new value, you can */ /* parse it from the supplied command image in the */ /* CmdImage parameter. */ /* groupName - The connect group, if specified, from RACINIT only. */ /* instAddr - The address, if supplied on the INSTLN= keyword, */ /* from RACINIT only. */ /* pwdHist - The address of the password history structure */ /* pwdFormat - An indicator of the format of the current and new */ /* passwords (clear text, encrypted, passticket, or */ /* not applicable) */ /* mixedCase - SETROPTS MIXEDCASE indicator. If not zero, */ /* mixed case passwords are allowed. */ /* */ /* A note on input addresses: At the time of this writing, the */ /* REXX STORAGE function does not support ALETs, and so input */ /* addresses may not be very useful (unless you call an */ /* assembler routine which can do something with them. In this */ /* case, you may as well alter ICHPWX01). The addresses are */ /* being passed to IRRPWREX so that, should the STORAGE function */ /* become more useful in the future, the ICHPWX01 changes will */ /* be minimal. Meanwhile, if you want some specific piece of */ /* data located by the address, then you'll need to update the */ /* sample ICHPWX01 to pass in the data as a character argument. */ /* */ /* Output arguments: */ /* ---------------- */ /* RexxRc - The return code from this exec. It will be set as */ /* ICHPWX01's return code, so it must adhere to the */ /* specifications of the exit. Namely: */ /* */ /* 0 - New value is acceptable */ /* 4 - New value is not accepatble */ /* */ /* RexxReason - The reason code from this exec. This is not */ /* used by the ICHPWX01 sample. It is used by this */ /* exec to indicate which rule rejected a new */ /* password value. The reason code, and an */ /* accompanying message, will be displayed to the */ /* console when debug = 'on'. */ /* */ /* The messages are defined in the Error_text.n */ /* variables declared below. If desired, you could */ /* use these as the basis for the outputMsg output */ /* argument (described next). */ /* */ /* When RexxRc=4, RexxReason can have one of the */ /* following values: */ /* */ /* 1 - Minimum length violation */ /* 2 - Password contains disallowed characters */ /* 3 - Password does not contain at least one */ /* character from a specified number of */ /* character types (numbers, letters, special) */ /* 4 - Password contains part of user's name */ /* 5 - Password is only trivially different from */ /* previous value */ /* 6 - Password does not contain enough character */ /* differences, by position, from previous value */ /* 7 - Password contains a word from the restricted */ /* dictionary */ /* 8 - Password contains too many unchanged */ /* characters, by position, from previous value */ /* 9 - Password does not contain enough new */ /* characters from previous value */ /* 10 - Password does not contain all unique */ /* characters */ /* 11 - Password contains "consecutive" characters */ /* 12 - Password contains the user ID, or some */ /* subset of the user ID */ /* 13 - Password contains too many repeating */ /* characters */ /* 14 - Password starts with a string from the */ /* restricted prefix list */ /* 15 - Password uses a restricted pattern */ /* */ /* outputMsg - A message to be returned to ICHPWX01. See */ /* outputMsg below for additional considerations. */ /* */ /* Limitations and restrictions: */ /* ---------------------------- */ /* */ /* - System REXX requires that this exec live in the REXXLIB */ /* concatenation (Prior to REXXLIB support, the exec had to */ /* reside in SYS1.SAXREXEC). */ /* */ /* - ICHPWX01 is coded to call an exec named IRRPWREX, so the name */ /* cannot be changed without a corresponding change to ICHPWX01. */ /* */ /* - ICHPWX01 is coded to give this exec 5 seconds to complete. */ /* Otherwise, the password change will be rejected. ICHPWX01 can */ /* be modified to set a different value, if desired. */ /* */ /* Configuration variables: */ /* ----------------------- */ /* */ /* Following are some configurable values used in the checking */ /* performed by this exec. The default settings of these variables */ /* result in no functional difference in the way RACF checks */ /* the validity of a new password. */ /* */ /* The types of checks implemented herein should not be construed */ /* as enforcing "best practice" quality checks, but serve to */ /* demonstrate a number of quality checks which some customers */ /* may find useful. */ /* */ /*-------------------------------------------------------------------*/ /* Debug mode. If 'on', the input arguments and final return */ /* and reason code are dumped to the console using WTO. */ /* */ /* Note that System REXX provides additional functions from the */ /* AXREXX macro which could be useful for debugging. ICHPWX01 */ /* would need to be modified to exploit these. */ /* */ debug = 'off' /* */ /*-------------------------------------------------------------------*/ /* Message to be returned to ICHPWX01. When RexxRc = 4 (a */ /* quality check failed), and the outputMsg variable is not */ /* null, ICHPWX01 will issue the message to a foreground TSO */ /* user using the TPUT macro. */ /* */ /* The maximum length is 255 characters (or whatever value you */ /* have set the MAXMSGLEN constant to in ICHPWX01). Any message */ /* longer than this will cause the AXREXX macro, and thus */ /* ICHPWX01, to fail. That is, the password change will still */ /* be rejected, but it will be because of an internal AXREXX */ /* error, and the user will not see the intended message. */ /* */ /* You could, for example, change the variable to contain a */ /* description of your password quality rules. */ /* */ /* Another possibility is to state which rule was specifically */ /* violated. Consider modifying the Error_text.n values */ /* defined below, and uncommenting the code before the return */ /* statement that sets outputMsg to Error_text.RexxReason. */ /* */ /* You must be IPLed with version 4 of ICHPWX01 to use this */ /* feature (its eyecatcher in storage will contain the string */ /* "IRRPWREX V4"). */ /* - If ICHPWX01 is at a level less than V4, the returned */ /* message will simply be ignored. */ /* - If ICHPWX01 is at least at version V4 but IRRPWREX is not, */ /* or simply does not define the outputMsg variable, then */ /* ICHPWX01 will FAIL (AXREXX return code 8, reason code */ /* xxxx082C: An output argument was not set in the exec). */ /* !!!! So, never delete this default variable setting, even */ /* if you have no intention of exploiting the function! */ /* */ outputMsg = '' /* */ /*-------------------------------------------------------------------*/ /* STIG compliance. */ /* */ /* This check automatically enables the other checks that enforce */ /* compliance with the United States Defense Information Systems */ /* Agency's (DISA) Security Technical Implementation Guide (STIG) */ /* V7R1 with regard to RACF password quality rules, to the extent */ /* possible, taking some liberties on the content of the user ID */ /* and user name that are checked. */ /* */ /* Not all the subsequent checks are relevant to the STIG, and */ /* they may also be enabled as desired. A STIG-relevant check */ /* will be identified with an asterisk to the left of the first */ /* line of its description. */ /* */ /* Changing the value of STIG_Compliant to 'yes' will result in */ /* the relevant checks being enabled, regardless of any changes */ /* made to the explicit checks immediately below. */ /* */ STIG_Compliant = 'no' /* Enforce DISA STIG compliance */ /* */ /*-------------------------------------------------------------------*/ /* * Minimum length. This can be from 1 to 8. */ /* */ /* This check may be disabled by deleting or commenting out the */ /* following line. */ /* */ Pwd_minlen = 1 /* Minimum password length */ /* */ /*-------------------------------------------------------------------*/ /* Allowable characters. This is set to the characters allowed on */ /* a RACF password. */ /* */ /* Note: if you are synchronizing passwords across a heterogeneous */ /* environment, you probably want to restrict the allowable */ /* characters to the least common denominator accepted by all */ /* of the systems. This might entail disallowing some special */ /* characters, as some are variant across some code pages. */ /* */ /* Enable this check by uncommenting and modifying the following */ /* lines as appropriate. */ /* */ /* Note that lower case letters will be added to the allowable */ /* characters list if SETROPTS PASSWORD(MIXEDCASE) is in effect. */ /* This will happen further below in the code, when we've had a */ /* chance to inspect the mixedCase input parameter. */ /* */ numbers = '0123456789' Lower_letters = 'abcdefghijklmnopqrstuvwxyz' Upper_letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' special = '$@#.<+|&!*-%_>?:=' Pwd_allowed_chars = numbers||Upper_letters||special /* */ /*-------------------------------------------------------------------*/ /* * Minimum number of character types represented in the password. */ /* The types are defined above (numbers, upper and lower case */ /* letters, and special characters). For example, setting */ /* Pwd_req_types to 4 when mixed case passwords are in effect */ /* requires the user to specify at least one number, one lower */ /* case letter, one upper case letter, and one special */ /* character. Setting the value to 3 would require at least one */ /* character from 3 of the 4 character types. */ /* */ /* By default, only 1 category of character is required. */ /* */ /* Lower case letters will be ignored if mixed case passwords */ /* are not in effect. */ /* */ /* NOTE!: This capability is now completely supported in the */ /* SETROPTS password syntax rules with the MIXEDALL */ /* content-keyword, available with APAR OA43999. */ /* */ Pwd_req_types = 1 /* */ /*-------------------------------------------------------------------*/ /* * User name allowed or not. This checks if any word in the user */ /* name is contained anywhere in the new password. This check is */ /* case insensitive. */ /* */ /* The NAME operand of the ALTUSER command is used to */ /* specify the user name. Note that this exec receives the user */ /* name as it currently exists in the RACF database. If we are */ /* being called for an ALTUSER command in which the NAME */ /* operand has been specified in addition to PASSWORD, this */ /* new value is *not* what is passed to us in the userName */ /* parameter. If you wish to perform processing for this new */ /* value of NAME, you can add logic to parse it from the input */ /* command image, contained in the CmdImage variable. */ /* */ /* A minimum 'word' length is also defined, and defaults to 3. */ /* This is so that, for example, a name such as "BRUCE R WELLS" */ /* doesn't cause a rejection for any instance of "R" in the new */ /* password. */ /* */ /* As an alternative to word length, you can specify the length */ /* of a name substring to check. Regardless of word boundaries, */ /* this check will fail for any name substring of the specified */ /* length in the new password. */ /* */ /* This check may be enabled by setting the value to 'no'. */ /* */ Pwd_name_allowed = 'yes' Pwd_name_minlen = 3 Pwd_name_chars = 4 /* */ /*-------------------------------------------------------------------*/ /* Triviality check with respect to old password. This checks */ /* that the shorter of the old and new password is not simply a */ /* substring of the longer one. Both passwords are upper cased */ /* prior to the check. */ /* */ /* This check is only performed for PASSWORD and RACINIT, since */ /* ALTUSER does not provide the old password. */ /* */ /* This check may be enabled by setting the value to 'yes'. */ /* */ Pwd_triviality = 'no' /* */ /*-------------------------------------------------------------------*/ /* Minimum unique characters by position. This check prevents */ /* a new password which differs by only a few character positions */ /* from the old password. For example, changing the password from */ /* AFD4TRH */ /* to */ /* BFD3TRH */ /* */ /* An associated variable controls whether the passwords are first */ /* upper-cased before the check is performed. The check is */ /* performed for the length of the smaller string, so even if the */ /* new password is longer than the old, this rule could still fail */ /* the change if there aren't enough unique characters in the */ /* beginning part. */ /* */ /* This check is only performed for PASSWORD and RACINIT, since */ /* ALTUSER does not provide the old password. */ /* */ /* This check may be enabled by changing the setting to a non-zero */ /* value. */ /* */ Pwd_min_unique = 0 Pwd_min_unique_upper = 'yes' /* */ /*-------------------------------------------------------------------*/ /* * Maximum number of unchanged characters by position. This */ /* check is similar to Pwd_min_unique, but expresses the */ /* constraint in an inverse fashion. */ /* */ /* In addition, an associated variable relaxes the check by */ /* specifying that the unchanged positions should only result in */ /* a failure if they are consecutive, thus representing a */ /* positional substring of the old password. */ /* */ /* An associated variable controls whether the passwords are first */ /* upper-cased before the check is performed. */ /* */ /* This check is only performed for PASSWORD and RACINIT, since */ /* ALTUSER does not provide the old password. */ /* */ /* This check may be enabled by changing the setting to a lower */ /* value. */ /* */ Pwd_max_unchanged = 8 Pwd_max_unchanged_upper = 'yes' Pwd_max_unchanged_consecutive = 'yes' /* */ /*-------------------------------------------------------------------*/ /* * All unique characters. This check prevents any character from */ /* being used twice. */ /* */ /* This check may be enabled by changing the setting to 'yes'. */ /* */ Pwd_all_unique = 'no' /* */ /* */ /*-------------------------------------------------------------------*/ /* No consecutive characters. This check prevents substrings */ /* of consecutive characters, such as "ab", "CD", or "56". */ /* */ /* An associated variable controls whether the password is first */ /* upper-cased before the check is performed. This will result in */ /* rejection of substrings such as "aB" and "Xy". */ /* */ /* This check may be enabled by changing the setting to 'yes'. */ /* */ Pwd_no_consecutive = 'no' Pwd_no_consecutive_upper = 'yes' /* */ /*-------------------------------------------------------------------*/ /* Minimum number of new characters. This check requires a */ /* minimum number of characters in the new password that do not */ /* appear in the old password, regardless of position. This */ /* check is case sensitive. */ /* */ /* For example, if Pwd_min_new=4, and the old password is */ /* aBcD12#, then AbCd12# would be accepted, and BcD12#a would not. */ /* */ /* This check is only performed for PASSWORD and RACINIT, since */ /* ALTUSER does not provide the old password. */ /* */ /* This check may be enabled by changing the setting to a non-zero */ /* value. */ /* */ Pwd_min_new = 0 /* */ /*-------------------------------------------------------------------*/ /* * User ID allowed or not. This checks if the user ID is */ /* contained anywhere in the new password. This check is */ /* case insensitive. */ /* */ /* An associated variable allows you to specify the length */ /* of a user ID substring to check. this check will fail for */ /* any user ID substring of the specified length in the new */ /* password. */ /* */ /* This check may be enabled by setting the value to 'no'. */ /* */ Pwd_userID_allowed = 'yes' Pwd_userID_chars = 4 /* */ /*-------------------------------------------------------------------*/ /* * Repeated character check. This checks that only n sets of */ /* repeated characters are allowed. */ /* */ /* For example: If the value is 1, then happy123 is acceptable */ /* but happy1dd and happpy12 are not. If the value is 0, then */ /* no repeating characters are allowed. */ /* */ /* An associated variable controls whether the passwords are first */ /* upper-cased before the check is performed. By default, "aA" */ /* will be considered repeating characters, but this can be */ /* changed by setting Pwd_repeat_upper to 'no'. A setting of 'no' */ /* is only meaningful when SETROPTS PASSWORD(MIXEDCASE) is active. */ /* */ Pwd_repeat_chars = 7 Pwd_repeat_upper = 'yes' /* */ /*-------------------------------------------------------------------*/ /* Dictionary check. Defines a stem containing words which are not */ /* allowed in a new password. This check is case insensitive. */ /* */ /* Keep in mind that there is a time limit enforced by ICHPWX01 */ /* on the execution of this exec. */ /* */ /* This check may be enabled by setting the value of Pwd_dict.0 */ /* to the number of words in the stem. */ /* */ /* Words defined in this list will be upper-cased prior to the */ /* check. */ /* */ Pwd_dict.0 = 0 /* Change this as words are added and deleted */ Pwd_dict.1 = 'IBM' Pwd_dict.2 = 'RACF' Pwd_dict.3 = 'PASSWORD' Pwd_dict.4 = 'PHRASE' Pwd_dict.5 = 'SECRET' Pwd_dict.6 = 'IBMUSER' Pwd_dict.7 = 'SYS1' Pwd_dict.8 = '12345678' Pwd_dict.9 = '99999999' /* */ /*-------------------------------------------------------------------*/ /* * Prefix check. Defines a stem containing strings which are not */ /* allowed at the beginning of a new password. This check is */ /* case insensitive. */ /* */ /* Keep in mind that there is a time limit enforced by ICHPWX01 */ /* on the execution of this exec. */ /* */ /* This check may be enabled by setting the value of Pwd_prefix.0 */ /* to the number of strings in the stem. */ /* */ Pwd_prefix.0 = 0 /* Change this as values are added and deleted */ Pwd_prefix.1 = 'APPL' Pwd_prefix.2 = 'APR' Pwd_prefix.3 = 'AUG' Pwd_prefix.4 = 'ASDF' Pwd_prefix.5 = 'BASIC' Pwd_prefix.6 = 'CADAM' Pwd_prefix.7 = 'DEC' Pwd_prefix.8 = 'DEMO' Pwd_prefix.9 = 'FEB' Pwd_prefix.10 = 'FOCUS' Pwd_prefix.11 = 'GAME' Pwd_prefix.12 = 'IBM' Pwd_prefix.13 = 'JAN' Pwd_prefix.14 = 'JUL' Pwd_prefix.15 = 'JUN' Pwd_prefix.16 = 'LOG' Pwd_prefix.17 = 'MAR' Pwd_prefix.18 = 'MAY' Pwd_prefix.19 = 'NET' Pwd_prefix.20 = 'NEW' Pwd_prefix.21 = 'NOV' Pwd_prefix.22 = 'OCT' Pwd_prefix.23 = 'PASS' Pwd_prefix.24 = 'ROS' Pwd_prefix.25 = 'SEP' Pwd_prefix.26 = 'SIGN' Pwd_prefix.27 = 'SYS' Pwd_prefix.28 = 'TEST' Pwd_prefix.29 = 'TSO' Pwd_prefix.30 = 'VALID' Pwd_prefix.31 = 'VTAM' Pwd_prefix.32 = 'XXX' Pwd_prefix.33 = '1234' /* */ /*-------------------------------------------------------------------*/ /* Pattern check. This is sort of like the SETROPTS password */ /* rules in reverse, in that we specify patterns (or "topologies") */ /* that can *not* be used, as opposed to those that *can* be used. */ /* The default patterns specified below are the ones most */ /* frequently encountered by password crackers and penetration */ /* testers for 8-character passwords. As such, these patterns */ /* are the first ones tried in a brute-force attack. Of course, */ /* these defaults may not be in sync with other of your password */ /* policy settings. */ /* */ /* U = upper, L = lower, N = number, and S = special. */ /* */ /* Strings defined in this list will be upper-cased prior to the */ /* check. */ /* */ /* To enable this check, add/remove/alter patterns as desired and */ /* set Pwd_pattern.0 to the resulting number of restricted */ /* patterns. */ /* */ Pwd_pattern.0 = 0 Pwd_pattern.1 = 'ULLLLLLN' Pwd_pattern.2 = 'ULLLLLLS' Pwd_pattern.3 = 'ULLLLLNN' Pwd_pattern.4 = 'ULLLNNNN' Pwd_pattern.5 = 'ULLLLLNS' Pwd_pattern.6 = 'ULLNNNNS' Pwd_pattern.7 = 'ULLSNNNN' /*********************************************************************/ /* If STIG_Compliant = 'yes' then we now override the configuration */ /* variables above in order to enforce STIG checks. */ /*********************************************************************/ If STIG_Compliant = 'yes' Then Do Pwd_minlen = 8 /* Minimum length of 8 */ Pwd_req_types = 4 /* Upper, lower, digit, and special */ Pwd_max_unchanged = 3 /* Only 3 unchanged positions allowed */ Pwd_repeat_chars = 0 /* No repeating characters allowed */ Pwd_name_allowed = 'no' /* Fail on 4 consecutive characters of */ Pwd_name_chars = 4 /* the user's name. */ Pwd_name_minlen = 8 /* Word length is irrelevant. */ Pwd_all_unique = 'yes' /* Any character can only be used once */ Pwd_userID_allowed = 'no' /* Fail on 4 consecutive characters of */ Pwd_userID_chars = 4 /* the user ID. */ Pwd_prefix.0 = 33 /* Disallow the default prefix list */ End /* */ /*-------------------------------------------------------------------*/ /* These variables associate descriptive text with each value of */ /* RexxReason so that a more helpful message can be issued upon */ /* failure in debug mode. You might also choose to return these */ /* messages to ICHPWX01 in the outputMsg output argument. If so, */ /* modify the messages as desired, and uncomment the lines */ /* immediately before the return statement (search for */ /* "outputMsg"). */ /* */ Error_text.0 = 'No rules violated!' Error_text.1 = 'Minimum length violation' Error_text.2 = 'Disallowed character(s)' Error_text.3 = 'Not enough different character types' Error_text.4 = 'Contains part of user''s name' Error_text.5 = 'Only trivially different from previous' Error_text.6 = 'Not enough character differences from previous' Error_text.7 = 'Contains restricted word' Error_text.8 = 'Too many unchanged characters from previous' Error_text.9 = 'Not enough new characters from previous' Error_text.10 = 'Not all characters are unique' Error_text.11 = 'Contains consecutive characters' Error_text.12 = 'Contains part of the user ID' Error_text.13 = 'Too many repeating characters' Error_text.14 = 'Starts with a restricted prefix' Error_text.15 = 'Follows a restricted pattern' /* */ /* ----------------------------------------------------------------- */ /* */ /* */ /* This ought to get you started. Now go ahead and add checks */ /* which make sense for your particular installation. It sure */ /* beats programming in assembler! */ /* */ /*********************************************************************/ /*********************************************************************/ /* Exit processing starts here: */ /*********************************************************************/ /*********************************************************************/ /* If this is a LIST request, report on the active configuration */ /* variables. A LIST is accomplished with the following sysrexx */ /* operator command: */ /* F AXR,IRRPWREX LIST */ /* In this case, IRRPWREX receives a single argument ("LIST"), */ /* which arrives in the "RexxRc" variable, since that is the first */ /* argument that was originally defined for this exec when invoked */ /* by ICHPWX01. */ /*********************************************************************/ Upper RexxRc /* In case user enclosed mixed case string in quotes */ If Substr(RexxRc,1,4) = "LIST" Then do call Perform_list(RexxRc) return /* Exit the exec */ end /*********************************************************************/ /* Initialize the return and reason code to 0 */ /*********************************************************************/ RexxRc = 0 RexxReason = 0 /*********************************************************************/ /* Set the arithmetic precision to 10 digits (from the default of 9) */ /* so that D2X (used below) can convert larger hex addresses. */ /*********************************************************************/ NUMERIC DIGITS 10 /*********************************************************************/ /* Convert numeric ExitCaller parm into a string value for later use.*/ /*********************************************************************/ Select When ExitCaller = 1 Then CallerName = "RACINIT" When ExitCaller = 2 Then CallerName = "PASSWORD" When ExitCaller = 3 Then CallerName = "ALTUSER" Otherwise CallerName = "INVALID" End /*********************************************************************/ /* Convert numeric pwdFormat parm into a string value. */ /*********************************************************************/ Select When pwdFormat = 0 Then sFormat = "CLEARTEXT" When pwdFormat = 1 Then sFormat = "ENCRYPTED" When pwdFormat = 2 Then sFormat = "PASSTICKET" When pwdFormat = 256 Then sFormat = "NOT APPLICABLE" Otherwise sFormat = "INVALID" End /*********************************************************************/ /* Dump arguments if debug is on. */ /* */ /* Note: */ /* - The args are dumped in the order in which they are received. */ /* - The old/new password and the command image are not dumped, as */ /* they contain sensitive data which will be logged. Uncomment */ /* these lines only when testing on a test system. */ /* - AXRWTO has a limit of 126 characters, and will truncate */ /* strings if necessary. */ /*********************************************************************/ if debug = 'on' then do WtoRc = AXRWTO("In IRRPWREX called by ICHPWX01. Dumping args:") WtoRc = AXRWTO("Caller name is: " CallerName) /* WtoRc = AXRWTO("CPPL address: X'"D2X(CPPLaddr)"'") */ /*******************************************************************/ /* Remove leading, trailing, and repeated embedded spaces first, */ /* just to get as much meaningful text as possible. This isn't */ /* foolproof as it might compress meaningful spaces, e.g. in */ /* installation data. You can break the command image into smaller */ /* pieces if you really want to display it all. */ /*******************************************************************/ If Length(CmdImage) = 0 Then WtoRc = AXRWTO("Command image not provided.") Else do CmdImage = Space(CmdImage) WtoRc = AXRWTO("Command image length:" LENGTH(CmdImage)) /* Be very careful about uncommenting this! It contains a password. WtoRc = AXRWTO("Command image:" CmdImage) */ End /* Be very careful about uncommenting this! If Length(newPwd) = 0 Then WtoRc = AXRWTO("New password not provided.") Else WtoRc = AXRWTO("New password:" newPwd) */ If pwdInterval = 0 Then WtoRc = AXRWTO("Password interval not provided.") Else WtoRc = AXRWTO("Password interval: " pwdInterval) WtoRc = AXRWTO("User ID: " userID) /* WtoRc = AXRWTO("Work area address: X'"D2X(workAddr)"'") */ /* If Length(oldPwd) = 0 Then WtoRc = AXRWTO("Old password not provided.") Else WtoRc = AXRWTO("Old password:" oldPwd) */ If Length(chgDate) = 0 Then WtoRc = AXRWTO("Last-change date not provided.") Else WtoRc = AXRWTO("Last-change date:" chgDate) /* WtcRc = AXRWTO("ACEE address: X'"D2X(ACEEaddr)"'") */ If Length(userName) = 0 Then WtoRc = AXRWTO("User name not provided.") Else do userName = Strip(userName,trailing) WtoRc = AXRWTO("User name:" userName) End If Length(instData) = 0 Then WtoRc = AXRWTO("Installation data not provided.") Else do WtoRc = AXRWTO("Installation data length:" Length(instData)) WtoRc = AXRWTO("Installation data:" instData) End If Length(groupName) = 0 Then WtoRc = AXRWTO("Group name not provided.") Else WtoRc = AXRWTO("Group name:" groupName) /* WtoRc = AXRWTO("INSTLN= address: X'"D2X(instAddr)"'") */ /* WtoRc = AXRWTO("Password history address: X'"D2X(pwdHist)"'") */ WtoRc = AXRWTO("Password format is: " sFormat) WtoRc = AXRWTO("Mixed case password indicator: " mixedCase) /*******************************************************************/ /* If a given WTO doesn't work, the following lines can print out */ /* error information provided the variable WtoRc was used in the */ /* WTO. For example: */ /* WtoRc = AXRWTO("text") */ /* AXRDIAG is a built-in variable provided by System REXX. */ /*******************************************************************/ /* rc = AXRWTO("AXRWTO rc: " WtoRc) rc = AXRWTO("AXRDIAG: " AXRDIAG) */ end /* debug on */ /*********************************************************************/ /* If the new password was not specified, then just exit. This can */ /* happen when the PASSWORD command is used to change only the */ /* interval. */ /* */ /* You can feel free to add interval processing, but this sample */ /* is only concerned with password quality. */ /*********************************************************************/ If Length(newPwd) = 0 Then signal pwdexit /*********************************************************************/ /* Only RACINIT passes in a format indicator. If the format is */ /* "PASSTICKET", then the 'old password' is a PassTicket, and the */ /* new password is in clear text. In this case, set CallerName to */ /* a bogus value of 'RACPTKT'. This will have the effect of */ /* bypassing RACINIT-based checks that compare the old and new */ /* password values, which would be meaningless. Otherwise, if */ /* the format is not clear text, it is encrypted or unknown, and */ /* we simply exit. */ /* */ /* If the caller is not RACINIT, we assume the format is clear text. */ /*********************************************************************/ If CallerName = 'RACINIT' Then Do If sFormat = "PASSTICKET" Then Do CallerName = 'RACPTKT' End Else If sFormat /= 'CLEARTEXT' Then Do signal pwdexit End End /*********************************************************************/ /* Enforce minimum length */ /*********************************************************************/ If Pwd_minlen /= 'PWD_MINLEN' &, length(newPwd) < Pwd_minlen then do RexxReason = 1 signal pwdexit End /*********************************************************************/ /* In preparation for subsequent checks, we examine the mixedCase */ /* input parm and add the lower case letters to the list of */ /* allowable characters if mixed case passwords are active. */ /*********************************************************************/ If Pwd_allowed_chars /= 'PWD_ALLOWED_CHARS',/* If variable defined */ & mixedCase /= 0 Then /* And mixed case OK */ Pwd_allowed_chars = Pwd_allowed_chars||Lower_letters /*********************************************************************/ /* Enforce allowable characters. */ /*********************************************************************/ If Pwd_allowed_chars /= 'PWD_ALLOWED_CHARS',/* If variable defined */ & Length(Pwd_allowed_chars) > 0 Then /* And not null */ If verify(newPwd,Pwd_allowed_chars) > 0 then do RexxReason = 2 signal pwdexit End /*********************************************************************/ /* Enforce minimum character type check. If mixed case passwords */ /* are not allowed in RACF, then the minimum can be no greater */ /* than 3. */ /*********************************************************************/ If Pwd_req_types /= 'PWD_REQ_TYPES' Then /* If variable defined */ Do If mixedCase = 0 Then Pwd_req_types = min(Pwd_req_types,3) numtypes = 0 /* Number of types encountered */ If verify(newPwd,numbers,Match) /= 0 Then numtypes = numtypes + 1 If verify(newPwd,Upper_letters,Match) /= 0 Then numtypes = numtypes + 1 If verify(newPwd,special,Match) /= 0 Then numtypes = numtypes + 1 If mixedCase /= 0 &, verify(newPwd,Lower_letters,Match) /= 0 Then numtypes = numtypes + 1 If numtypes < Pwd_req_types Then do RexxReason = 3 signal pwdexit End End /*********************************************************************/ /* For subsequent checking, we will create an upper case copy of the */ /* passwords. */ /*********************************************************************/ UpperNewPwd = newPwd Upper UpperNewPwd If CallerName = 'PASSWORD' | CallerName = 'RACINIT' Then do UpperOldPwd = oldPwd Upper UpperOldPwd End /*********************************************************************/ /* Enforce user name restriction. The user name is not required in */ /* a RACF profile, and ICHPWX01 will have set a length value of 0 */ /* if there is no name. Note that the RACF user name is always in */ /* upper case. */ /*********************************************************************/ If Pwd_name_allowed = 'no' &, Length(userName) > 0 Then Do /*****************************************************************/ /* Strip off leading and trailing blanks from name. */ /*****************************************************************/ userName = Strip(userName) /*****************************************************************/ /* Check for name words. */ /*****************************************************************/ Do I = 1 to Words(userName) If Pos(Word(userName,I),UpperNewPwd) /= 0 &, Length(Word(userName,I)) >= Pwd_name_minlen Then do RexxReason = 4 End End /*****************************************************************/ /* Check for name substrings. Although the name can contain */ /* invalid password characters (e.g. space, apostrophe, etc), */ /* for the sake of code simplicity, we'll just let them get */ /* checked, since they can't cause a failure. */ /*****************************************************************/ If RexxReason = 0 &, /* OK so far */ Pwd_name_chars /= 'PWD_NAME_CHARS' &, /* Var defined */ Length(userName) >= Pwd_name_chars Then /* Name big enough*/ Do I = 1 to Length(userName)-Pwd_name_chars+1, Until RexxReason = 4 sub_string = Substr(userName,I,Pwd_name_chars) If Pos(sub_string,UpperNewPwd) /= 0 Then RexxReason = 4 End If RexxReason = 4 Then signal pwdexit End /*********************************************************************/ /* Reject trivial differences between new and old password. */ /*********************************************************************/ If Pwd_triviality = 'yes' &, (CallerName = 'PASSWORD' | CallerName = 'RACINIT') Then If UpperOldPwd = UpperNewPwd |, Pos(UpperNewPwd,UpperOldPwd) /= 0 |, Pos(UpperOldPwd,UpperNewPwd) /= 0 Then do RexxReason = 5 signal pwdexit End /*********************************************************************/ /* Enforce minimum unique characters by position. */ /*********************************************************************/ If Pwd_min_unique /= 'PWD_MIN_UNIQUE' &, /* Variable is defined */ Pwd_min_unique > 0 &, /* And greater than 0 */ (CallerName = 'PASSWORD' | CallerName = 'RACINIT') Then do If Pwd_min_unique_upper = 'yes' Then do old_string = UpperOldPwd new_string = UpperNewPwd End Else do old_string = oldPwd new_string = newPwd End MinDiff = 0 Do I = 1 to Min(Length(old_string),Length(new_string)) If Substr(old_string,I,1) /= Substr(new_string,I,1) Then MinDiff = MinDiff + 1 End If MinDiff < Pwd_min_unique Then do RexxReason = 6 signal pwdexit End End /*********************************************************************/ /* Enforce maximum unchanged characters by position. */ /*********************************************************************/ If Pwd_max_unchanged /= 'PWD_MAX_UNCHANGED' &, /* Variable defined */ Pwd_max_unchanged < 8 &, /* Check enabled */ (CallerName = 'PASSWORD' | CallerName = 'RACINIT') Then do If Pwd_max_unchanged_upper = 'yes' Then do old_string = UpperOldPwd new_string = UpperNewPwd End Else do old_string = oldPwd new_string = newPwd End /*******************************************************************/ /* Count the number of unchanged positions. As we do, construct */ /* a string containing an "s" in each unchanged (same) position. */ /* This will be helpful if the "consecutive" option is specified. */ /*******************************************************************/ same_chars = 0 match_string = '' Do I = 1 to Min(Length(old_string),Length(new_string)) If Substr(old_string,I,1) = Substr(new_string,I,1) Then Do match_string = match_string||'s' /* Position is the same */ same_chars = same_chars + 1 End Else match_string = match_string||'d' /* Position is different */ End /*******************************************************************/ /* If consecutiveness is *not* specified, then we check the */ /* number of unchanged positions against the specified maximum. */ /* If greater than the specified amount, the check fails. */ /*******************************************************************/ If Pwd_max_unchanged_consecutive /= 'yes' Then Do If same_chars > Pwd_max_unchanged Then RexxReason = 8 End /*******************************************************************/ /* If consecutiveness *is* specified, then look in the constructed */ /* string for a (sub)string of "s"s one greater than the maximum */ /* number of unchanged positions. */ /*******************************************************************/ Else Do /* Construct the test string */ test_string = '' Do i = 1 to Pwd_max_unchanged+1 test_string = test_string||"s" End If Pos(test_string,match_string) > 0 Then RexxReason = 8 End If RexxReason = 8 Then signal pwdexit End /*********************************************************************/ /* Enforce dictionary check. */ /*********************************************************************/ If Pwd_dict.0 /= 'PWD_DICT.0' &, /* Stem defined and */ Pwd_dict.0 > 0 Then /* Count field > zero */ Do I = 1 to Pwd_dict.0 UpperWord = Pwd_dict.I Upper UpperWord If Pos(UpperWord,UpperNewPwd) > 0 Then do RexxReason = 7 signal pwdexit End End /*********************************************************************/ /* Enforce restricted prefix check. */ /*********************************************************************/ If Pwd_prefix.0 /= 'PWD_PREFIX.0' &, /* Stem defined and */ Pwd_prefix.0 > 0 Then /* Count field > zero */ Do I = 1 to Pwd_prefix.0 UpperWord = Pwd_prefix.I Upper UpperWord If Pos(UpperWord,UpperNewPwd) = 1 Then do RexxReason = 14 signal pwdexit End End /*********************************************************************/ /* Enforce minimum new characters check. */ /*********************************************************************/ If Pwd_min_new /= 'PWD_MIN_NEW' &, /* Variable is defined */ Pwd_min_new > 0 &, /* And greater than 0 */ (CallerName = 'PASSWORD' | CallerName = 'RACINIT') Then do old_string = oldPwd new_string = newPwd MinDiff = 0 Do I = 1 to Length(new_string) If Index(old_string,Substr(new_string,I,1)) = 0 Then MinDiff = MinDiff + 1 End If MinDiff < Pwd_min_new Then do RexxReason = 9 signal pwdexit End End /*********************************************************************/ /* Check that all characters of the new password are unique. */ /*********************************************************************/ If Pwd_all_unique = 'yes' Then Do I = 1 to Length(newPwd)-1 Do J = I+1 to Length(newPwd) If Substr(newPwd,I,1) = Substr(newPwd,J,1) Then do RexxReason = 10 signal pwdexit End End End /*********************************************************************/ /* Check that no consecutive characters are specified. */ /* */ /* For each password character: */ /* - Set chk_char to the current password character */ /* - Find the string constant (defined above as the valid password */ /* characters) containing the character to be checked. Set */ /* chk_string to this string. */ /* - Set J to position of the character to be checked within */ /* chk_string. */ /* - If the password character is a symbol (national or special */ /* character), this check does not apply. */ /* - If chk_char is not the 1st character in chk_string (J=1), */ /* make sure the next password character does not equal the */ /* *previous* character in chk_string */ /* - If chk_char is not the last character in chk_string, */ /* make sure the next password character does not equal the */ /* *next* character in chk_string */ /*********************************************************************/ If Pwd_no_consecutive = 'yes' Then Do I = 1 to Length(newPwd)-1 If Pwd_no_consecutive_upper = 'yes' Then do chk_char = Substr(UpperNewPwd,I,1) /* Current password char */ CmpPwd = UpperNewPwd end Else do chk_char = Substr(newPwd,I,1) /* Current password char */ CmpPwd = newPwd end chk_string = numbers /* Is it a number? */ J = Pos(chk_char,chk_string) If J = 0 Then do chk_string = Upper_letters /* Is it an upper letter? */ J = Pos(chk_char,chk_string) If J = 0 Then do chk_string = Lower_letters /* Is it a lower letter? */ J = Pos(chk_char,chk_string) If J = 0 Then do chk_string = "" /* It's a symbol. N/A */ End End End If chk_string /= "" Then do /* If not a symbol */ /*****************************************/ /* If there is a previous char, AND it's */ /* equal to the next password char, */ /* the check fails. */ /*****************************************/ If J > 1 Then If Substr(CmpPwd,I+1,1) = Substr(chk_string,J-1,1) Then do RexxReason = 11 signal pwdexit End /*****************************************/ /* If there is a next char, AND it's */ /* equal to the next password char, */ /* the check fails. */ /*****************************************/ If J < Length(chk_string) Then If Substr(CmpPwd,I+1,1) = Substr(chk_string,J+1,1) Then do RexxReason = 11 signal pwdexit End End /* Not a symbol */ End /* Loop on password characters */ /*********************************************************************/ /* Check that the user ID is not part of the password. */ /*********************************************************************/ If Pwd_userID_allowed = 'no' Then Do /*****************************************************************/ /* If no subset is specified, then it is a simple matter of */ /* checking if the entire user ID is contained in the new pwd. */ /*****************************************************************/ If Pwd_userID_chars = 0 Then Do If Pos(userID,UpperNewPwd) /= 0 Then RexxReason = 12 End /*****************************************************************/ /* If a subset is specified, then we need to loop through the */ /* positions of the user ID checking for each possible */ /* substring of the specified length in the new password. If */ /* the user ID is shorter than the specified length, then */ /* don't check for it. */ /*****************************************************************/ Else If Pwd_userID_chars /= 'PWD_USERID_CHARS' &, /* Var defined */ Length(userID) >= Pwd_userID_chars Then Do I = 1 to Length(userID)-Pwd_userID_chars+1, Until RexxReason = 12 sub_string = Substr(userID,I,Pwd_userID_chars) If Pos(sub_string,UpperNewPwd) /= 0 Then RexxReason = 12 End If RexxReason = 12 Then signal pwdexit End /*********************************************************************/ /* Check password for repeated characters */ /*********************************************************************/ If Pwd_repeat_upper = 'yes' Then chk_string = UpperNewPwd Else chk_string = newPwd If Pwd_repeat_chars /= 'PWD_REPEAT_CHARS' &, Pwd_repeat_chars < 7 Then do rchrs = 0 Do I = 1 to Length(chk_string)-1 If substr(chk_string,I,1) = substr(chk_string,I+1,1) then rchrs = rchrs + 1 End If rchrs > Pwd_repeat_chars Then do RexxReason = 13 signal pwdexit End End /*********************************************************************/ /* Enforce restricted password patterns. */ /* */ /* Note: If you are interested in discerning the patterns used in */ /* your installation, you could in theory maintain a side-file of */ /* pattern strings and the number of times a given one has been */ /* used. You should protect this file. */ /*********************************************************************/ If Pwd_pattern.0 /= 'PWD_PATTERN.0' &, /* Stem defined and */ Pwd_pattern.0 > 0 Then /* Count field > zero */ Do pattern_string = '' /*****************************************************************/ /* Build the pattern string by assigning the appropriate */ /* "pattern character" for each position of the password. */ /*****************************************************************/ Do I = 1 to Length(newPwd) chk_letter = Substr(newPwd,I,1) Select When verify(chk_letter,Upper_letters,Match) /= 0 Then pattern_string = pattern_string || 'U' When verify(chk_letter,Lower_letters,Match) /= 0 Then pattern_string = pattern_string || 'L' When verify(chk_letter,numbers,Match) /= 0 Then pattern_string = pattern_string || 'N' Otherwise pattern_string = pattern_string || 'S' End /* Select */ End /* For each password character */ /*****************************************************************/ /* Now see if the constructed pattern string matches any of */ /* the restricted patterns. */ /*****************************************************************/ Do I = 1 to Pwd_pattern.0 UpperPattern = Pwd_pattern.I Upper UpperPattern If UpperPattern = pattern_string Then Do RexxReason = 15 signal pwdexit End End /* For each restricted pattern */ End /* Enforce pattern check */ /*********************************************************************/ /* Complete processing and return to ICHPWX01. */ /*********************************************************************/ pwdexit: If RexxReason /= 0 Then RexxRc = 4 /*********************************************************************/ /* If debug is on, display failure reason message. */ /*********************************************************************/ If debug = 'on' then do WtoRc = AXRWTO("RexxRc =" RexxRc) WtoRc = AXRWTO("RexxReason =" RexxReason": "Error_text.RexxReason) End /*********************************************************************/ /* If desired, you can use the same debug message (feel free to */ /* tailor them) as your returned message by uncommenting the */ /* statements below. */ /*********************************************************************/ /* If RexxRc = 4 Then outputMsg = Error_text.RexxReason */ return /*******************************************************************/ /* Perform_list: */ /* */ /* Use the AXRMLWTO function to display the values of the active */ /* configuration variables. This is a Sysrexx front-end to a */ /* multiline WTO. We issue it this way so that our output lines */ /* stay together on the console. */ /* */ /* Input: */ /* list_type - Type of listing to perform */ /* LISTVAR: List each configuration variable and its value, */ /* separated by a colon. This is appropriate for */ /* consumption by a program. */ /* LISTxxx: Anything else displays the output in English, */ /* for consumption by a human being. */ /* */ /* When new checks are added, this routine should be updated. */ /* */ /*******************************************************************/ Perform_list: arg list_type Select When Substr(list_type,1,5) = "LISTC" Then format = "c" /* List configuration variables/values */ Otherwise format = "e" /* List settings in English */ End /******************************************************************/ /* Issue a blank line just to get the output started on a clean */ /* line. */ /******************************************************************/ displayed_an_option = 'no' /* No rules displayed yet */ ConnectId = 'firstline' Out_line = ' ' rc = AXRMLWTO(Out_line,'ConnectId','d') /******************************************************************/ /* See if the ICHPWX01 exit is active. If not, then this */ /* part of the exit is not even being called, and the config */ /* variables don't matter in the slightest. Only issue a */ /* message line if the exit is not active, or its eyecatcher */ /* does not confirm intent to call IRRPWREX. */ /******************************************************************/ ichpwx01_version = "???" /* Initialize version to 'unknown' */ cvt_addr = c2d(Storage(10,4)) /* CVT address */ rcvt_addr = c2d(Storage(d2x(cvt_addr + 992),4)) /* RCVT address */ pwx01hex = Storage(D2x(rcvt_addr + 236),4) /* ICHPWX01 addr */ RCVTPWDX = C2d(BITAND(pwx01hex,'7FFFFFFF'x)) If RCVTPWDX = 0 Then Do Out_line = 'The ICHPWX01 exit is not active. IRRPWREX is not', 'being called!' rc = AXRMLWTO(Out_line,'ConnectId','d') End Else Do /* Look for 'IRRPWREX' eyecatcher in ICHPWX01 string */ icatcher = Storage(D2X(RCVTPWDX),48) If POS('IRRPWREX',icatcher) = 0 Then Do Out_line = 'Expected eyecatcher *not* detected in ICHPWX01' rc = AXRMLWTO(Out_line,'ConnectId','d') Out_line = ' Unable to confirm intent to call IRRPWREX' rc = AXRMLWTO(Out_line,'ConnectId','d') End Else /***************************************************************/ /* The eyecatcher does contain "IRRPWREX". Starting with V4, */ /* the eyecatcher is in a different format that also contains */ /* the version number in the blank-delimited word preceding */ /* "IRRPWREX". If we find a string there that starts with "V", */ /* report that as the version. Otherwise, the version */ /* remains unknown. */ /***************************************************************/ Do i = 1 to Words(icatcher) thisWord = Word(icatcher,i) If thisWord = "IRRPWREX" & i > 1 Then Do If Substr(Word(icatcher,i-1),1,1) = "V" Then Do ichpwx01_version = Word(icatcher,i-1) Leave End End End End /******************************************************************/ /* Display the IRRPWREX and ICHPWX01 version values. */ /******************************************************************/ Select When format = 'c' Then Do Out_line = 'ICHPWX01_VERSION:'ichpwx01_version rc = AXRMLWTO(Out_line,'ConnectId','d') Out_line = 'IRRPWREX_VERSION:'irrpwrex_version rc = AXRMLWTO(Out_line,'ConnectId','d') End Otherwise Out_line = 'ICHPWX01 version is' ichpwx01_version rc = AXRMLWTO(Out_line,'ConnectId','d') Out_line = 'IRRPWREX version is' irrpwrex_version rc = AXRMLWTO(Out_line,'ConnectId','d') End /******************************************************************/ /* Now issue a header line for IRRPWREX settings. */ /* We will then issue a message for each configuration variable */ /* that has been enabled (where we consider "enabled" to mean */ /* that it is set to a value that RACF does not already enforce */ /* natively). */ /******************************************************************/ Select When format = 'c' Then Out_line = 'The following IRRPWREX', 'configuration variables are defined:' Otherwise Out_line = 'The following IRRPWREX', 'password exit rules are in place:' End rc = AXRMLWTO(Out_line,'ConnectId','d') /*STIG_Compliant = 'no' */ If STIG_Compliant /= 'STIG_COMPLIANT' Then Do Select When format = 'c' Then Out_line = 'STIG_COMPLIANT:'||STIG_Compliant Otherwise If STIG_Compliant = 'yes' Then Out_line = ' STIG compliance is explicitly specified' Else Out_line = '' End If Out_line /= '' Then rc = AXRMLWTO(Out_line,'ConnectId','d') End /*Pwd_minlen = 1*/ If Pwd_minlen /= 'PWD_MINLEN' Then Do Select When format = 'c' Then Out_line = 'PWD_MINLEN:'||Pwd_minlen Otherwise If Pwd_minlen > 1 Then Do Out_line = ' The minimum password length is ' || Pwd_minlen displayed_an_option = 'yes' End Else Out_line = '' End If Out_line /= '' Then rc = AXRMLWTO(Out_line,'ConnectId','d') End /*Pwd_req_types = 1*/ If Pwd_req_types /= 'PWD_REQ_TYPES' Then Do Select When format = 'c' Then Out_line = 'PWD_REQ_TYPES:'||Pwd_req_types Otherwise If Pwd_req_types > 1 Then Do Out_line = ' The number of required character types is ' ||, Pwd_req_types displayed_an_option = 'yes' End Else Out_line = '' End If Out_line /= '' Then rc = AXRMLWTO(Out_line,'ConnectId','d') End /*Pwd_name_allowed = 'yes'*/ /*Pwd_name_minlen = 3 */ /*Pwd_name_chars = 4 */ If Pwd_name_allowed /= 'PWD_NAME_ALLOWED' Then Do Select When format = 'c' Then Do Out_line = 'PWD_NAME_ALLOWED:'||Pwd_name_allowed rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_name_minlen /= 'PWD_NAME_MINLEN' Then Do Out_line = ' PWD_NAME_MINLEN:'||Pwd_name_minlen rc = AXRMLWTO(Out_line,'ConnectId','d') End If Pwd_name_chars /= 'PWD_NAME_CHARS' Then Do Out_line = ' PWD_NAME_CHARS:'||Pwd_name_chars rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_name_allowed = 'no' Then Do Out_line = ' The user''s name cannot be contained', 'in the password' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_name_chars /= 'PWD_NAME_CHARS' Then Do Out_line = ' Only '||Pwd_name_chars-1||' consecutive', 'characters of the user''s name are allowed' rc = AXRMLWTO(Out_line,'ConnectId','d') End If Pwd_name_minlen /= 'PWD_NAME_MINLEN' Then Do Out_line = ' The minimum word length checked is ' ||, Pwd_name_minlen rc = AXRMLWTO(Out_line,'ConnectId','d') End End End End /*Pwd_userID_allowed = 'yes'*/ /*Pwd_userID_chars = 4 */ If Pwd_userID_allowed /= 'PWD_USERID_ALLOWED' Then Do Select When format = 'c' Then Do Out_line = 'PWD_USERID_ALLOWED:'||Pwd_userID_allowed rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_userID_chars /= 'PWD_USERID_CHARS' Then Do Out_line = ' PWD_USERID_CHARS:'||Pwd_userID_chars rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_userID_allowed = 'no' Then Do Out_line = ' The user ID cannot be contained in', 'the password' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_userID_chars /= 'PWD_USERID_CHARS' Then Do Out_line = ' Only '||Pwd_userID_chars-1||' consecutive', 'characters of the user ID are allowed' rc = AXRMLWTO(Out_line,'ConnectId','d') End End End End /*Pwd_triviality = 'no' */ If Pwd_triviality /= 'PWD_TRIVIALITY' Then Do Select When format = 'c' Then Out_line = 'PWD_TRIVIALITY:'||Pwd_triviality Otherwise If Pwd_triviality = 'yes' Then Do Out_line = ' Password must be non-trivially different', 'from the current value' displayed_an_option = 'yes' End Else Out_line = '' End If Out_line /= '' Then rc = AXRMLWTO(Out_line,'ConnectId','d') End /*Pwd_min_unique = 0 */ /*Pwd_min_unique_upper = 'yes' */ If Pwd_min_unique /= 'PWD_MIN_UNIQUE' Then Do Select When format = 'c' Then Do Out_line = 'PWD_MIN_UNIQUE:'||Pwd_min_unique rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_min_unique_upper /= 'PWD_MIN_UNIQUE_UPPER' Then Do Out_line = ' PWD_MIN_UNIQUE_UPPER:'||Pwd_min_unique_upper rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_min_unique > 0 Then do Out_line = ' At least '||Pwd_min_unique||' positions must be', 'unique from the current value' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_min_unique_upper = 'yes' Then Out_line = ' This check is not case sensitive' Else Out_line = ' This check is case sensitive' rc = AXRMLWTO(Out_line,'ConnectId','d') End End End /*Pwd_max_unchanged = 8 */ /*Pwd_max_unchanged_upper = 'yes' */ /*Pwd_max_unchanged_consecutive = 'yes' */ If Pwd_max_unchanged /= 'PWD_MAX_UNCHANGED' Then Do Select When format = 'c' Then Do Out_line = 'PWD_MAX_UNCHANGED:'||Pwd_max_unchanged rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_max_unchanged_upper /= 'PWD_MAX_UNCHANGED_UPPER' Then Do Out_line = ' PWD_MAX_UNCHANGED_UPPER:'||, Pwd_max_unchanged_upper rc = AXRMLWTO(Out_line,'ConnectId','d') End If Pwd_max_unchanged_consecutive /= , 'PWD_MAX_UNCHANGED_CONSECUTIVE' Then Do Out_line = ' PWD_MAX_UNCHANGED_CONSECUTIVE:'||, Pwd_max_unchanged_consecutive rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_max_unchanged < 8 Then Do Out_line = ' Only '||Pwd_max_unchanged||' unchanged'||, ' positions of the current password are allowed' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_max_unchanged_consecutive = 'yes' Then consec = 'need to' Else consec = 'need not' Out_line = ' These positions ' || consec ||, ' be consecutive to cause a failure' rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_max_unchanged_upper = 'yes' Then Out_line = ' This check is not case sensitive' Else Out_line = ' This check is case sensitive' rc = AXRMLWTO(Out_line,'ConnectId','d') End End End /*Pwd_all_unique = 'no' */ If Pwd_all_unique /= 'PWD_ALL_UNIQUE' Then Do Select When format = 'c' Then Do Out_line = 'PWD_ALL_UNIQUE:'||Pwd_all_unique rc = AXRMLWTO(Out_line,'ConnectId','d') End Otherwise If Pwd_all_unique = 'yes' Then Do Out_line = ' All characters in the new password must be unique' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' End End End /*Pwd_no_consecutive = 'no' */ /*Pwd_no_consecutive_upper = 'yes' */ If Pwd_no_consecutive /= 'PWD_NO_CONSECUTIVE' Then Do Select When format = 'c' Then Do Out_line = 'PWD_NO_CONSECUTIVE:'||Pwd_no_consecutive rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_no_consecutive_upper /=, 'PWD_NO_CONSECUTIVE_UPPER' Then Do Out_line = ' PWD_NO_CONSECUTIVE_UPPER:'||, Pwd_no_consecutive_upper rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_no_consecutive = 'yes' Then Do Out_line = ' No consecutive characters (e.g. AB or 12)', 'are allowed' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_no_consecutive_upper = 'yes' Then Out_line = ' This check is not case sensitive' Else Out_line = ' This check is case sensitive' rc = AXRMLWTO(Out_line,'ConnectId','d') End End End /*Pwd_min_new = 0 */ If Pwd_min_new /= 'PWD_MIN_NEW' Then Do Select When format = 'c' Then Do Out_line = 'PWD_MIN_NEW:'||Pwd_min_new rc = AXRMLWTO(Out_line,'ConnectId','d') End Otherwise If Pwd_min_new > 0 Then Do Out_line = ' The password must contain at least ' ||, Pwd_min_new||' characters not used in the old' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' End End End /*Pwd_repeat_chars = 7 */ /*Pwd_repeat_upper = 'yes' */ If Pwd_repeat_chars /= 'PWD_REPEAT_CHARS' Then Do Select When format = 'c' Then Do Out_line = 'PWD_REPEAT_CHARS:'||Pwd_repeat_chars rc = AXRMLWTO(Out_line,'ConnectId','d') If Pwd_repeat_upper /= 'PWD_REPEAT_UPPER' Then Do Out_line = ' PWD_REPEAT_UPPER:'||Pwd_repeat_upper rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If Pwd_repeat_chars < 7 Then do Out_line = ' No more than '||Pwd_repeat_chars||' pairs of', 'repeating characters are allowed' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' If Pwd_repeat_upper = 'yes' Then Out_line = ' This check is not case sensitive' Else Out_line = ' This check is case sensitive' rc = AXRMLWTO(Out_line,'ConnectId','d') End End End /*Pwd_dict.0 = 4 */ /*Pwd_prefix.0 = 3 */ /*Pwd_pattern.0 = 5 */ Do pass = 1 to 3 If pass = 1 Then Do stem = 'PWD_DICT.' stem_things = 'words' End Else If pass = 2 Then Do stem = 'PWD_PREFIX.' stem_things = 'prefix strings' End Else Do stem = 'PWD_PATTERN.' stem_things = 'restricted patterns' End num_words = Value(stem||'0') If Value(stem||'0') /= stem||'0' Then Do Select When format = 'c' Then Do Out_line = stem||'0:'||num_words rc = AXRMLWTO(Out_line,'ConnectId','d') Do i = 1 to num_words Out_Line = ' '||stem||i||':'||Value(stem||i) rc = AXRMLWTO(Out_line,'ConnectId','d') End End Otherwise If num_words > 0 Then Do Out_line = ' A list of '||num_words||' restricted '||, stem_things||' is being checked:' rc = AXRMLWTO(Out_line,'ConnectId','d') displayed_an_option = 'yes' ml = 71 /* Max line size is 71 characters */ lp = ' ' /* line prefix for indentation */ i = 1 /* Start with first word in stem */ Do Until i > num_words Out_line = lp /* Start a fresh line */ ll = Length(lp) /* Line length initialized to prefix len */ Do Until ll = ml/* Add words until the line is full */ If i > num_words Then /* If no more words, bail out */ Leave If ll + Length(Value(stem||i)) + 1 <= ml Then Do Out_Line = Out_line||' 'Value(stem||i) ll = ll + Length(Value(stem||i)) + 1 i = i + 1 /* Bump to next word */ End Else /* Next word won't fit */ ll = ml /* Terminate the line */ End /* We have a full line */ rc = AXRMLWTO(Out_line,'ConnectId','d') End /* We printed all the words */ End /* There was at least one word */ End /* End Select */ End /* There is a stem */ End /* All stems processed */ /******************************************************************/ /* If there were no enabled checks, issue a message stating this. */ /******************************************************************/ If format = 'e' & displayed_an_option = 'no' Then Do Out_line = ' There are no rules active that RACF does not already', 'enforce' rc = AXRMLWTO(Out_line,'ConnectId','d') End /******************************************************************/ /* Complete the multi-line WTO. */ /******************************************************************/ rc = AXRMLWTO(,'ConnectId','e') return