src/common/commonutils/PassUtils.c (654 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "Internal.h" static const char* g_etcPamdCommonPassword = "/etc/pam.d/common-password"; static const char* g_etcSecurityPwQualityConf = "/etc/security/pwquality.conf"; static const char* g_etcPamdSystemAuth = "/etc/pam.d/system-auth"; static const char* g_pamUnixSo = "pam_unix.so"; static const char* g_remember = "remember"; static char* FindPamModule(const char* pamModule, OsConfigLogHandle log) { const char* paths[] = { "/usr/lib/x86_64-linux-gnu/security/%s", "/usr/lib/security/%s", "/lib/security/%s", "/lib64/security/%s", "/lib/x86_64-linux-gnu/security/%s" }; int numPaths = ARRAY_SIZE(paths); char* result = NULL; int i = 0; if (NULL == pamModule) { OsConfigLogError(log, "FindPamModule: invalid argument"); return NULL; } for (i = 0; i < numPaths; i++) { if (NULL != (result = FormatAllocateString(paths[i], pamModule))) { if (0 == CheckFileExists(result, NULL, log)) { break; } else { FREE_MEMORY(result); } } else { OsConfigLogError(log, "FindPamModule: out of memory"); break; } } if (result) { OsConfigLogInfo(log, "FindPamModule: the PAM module '%s' is present on this system as '%s'", pamModule, result); } else { OsConfigLogInfo(log, "FindPamModule: the PAM module '%s' is not present on this system", pamModule); } return result; } int CheckEnsurePasswordReuseIsLimited(int remember, char** reason, OsConfigLogHandle log) { int status = ENOENT; char* pamModule = NULL; if (0 == CheckFileExists(g_etcPamdCommonPassword, NULL, log)) { // On Debian-based systems '/etc/pam.d/common-password' is expected to exist status = ((0 == CheckLineFoundNotCommentedOut(g_etcPamdCommonPassword, '#', g_remember, reason, log)) && (0 == CheckIntegerOptionFromFileLessOrEqualWith(g_etcPamdCommonPassword, g_remember, '=', remember, reason, log))) ? 0 : ENOENT; } else if (0 == CheckFileExists(g_etcPamdSystemAuth, NULL, log)) { // On Red Hat-based systems '/etc/pam.d/system-auth' is expected to exist status = ((0 == CheckLineFoundNotCommentedOut(g_etcPamdSystemAuth, '#', g_remember, reason, log)) && (0 == CheckIntegerOptionFromFileLessOrEqualWith(g_etcPamdSystemAuth, g_remember, '=', remember, reason, log))) ? 0 : ENOENT; } else { OsConfigCaptureReason(reason, "Neither '%s' or '%s' found, unable to check for '%s' option being set", g_etcPamdCommonPassword, g_etcPamdSystemAuth, g_remember); } if (status) { if (NULL == (pamModule = FindPamModule(g_pamUnixSo, log))) { OsConfigCaptureReason(reason, "The PAM module '%s' is not available. Automatic remediation is not possible", g_pamUnixSo); } FREE_MEMORY(pamModule); } return status; } static void EnsurePamModulePackagesAreInstalled(OsConfigLogHandle log) { const char* pamPackages[] = {"pam", "libpam-modules", "pam_pwquality", "libpam-pwquality", "libpam-cracklib"}; int numPamPackages = ARRAY_SIZE(pamPackages); int i = 0; for (i = 0; i < numPamPackages; i++) { InstallPackage(pamPackages[i], log); } } int SetEnsurePasswordReuseIsLimited(int remember, OsConfigLogHandle log) { // This configuration line is used in the PAM (Pluggable Authentication Module) configuration // to set the number of previous passwords to remember in order to prevent password reuse. // // Where: // // - 'password required': specifies that the password module is required for authentication // - 'pam_unix.so': the PAM module responsible for traditional Unix authentication // - 'sha512': indicates that the SHA-512 hashing algorithm shall be used to hash passwords // - 'shadow': specifies that the password information shall be stored in the /etc/shadow file // - 'remember=n': sets the number of previous passwords to remember to prevent password reuse // - 'retry=3': the number of times a user can retry entering their password before failing // // An alternative is: // // const char* endsHereIfSucceedsTemplate = "password sufficient pam_unix.so sha512 shadow %s=%d retry=3\n"; // // Where 'sufficient' says that if this module succeeds other modules are not invoked. // While 'required' says that if this module fails, authentication fails. const char* endsHereIfFailsTemplate = "password required %s sha512 shadow %s=%d retry=3\n"; char* pamModulePath = NULL; char* newline = NULL; int status = 0, _status = 0; EnsurePamModulePackagesAreInstalled(log); if (NULL == (pamModulePath = FindPamModule(g_pamUnixSo, log))) { OsConfigLogInfo(log, "SetEnsurePasswordReuseIsLimited: cannot proceed without %s being present", g_pamUnixSo); return ENOENT; } if (NULL != (newline = FormatAllocateString(endsHereIfFailsTemplate, pamModulePath, g_remember, remember))) { if (0 == CheckFileExists(g_etcPamdSystemAuth, NULL, log)) { status = ReplaceMarkedLinesInFile(g_etcPamdSystemAuth, g_remember, newline, '#', true, log); } if (0 == CheckFileExists(g_etcPamdCommonPassword, NULL, log)) { if ((0 != (_status = ReplaceMarkedLinesInFile(g_etcPamdCommonPassword, g_remember, newline, '#', true, log))) && (0 == status)) { status = _status; } } } else { OsConfigLogError(log, "SetEnsurePasswordReuseIsLimited: out of memory"); status = ENOMEM; } FREE_MEMORY(newline); FREE_MEMORY(pamModulePath); OsConfigLogInfo(log, "SetEnsurePasswordReuseIsLimited(%d) returning %d", remember, status); return status; } int CheckLockoutForFailedPasswordAttempts(const char* fileName, const char* pamSo, char commentCharacter, char** reason, OsConfigLogHandle log) { const char* auth = "auth"; const char* required = "required"; FILE* fileHandle = NULL; char* line = NULL; char* authValue = NULL; int deny = INT_ENOENT; int unlockTime = INT_ENOENT; long lineMax = sysconf(_SC_LINE_MAX); int status = ENOENT; if ((NULL == fileName) || (NULL == pamSo)) { OsConfigLogError(log, "CheckLockoutForFailedPasswordAttempts: invalid arguments"); return EINVAL; } else if (0 != CheckFileExists(fileName, reason, log)) { // CheckFileExists logs return ENOENT; } else if (NULL == (line = malloc(lineMax + 1))) { OsConfigLogError(log, "CheckLockoutForFailedPasswordAttempts: out of memory"); return ENOMEM; } else { memset(line, 0, lineMax + 1); } if (NULL == (fileHandle = fopen(fileName, "r"))) { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: cannot read from '%s' (errno: %d)", fileName, errno); status = EACCES; } else { status = ENOENT; while (NULL != fgets(line, lineMax + 1, fileHandle)) { // Example of valid lines: // // 'auth required pam_tally2.so onerr=fail audit silent deny=5 unlock_time=900' // 'auth required pam_faillock.so preauth silent audit deny=3 unlock_time=900' // 'auth required pam_tally.so onerr=fail deny=3 unlock_time=900' if ((commentCharacter == line[0]) || (EOL == line[0])) { status = 0; continue; } else if ((NULL != strstr(line, auth)) && (NULL != strstr(line, pamSo)) && (NULL != (authValue = GetStringOptionFromBuffer(line, auth, ' ', log))) && (0 == strcmp(authValue, required)) && (0 <= (deny = GetIntegerOptionFromBuffer(line, "deny", '=', log))) && (deny <= 5) && (0 < (unlockTime = GetIntegerOptionFromBuffer(line, "unlock_time", '=', log)))) { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: '%s %s %s' found uncommented with 'deny' set to %d and 'unlock_time' set to %d in '%s'", auth, required, pamSo, deny, unlockTime, fileName); OsConfigCaptureSuccessReason(reason, "'%s %s %s' found uncommented with 'deny' set to %d and 'unlock_time' set to %d in '%s'", auth, required, pamSo, deny, unlockTime, fileName); status = 0; FREE_MEMORY(authValue); break; } else { FREE_MEMORY(authValue); status = ENOENT; } memset(line, 0, lineMax + 1); } if (status) { if (INT_ENOENT == deny) { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: 'deny' not found in '%s' for '%s'", fileName, pamSo); OsConfigCaptureReason(reason, "'deny' not found in '%s' for '%s'", fileName, pamSo); } else { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: 'deny' found set to %d in '%s' for '%s' instead of a value between 0 and 5", deny, fileName, pamSo); OsConfigCaptureReason(reason, "'deny' found set to %d in '%s' for '%s' instead of a value between 0 and 5", deny, fileName, pamSo); } if (INT_ENOENT == unlockTime) { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: 'unlock_time' not found in '%s' for '%s'", fileName, pamSo); OsConfigCaptureReason(reason, "'unlock_time' not found in '%s' for '%s'", fileName, pamSo); } else { OsConfigLogInfo(log, "CheckLockoutForFailedPasswordAttempts: 'unlock_time' found set to %d in '%s' for '%s' instead of a positive value", unlockTime, fileName, pamSo); OsConfigCaptureReason(reason, "'unlock_time' found set to %d in '%s' for '%s' instead of a positive value", unlockTime, fileName, pamSo); } } fclose(fileHandle); } FREE_MEMORY(line); return status; } int SetLockoutForFailedPasswordAttempts(OsConfigLogHandle log) { // These configuration lines are used in the PAM (Pluggable Authentication Module) settings to count // number of attempted accesses and lock user accounts after a specified number of failed login attempts. // // For etc/pam.d/login, /etc/pam.d/system-auth and /etc/pam.d/password-auth when pam_faillock.so exists: // // 'auth required pam_faillock.so preauth silent audit deny=3 unlock_time=900 even_deny_root' // // For etc/pam.d/login, /etc/pam.d/system-auth and /etc/pam.d/password-auth when pam_faillock.so does not exist and pam_tally2.so exists: // // 'auth required pam_tally2.so file=/var/log/tallylog onerr=fail audit silent deny=5 unlock_time=900 even_deny_root' // // Otherwise, if pam_tally.so and pam_deny.so exist: // // 'auth required pam_tally.so onerr=fail deny=3 unlock_time=900\nauth required pam_deny.so\n' // // Where: // // - 'auth': specifies that the module is invoked during authentication // - 'required': the module is essential for authentication to proceed // - 'file=/var/log/tallylog': the default log file used to keep login counts // - 'onerr=fail': if an error occurs (e.g., unable to open a file), return with a PAM error code // - 'audit': generate an audit record for this event // - 'silent': do not display any error messages // - 'deny=5': deny access if the tally (failed login attempts) for this user exceeds 5 times // - 'unlock_time=900': allow access after 900 seconds (15 minutes) following a failed attempt const char* pamFailLockLineTemplate = "auth required %s preauth silent audit deny=3 unlock_time=900 even_deny_root\n"; const char* pamTally2LineTemplate = "auth required %s file=/var/log/tallylog onerr=fail audit silent deny=5 unlock_time=900 even_deny_root\n"; const char* pamTallyDenyLineTemplate = "auth required %s onerr=fail deny=3 unlock_time=900\nauth required %s\n"; const char* pamFaillockSo = "pam_faillock.so"; const char* pamTally2So = "pam_tally2.so"; const char* pamTallySo = "pam_tally.so"; const char* pamDenySo = "pam_deny.so"; const char* pamConfigurations[] = {"/etc/pam.d/login", "/etc/pam.d/system-auth", "/etc/pam.d/password-auth", "/etc/pam.d/common-auth"}; int numPamConfigurations = ARRAY_SIZE(pamConfigurations); char* pamModulePath = NULL; char* pamModulePath2 = NULL; char* line = NULL; int i = 0, status = 0, _status = 0; EnsurePamModulePackagesAreInstalled(log); for (i = 0; i < numPamConfigurations; i++) { if (0 == CheckFileExists(pamConfigurations[i], NULL, log)) { if (NULL != (pamModulePath = FindPamModule(pamFaillockSo, log))) { if (NULL != (line = FormatAllocateString(pamFailLockLineTemplate, pamModulePath))) { _status = ReplaceMarkedLinesInFile(pamConfigurations[i], pamFaillockSo, line, '#', true, log); FREE_MEMORY(line); } else { _status = ENOMEM; } FREE_MEMORY(pamModulePath); } else if (NULL != (pamModulePath = FindPamModule(pamTally2So, log))) { if (NULL != (line = FormatAllocateString(pamTally2LineTemplate, pamModulePath))) { _status = ReplaceMarkedLinesInFile(pamConfigurations[i], pamTally2So, line, '#', true, log); FREE_MEMORY(line); } else { _status = ENOMEM; } FREE_MEMORY(pamModulePath); } else if ((NULL != (pamModulePath = FindPamModule(pamTallySo, log))) && (NULL != (pamModulePath2 = FindPamModule(pamDenySo, log)))) { if (NULL != (line = FormatAllocateString(pamTallyDenyLineTemplate, pamModulePath, pamModulePath2))) { _status = ReplaceMarkedLinesInFile(pamConfigurations[i], pamTallySo, line, '#', true, log); FREE_MEMORY(line); } else { _status = ENOMEM; } FREE_MEMORY(pamModulePath); FREE_MEMORY(pamModulePath2); } if (_status && (_status != status)) { status = _status; } if (ENOMEM == status) { OsConfigLogError(log, "SetLockoutForFailedPasswordAttempts: out of memory"); break; } } } return status; } static int CheckRequirementsForCommonPassword(int retry, int minlen, int dcredit, int ucredit, int ocredit, int lcredit, char** reason, OsConfigLogHandle log) { const char* pamPwQualitySo = "pam_pwquality.so"; const char* pamCrackLibSo = "pam_cracklib.so"; const char* password = "password"; const char* requisite = "requisite"; int retryOption = 0; int minlenOption = 0; int dcreditOption = 0; int ucreditOption = 0; int ocreditOption = 0; int lcreditOption = 0; char commentCharacter = '#'; FILE* fileHandle = NULL; char* line = NULL; long lineMax = sysconf(_SC_LINE_MAX); bool found = false; int status = ENOENT; if (false == FileExists(g_etcPamdCommonPassword)) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: '%s' does not exist", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "%s' does not exist", g_etcPamdCommonPassword); return ENOENT; } else if (NULL == (line = malloc(lineMax + 1))) { OsConfigLogError(log, "CheckRequirementsForCommonPassword: out of memory"); return ENOMEM; } else { memset(line, 0, lineMax + 1); } if (NULL == (fileHandle = fopen(g_etcPamdCommonPassword, "r"))) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: cannot read from '%s'", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "Cannot read from '%s'", g_etcPamdCommonPassword); status = EACCES; } else { status = ENOENT; while (NULL != fgets(line, lineMax + 1, fileHandle)) { // Example of valid line: // 'password requisite pam_pwquality.so retry=3 minlen=14 lcredit=-1 ucredit=1 ocredit=-1 dcredit=-1' if ((commentCharacter == line[0]) || (EOL == line[0])) { status = 0; continue; } else if ((NULL != strstr(line, password)) && (NULL != strstr(line, requisite)) && ((NULL != strstr(line, pamPwQualitySo)) || (NULL != strstr(line, pamCrackLibSo)) || (NULL != strstr(line, g_pamUnixSo)))) { found = true; if ((retry == (retryOption = GetIntegerOptionFromBuffer(line, "retry", '=', log))) && (minlen == (minlenOption = GetIntegerOptionFromBuffer(line, "minlen", '=', log))) && (dcredit == (dcreditOption = GetIntegerOptionFromBuffer(line, "dcredit", '=', log))) && (ucredit == (ucreditOption = GetIntegerOptionFromBuffer(line, "ucredit", '=', log))) && (ocredit == (ocreditOption = GetIntegerOptionFromBuffer(line, "ocredit", '=', log))) && (lcredit == (lcreditOption = GetIntegerOptionFromBuffer(line, "lcredit", '=', log)))) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: '%s' contains uncommented '%s %s' with " "the expected password creation requirements (retry: %d, minlen: %d, dcredit: %d, ucredit: %d, ocredit: %d, lcredit: %d)", g_etcPamdCommonPassword, password, requisite, retryOption, minlenOption, dcreditOption, ucreditOption, ocreditOption, lcreditOption); OsConfigCaptureSuccessReason(reason, "'%s' contains uncommented '%s %s' with the expected password creation requirements " "(retry: %d, minlen: %d, dcredit: %d, ucredit: %d, ocredit: %d, lcredit: %d)", g_etcPamdCommonPassword, password, requisite, retryOption, minlenOption, dcreditOption, ucreditOption, ocreditOption, lcreditOption); status = 0; break; } else { if (INT_ENOENT == retryOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'retry' is missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'retry' is missing", g_etcPamdCommonPassword); } else if (retryOption != retry) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'retry' is set to %d instead of %d", g_etcPamdCommonPassword, retryOption, retry); OsConfigCaptureReason(reason, "In '%s' 'retry' is set to %d instead of %d", g_etcPamdCommonPassword, retryOption, retry); } if (INT_ENOENT == minlenOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'minlen' is missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'minlen' is missing", g_etcPamdCommonPassword); } else if (minlenOption != minlen) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'minlen' is set to %d instead of %d", g_etcPamdCommonPassword, minlenOption, minlen); OsConfigCaptureReason(reason, "In '%s' 'minlen' is set to %d instead of %d", g_etcPamdCommonPassword, minlenOption, minlen); } if (INT_ENOENT == dcreditOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'dcredit' is missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'dcredit' is missing", g_etcPamdCommonPassword); } else if (dcreditOption != dcredit) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'dcredit' is set to '%d' instead of %d", g_etcPamdCommonPassword, dcreditOption, dcredit); OsConfigCaptureReason(reason, "In '%s' 'dcredit' is set to '%d' instead of %d", g_etcPamdCommonPassword, dcreditOption, dcredit); } if (INT_ENOENT == ucreditOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'ucredit' missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'ucredit' missing", g_etcPamdCommonPassword); } else if (ucreditOption != ucredit) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'ucredit' set to '%d' instead of %d", g_etcPamdCommonPassword, ucreditOption, ucredit); OsConfigCaptureReason(reason, "In '%s' 'ucredit' set to '%d' instead of %d", g_etcPamdCommonPassword, ucreditOption, ucredit); } if (INT_ENOENT == ocreditOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'ocredit' missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'ocredit' missing", g_etcPamdCommonPassword); } else if (ocreditOption != ocredit) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'ocredit' set to '%d' instead of %d", g_etcPamdCommonPassword, ocreditOption, ocredit); OsConfigCaptureReason(reason, "In '%s' 'ocredit' set to '%d' instead of %d", g_etcPamdCommonPassword, ocreditOption, ocredit); } if (INT_ENOENT == lcreditOption) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'lcredit' missing", g_etcPamdCommonPassword); OsConfigCaptureReason(reason, "In '%s' 'lcredit' missing", g_etcPamdCommonPassword); } else if (lcreditOption != lcredit) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: in '%s' 'lcredit' set to '%d' instead of %d", g_etcPamdCommonPassword, lcreditOption, lcredit); OsConfigCaptureReason(reason, "In '%s' 'lcredit' set to '%d' instead of %d", g_etcPamdCommonPassword, lcreditOption, lcredit); } status = ENOENT; break; } } memset(line, 0, lineMax + 1); } fclose(fileHandle); } FREE_MEMORY(line); if (false == found) { OsConfigLogInfo(log, "CheckRequirementsForCommonPassword: '%s' does not contain a line '%s %s' with retry, minlen, dcredit, ucredit, ocredit, lcredit password creation options", g_etcPamdCommonPassword, password, requisite); OsConfigCaptureReason(reason, "'%s' does not contain a line '%s %s' with retry, minlen, dcredit, ucredit, ocredit, lcredit password creation options", g_etcPamdCommonPassword, password, requisite); status = ENOENT; } return status; } static int CheckPasswordRequirementFromBuffer(const char* buffer, const char* option, const char* fileName, char separator, char comment, int desired, char** reason, OsConfigLogHandle log) { int value = INT_ENOENT; int status = ENOENT; if ((NULL == buffer) || (NULL == option) || (NULL == fileName)) { OsConfigLogError(log, "CheckPasswordRequirementFromBuffer: invalid arguments"); return INT_ENOENT; } if (desired == (value = GetIntegerOptionFromBuffer(buffer, option, separator, log))) { if (comment == buffer[0]) { OsConfigLogInfo(log, "CheckPasswordRequirementFromBuffer: '%s' is set to correct value %d in '%s' but it's commented out", option, value, fileName); OsConfigCaptureReason(reason, "'%s' is set to correct value %d in '%s' but it's commented out", option, value, fileName); } else { OsConfigLogInfo(log, "CheckPasswordRequirementFromBuffer: '%s' is set to correct value %d in '%s'", option, value, fileName); OsConfigCaptureSuccessReason(reason, "'%s' is set to correct value %d in '%s'", option, value, fileName); status = 0; } } else { if (comment == buffer[0]) { OsConfigLogInfo(log, "CheckPasswordRequirementFromBuffer: '%s' is set to %d instead of %d in '%s' and it's commented out", option, value, desired, fileName); OsConfigCaptureReason(reason, "'%s' is set to %d instead of %d in '%s' and it's commented out", option, value, desired, fileName); } else { OsConfigLogInfo(log, "CheckPasswordRequirementFromBuffer: '%s' is set to %d instead of %d in '%s'", option, value, desired, fileName); OsConfigCaptureReason(reason, "'%s' is set to %d instead of %d in '%s'", option, value, desired, fileName); } } return status; } static int CheckRequirementsForPwQualityConf(int retry, int minlen, int minclass, int dcredit, int ucredit, int ocredit, int lcredit, char** reason, OsConfigLogHandle log) { FILE* fileHandle = NULL; char* line = NULL; long lineMax = sysconf(_SC_LINE_MAX); int status = ENOENT, _status = ENOENT; if (false == FileExists(g_etcSecurityPwQualityConf)) { OsConfigLogInfo(log, "CheckRequirementsForPwQualityConf: '%s' does not exist", g_etcSecurityPwQualityConf); OsConfigCaptureReason(reason, "'%s' does not exist", g_etcSecurityPwQualityConf); return ENOENT; } else if (NULL == (line = malloc(lineMax + 1))) { OsConfigLogError(log, "CheckRequirementsForPwQualityConf: out of memory"); return ENOMEM; } else { memset(line, 0, lineMax + 1); } if (NULL == (fileHandle = fopen(g_etcSecurityPwQualityConf, "r"))) { OsConfigLogInfo(log, "CheckRequirementsForPwQualityConf: cannot read from '%s'", g_etcSecurityPwQualityConf); OsConfigCaptureReason(reason, "Cannot read from '%s'", g_etcSecurityPwQualityConf); status = EACCES; } else { status = ENOENT; while (NULL != fgets(line, lineMax + 1, fileHandle)) { // Example of typical lines coming by default commented out: // //# retry = 3 //# minlen = 8 //# minclass = 0 //# dcredit = 0 //# ucredit = 0 //# lcredit = 0 //# ocredit = 0 if (NULL != strstr(line, "retry")) { _status = CheckPasswordRequirementFromBuffer(line, "retry", g_etcSecurityPwQualityConf, '=', '#', retry, reason, log); } else if (NULL != strstr(line, "minlen")) { _status = CheckPasswordRequirementFromBuffer(line, "minlen", g_etcSecurityPwQualityConf, '=', '#', minlen, reason, log); } else if (NULL != strstr(line, "minclass")) { _status = CheckPasswordRequirementFromBuffer(line, "minclass", g_etcSecurityPwQualityConf, '=', '#', minclass, reason, log); } else if (NULL != strstr(line, "dcredit")) { _status = CheckPasswordRequirementFromBuffer(line, "dcredit", g_etcSecurityPwQualityConf, '=', '#', dcredit, reason, log); } else if (NULL != strstr(line, "ucredit")) { _status = CheckPasswordRequirementFromBuffer(line, "ucredit", g_etcSecurityPwQualityConf, '=', '#', ucredit, reason, log); } else if (NULL != strstr(line, "lcredit")) { _status = CheckPasswordRequirementFromBuffer(line, "lcredit", g_etcSecurityPwQualityConf, '=', '#', lcredit, reason, log); } else if (NULL != strstr(line, "ocredit")) { _status = CheckPasswordRequirementFromBuffer(line, "ocredit", g_etcSecurityPwQualityConf, '=', '#', ocredit, reason, log); } if (_status && (0 == status)) { status = _status; } memset(line, 0, lineMax + 1); } fclose(fileHandle); } FREE_MEMORY(line); return status; } int CheckPasswordCreationRequirements(int retry, int minlen, int minclass, int dcredit, int ucredit, int ocredit, int lcredit, char** reason, OsConfigLogHandle log) { int status = ENOENT; if (0 == CheckFileExists(g_etcPamdCommonPassword, NULL, log)) { status = CheckRequirementsForCommonPassword(retry, minlen, dcredit, ucredit, ocredit, lcredit, reason, log); } else if (0 == CheckFileExists(g_etcSecurityPwQualityConf, NULL, log)) { status = CheckRequirementsForPwQualityConf(retry, minlen, minclass, dcredit, ucredit, ocredit, lcredit, reason, log); } else { OsConfigLogInfo(log, "CheckPasswordCreationRequirements: neither '%s' or '%s' exist", g_etcPamdCommonPassword, g_etcSecurityPwQualityConf); OsConfigCaptureReason(reason, "Neither '%s' or '%s' exist", g_etcPamdCommonPassword, g_etcSecurityPwQualityConf); } return status; } typedef struct PasswordCreationRequirements { const char* name; int value; } PasswordCreationRequirements; int SetPasswordCreationRequirements(int retry, int minlen, int minclass, int dcredit, int ucredit, int ocredit, int lcredit, OsConfigLogHandle log) { // These lines are used for password creation requirements configuration. // // A single line for /etc/pam.d/common-password when pam_pwquality.so is present: // // 'password requisite pam_pwquality.so retry=3 minlen=14 lcredit=-1 ucredit=-1 ocredit=-1 dcredit=-1' // // Otherwise a single line for /etc/pam.d/common-password when pam_cracklib.so is present: // // 'password required pam_cracklib.so retry=3 minlen=14 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1' // // Separate lines for /etc/security/pwquality.conf: // // 'retry = 3' // 'minlen = 14' // 'minclass = 4' // 'dcredit = -1' // 'ucredit = -1' // 'ocredit = -1' // 'lcredit = -1' // // Where: // // - password requisite pam_pwquality.so: the pam_pwquality module is required during password authentication // - retry: the user will be prompted at most this times to enter a valid password before an error is returned // - minlen: the minlen parameter sets the minimum acceptable length for a password to 14 characters // - minclass: the minimum number of character types that must be used (e.g., uppercase, lowercase, digits, other) // - lcredit: the minimum number of lowercase letters required in the password (negative means no requirement) // - ucredit: the minimum number of uppercase letters required in the password (negative means no requirement) // - ocredit: the minimum number of other (non-alphanumeric) characters required in the password (negative means none) // - dcredit: the minimum number of digits required in the password (negative means no requirement) const char* etcPamdCommonPasswordLineTemplate = "password requisite %s retry=%d minlen=%d lcredit=%d ucredit=%d ocredit=%d dcredit=%d\n"; const char* etcSecurityPwQualityConfLineTemplate = "%s = %d\n"; const char* pamPwQualitySo = "pam_pwquality.so"; const char* pamCrackLibSo = "pam_cracklib.so"; PasswordCreationRequirements entries[] = {{"retry", 0}, {"minlen", 0}, {"minclass", 0}, {"dcredit", 0}, {"ucredit", 0}, {"ocredit", 0}, {"lcredit", 0}}; int numEntries = ARRAY_SIZE(entries); bool pamPwQualitySoExists = false; bool pamCrackLibSoExists = false; bool pamUnixSoExists = false; char* pamModulePath = NULL; char* pamModulePath2 = NULL; char* pamModulePath3 = NULL; int i = 0, status = 0, _status = 0; char* line = NULL; if (0 == CheckFileExists(g_etcPamdCommonPassword, NULL, log)) { EnsurePamModulePackagesAreInstalled(log); pamPwQualitySoExists = (NULL != (pamModulePath = FindPamModule(pamPwQualitySo, log))) ? true : false; pamCrackLibSoExists = (NULL != (pamModulePath2 = FindPamModule(pamCrackLibSo, log))) ? true : false; pamUnixSoExists = (NULL != (pamModulePath3 = FindPamModule(g_pamUnixSo, log))) ? true : false; if (pamPwQualitySoExists || pamCrackLibSoExists || pamUnixSoExists) { if (NULL != (line = FormatAllocateString(etcPamdCommonPasswordLineTemplate, pamPwQualitySoExists ? pamModulePath : (pamCrackLibSoExists ? pamModulePath2 : pamModulePath3), retry, minlen, lcredit, ucredit, ocredit, dcredit))) { status = ReplaceMarkedLinesInFile(g_etcPamdCommonPassword, pamPwQualitySoExists ? pamPwQualitySo : (pamCrackLibSoExists ? pamCrackLibSo : g_pamUnixSo), line, '#', true, log); FREE_MEMORY(line); } else { OsConfigLogError(log, "SetPasswordCreationRequirements: out of memory when allocating new line for '%s'", g_etcPamdCommonPassword); } } else { status = ENOENT; } FREE_MEMORY(pamModulePath); FREE_MEMORY(pamModulePath2); FREE_MEMORY(pamModulePath3); } if (0 == CheckFileExists(g_etcSecurityPwQualityConf, NULL, log)) { entries[0].value = retry; entries[1].value = minlen; entries[2].value = minclass; entries[3].value = dcredit; entries[4].value = ucredit; entries[5].value = ocredit; entries[6].value = lcredit; for (i = 0; i < numEntries; i++) { if (NULL != (line = FormatAllocateString(etcSecurityPwQualityConfLineTemplate, entries[i].name, entries[i].value))) { _status = ReplaceMarkedLinesInFile(g_etcSecurityPwQualityConf, entries[i].name, line, '#', true, log); FREE_MEMORY(line); } else { OsConfigLogError(log, "SetPasswordCreationRequirements: out of memory when allocating new line for '%s'", g_etcSecurityPwQualityConf); } } } if ((0 == _status) || (_status && (0 == status))) { status = _status; } return status; }