//******************************************************************************************************// UnixUserInfo.cs - Gbtc//// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 08/27/2014 - J. Ritchie Carroll// Generated original version of source code.////****************************************************************************************************** // Define USE_SHARED_OBJECT to use GSF.POSIX.so shared object library for unmanaged functions// Undefine USE_SHARED_OBJECT to use internally linked unmanaged functions (e.g., Mono hosted gsf service) #define USE_SHARED_OBJECT// #undef USE_SHARED_OBJECT using System;using System.Collections.Generic;using System.Diagnostics.CodeAnalysis;using System.Globalization;using System.IO;using System.Linq;using System.Runtime.InteropServices;using System.Security;using System.Security.Principal;using System.Text;using System.Threading;using GSF.Annotations;using GSF.Collections;using GSF.Configuration;using GSF.Console;using GSF.Units;using Novell.Directory.Ldap; #pragma warning disable 0649 namespace GSF.Identity{ // Unix implementation of key UserInfo class elements internal sealed class UnixUserInfo : IUserInfo { #region [ Members ] // Nested Types // ReSharper disable UnusedMember.Local [Serializable] private class UnixIdentity : WindowsIdentity { #region [ Members ] // Fields private readonly string m_domain; private readonly string m_userName; private LdapConnection m_connection; private string m_providerType; private string m_ldapRoot; private readonly bool m_loadedUserPasswordInformation; private readonly UserPasswordInformation m_userPasswordInformation; private readonly AccountStatus m_accountStatus; #endregion #region [ Constructors ] public UnixIdentity(string domain, string userName, LdapConnection connection = null) : base(GetUserIDAsToken(domain, userName), null, WindowsAccountType.Normal, true) { m_domain = domain; m_userName = userName; Connection = connection; if (UserInfo.IsLocalDomain(domain)) { // Cache shadow information for local user before possible reduction in privileges if (GetLocalUserPasswordInformation(userName, out m_userPasswordInformation, out m_accountStatus) == 0) m_loadedUserPasswordInformation = true; else m_accountStatus = AccountStatus.Disabled; } } #endregion #region [ Properties ] public string Domain { get { return m_domain; } } public string UserName { get { return m_userName; } } public string LoginID { get { return string.Format("{0}\\{1}", m_domain, m_userName); } } public string ProviderType { get { return m_providerType; } } public string LdapRoot { get { return m_ldapRoot; } } public LdapConnection Connection { get { return m_connection; } set { m_connection = value; m_providerType = (object)m_connection == null ? "PAM_LOCAL" : "PAM_LDAP"; if ((object)m_connection != null) { // Extract LDAP search root distinguished name StringBuilder ldapRoot = new StringBuilder(); string[] elements = m_connection.GetSchemaDN().Split(','); for (int i = 0; i < elements.Length; i++) { string element = elements[i].Trim(); if (element.StartsWith("DC", StringComparison.OrdinalIgnoreCase)) { if (ldapRoot.Length > 0) ldapRoot.Append(','); ldapRoot.Append(element); } } m_ldapRoot = ldapRoot.ToString(); } else { m_ldapRoot = ""; } } } public bool LoadedUserPasswordInformation { get { return m_loadedUserPasswordInformation; } } public UserPasswordInformation UserPasswordInformation { get { return m_userPasswordInformation; } } public AccountStatus AccountStatus { get { return m_accountStatus; } } #endregion #region [ Static ] private static IntPtr GetUserIDAsToken(string domain, string userName) { uint userID; if (!UserInfo.IsLocalDomain(domain)) userName = string.Format("{0}\\{1}", domain, userName); return GetLocalUserID(userName, out userID) == 0 ? new IntPtr(userID) : IntPtr.Zero; } #endregion } private struct UserPasswordInformation { [UsedImplicitly] public long lastChangeDate; [UsedImplicitly] public long minDaysForChange; [UsedImplicitly] public long maxDaysForChange; [UsedImplicitly] public long warningDays; [UsedImplicitly] public long inactivityDays; [UsedImplicitly] public long accountExpirationDate; } private struct UserPasswordInformation32 { [UsedImplicitly] public int lastChangeDate; [UsedImplicitly] public int minDaysForChange; [UsedImplicitly] public int maxDaysForChange; [UsedImplicitly] public int warningDays; [UsedImplicitly] public int inactivityDays; [UsedImplicitly] public int accountExpirationDate; } private enum AccountStatus { [UsedImplicitly] Normal, // 0: Normal - encrypted password [UsedImplicitly] Disabled, // 1: Password is * [UsedImplicitly] LockedOut, // 2: Password starts with ! [UsedImplicitly] NoPassword // 3: Password is not defined (blank) } private enum PAMResponseCode { PAM_SYSTEM_ERR = 4, PAM_BUF_ERR = 5, PAM_PERM_DENIED = 7, PAM_MAXTRIES = 8, PAM_AUTH_ERR = 9, PAM_CRED_INSUFFICIENT = 11, PAM_AUTHINFO_UNAVAIL = 12, PAM_USER_UNKNOWN = 13, PAM_AUTHTOK_ERR = 20, PAM_AUTHTOK_RECOVERY_ERR = 21, PAM_AUTHTOK_LOCK_BUSY = 22, PAM_AUTHTOK_DISABLE_AGING = 23, PAM_ABORT = 26, PAM_TRY_AGAIN = 27 } // Constants private const int MaxAccountNameLength = 256; // DllImport code is in GSF.POSIX.c#if USE_SHARED_OBJECT private const string ImportFileName = "./GSF.POSIX.so";#else private const string ImportFileName = "__Internal";#endif // Fields private readonly UserInfo m_parent; private LdapConnection m_connection; private LdapEntry m_userEntry; private string m_ldapRoot; private bool m_domainRespondsForUser; private bool m_isLocalAccount; private bool m_enabled; private bool m_initialized; private bool m_disposed; #endregion #region [ Constructors ] public UnixUserInfo(UserInfo parent) { m_parent = parent; } ~UnixUserInfo() { Dispose(false); } #endregion #region [ Properties ] public bool DomainRespondsForUser { get { if (!m_initialized) { // Attempt to initialize try { Initialize(); } catch (InitializationException) { // Initialization failures are due to domain not responding, // member flag is set in Initialize() method. } } return m_domainRespondsForUser; } } public bool Exists { get { bool exists = false; if (!m_initialized) { // Attempt to initialize try { Initialize(); } catch (InitializationException) { // User could not be found - this could simply mean that ActiveDirectory is unavailable (e.g., laptop disconnected from the domain). // In this case, if user logged in with cached credentials they are at least authenticated so we can assume that the user exists... WindowsPrincipal windowsPrincipal = Thread.CurrentPrincipal as WindowsPrincipal; exists = (object)windowsPrincipal != null && ((!string.IsNullOrEmpty(m_parent.LoginID) && windowsPrincipal.Identity.Name.Equals(m_parent.LoginID, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(m_parent.UserName) && windowsPrincipal.Identity.Name.Equals(m_parent.UserName, StringComparison.OrdinalIgnoreCase))) && windowsPrincipal.Identity.IsAuthenticated; } } if (!exists) { if (IsLocalAccount) { try { exists = LocalUserExists(m_parent.UserName); } catch { exists = false; } } else { exists = ((object)m_userEntry != null); } } return exists; } } public bool Enabled { get { return m_enabled; } set { m_enabled = value; } } public DateTime LastLogon { get { DateTime lastLogon = DateTime.MinValue; if (m_enabled) { try { CommandResponse response = Command.Execute("lastlog", "-u " + EncodeAccountName(m_isLocalAccount ? m_parent.UserName : m_parent.LoginID)); if (response.ExitCode == 0) { string[] lines = response.StandardOutput.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length < 2 || lines[1].Length < 44 || !DateTime.TryParseExact(lines[1].Substring(43), "ddd MMM d HH:mm:ss zzff yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite, out lastLogon)) lastLogon = DateTime.MinValue; } else { if (!m_isLocalAccount) lastLogon = DateTime.FromFileTime(long.Parse(GetUserPropertyValue("lastLogon"))); } } catch { lastLogon = DateTime.MinValue; } } return lastLogon; } } public DateTime AccountCreationDate { get { DateTime creationDate = DateTime.MinValue; if (m_enabled) { try { creationDate = Directory.GetCreationTime(Environment.GetEnvironmentVariable("HOME") ?? EncodeAccountName("/home/" + m_parent.UserName)); } catch { if (!m_isLocalAccount || !DateTime.TryParseExact(GetUserPropertyValue("whenCreated"), "yyyyMMddHHmmss.fZ", CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite | DateTimeStyles.AssumeUniversal, out creationDate)) creationDate = DateTime.MinValue; } } return creationDate; } } public DateTime NextPasswordChangeDate { get { DateTime passwordChangeDate = DateTime.MaxValue; if (m_enabled) { if (IsLocalAccount) { UserPasswordInformation userPasswordInfo; AccountStatus status; if (GetCachedLocalUserPasswordInformation(m_isLocalAccount ? m_parent.UserName : m_parent.LoginID, out userPasswordInfo, out status) == 0) { if (userPasswordInfo.maxDaysForChange >= 99999) { passwordChangeDate = DateTime.MaxValue; } else { // From chage.c source: // The password expiration date is determined from the last change // date plus the number of days the password is valid for. UnixTimeTag expirationDate = new UnixTimeTag((uint)(userPasswordInfo.lastChangeDate + userPasswordInfo.maxDaysForChange)); passwordChangeDate = expirationDate.ToDateTime(); } } } else { try { long passwordSetOn = long.Parse(GetUserPropertyValue("pwdLastSet")); if (passwordSetOn == 0) { // User must change password on next logon.Lines 1–500 of 2006. Load more
//****************************************************************************************************** // UnixUserInfo.cs - Gbtc // // Copyright © 2014, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 08/27/2014 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** // Define USE_SHARED_OBJECT to use GSF.POSIX.so shared object library for unmanaged functions // Undefine USE_SHARED_OBJECT to use internally linked unmanaged functions (e.g., Mono hosted gsf service) #define USE_SHARED_OBJECT // #undef USE_SHARED_OBJECT using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Text; using System.Threading; using GSF.Annotations; using GSF.Collections; using GSF.Configuration; using GSF.Console; using GSF.Units; using Novell.Directory.Ldap; #pragma warning disable 0649 namespace GSF.Identity { // Unix implementation of key UserInfo class elements internal sealed class UnixUserInfo : IUserInfo { #region [ Members ] // Nested Types // ReSharper disable UnusedMember.Local [Serializable] private class UnixIdentity : WindowsIdentity { #region [ Members ] // Fields private readonly string m_domain; private readonly string m_userName; private LdapConnection m_connection; private string m_providerType; private string m_ldapRoot; private readonly bool m_loadedUserPasswordInformation; private readonly UserPasswordInformation m_userPasswordInformation; private readonly AccountStatus m_accountStatus; #endregion #region [ Constructors ] public UnixIdentity(string domain, string userName, LdapConnection connection = null) : base(GetUserIDAsToken(domain, userName), null, WindowsAccountType.Normal, true) { m_domain = domain; m_userName = userName; Connection = connection; if (UserInfo.IsLocalDomain(domain)) { // Cache shadow information for local user before possible reduction in privileges if (GetLocalUserPasswordInformation(userName, out m_userPasswordInformation, out m_accountStatus) == 0) m_loadedUserPasswordInformation = true; else m_accountStatus = AccountStatus.Disabled; } } #endregion #region [ Properties ] public string Domain { get { return m_domain; } } public string UserName { get { return m_userName; } } public string LoginID { get { return string.Format("{0}\\{1}", m_domain, m_userName); } } public string ProviderType { get { return m_providerType; } } public string LdapRoot { get { return m_ldapRoot; } } public LdapConnection Connection { get { return m_connection; } set { m_connection = value; m_providerType = (object)m_connection == null ? "PAM_LOCAL" : "PAM_LDAP"; if ((object)m_connection != null) { // Extract LDAP search root distinguished name StringBuilder ldapRoot = new StringBuilder(); string[] elements = m_connection.GetSchemaDN().Split(','); for (int i = 0; i < elements.Length; i++) { string element = elements[i].Trim(); if (element.StartsWith("DC", StringComparison.OrdinalIgnoreCase)) { if (ldapRoot.Length > 0) ldapRoot.Append(','); ldapRoot.Append(element); } } m_ldapRoot = ldapRoot.ToString(); } else { m_ldapRoot = ""; } } } public bool LoadedUserPasswordInformation { get { return m_loadedUserPasswordInformation; } } public UserPasswordInformation UserPasswordInformation { get { return m_userPasswordInformation; } } public AccountStatus AccountStatus { get { return m_accountStatus; } } #endregion #region [ Static ] private static IntPtr GetUserIDAsToken(string domain, string userName) { uint userID; if (!UserInfo.IsLocalDomain(domain)) userName = string.Format("{0}\\{1}", domain, userName); return GetLocalUserID(userName, out userID) == 0 ? new IntPtr(userID) : IntPtr.Zero; } #endregion } private struct UserPasswordInformation { [UsedImplicitly] public long lastChangeDate; [UsedImplicitly] public long minDaysForChange; [UsedImplicitly] public long maxDaysForChange; [UsedImplicitly] public long warningDays; [UsedImplicitly] public long inactivityDays; [UsedImplicitly] public long accountExpirationDate; } private struct UserPasswordInformation32 { [UsedImplicitly] public int lastChangeDate; [UsedImplicitly] public int minDaysForChange; [UsedImplicitly] public int maxDaysForChange; [UsedImplicitly] public int warningDays; [UsedImplicitly] public int inactivityDays; [UsedImplicitly] public int accountExpirationDate; } private enum AccountStatus { [UsedImplicitly] Normal, // 0: Normal - encrypted password [UsedImplicitly] Disabled, // 1: Password is * [UsedImplicitly] LockedOut, // 2: Password starts with ! [UsedImplicitly] NoPassword // 3: Password is not defined (blank) } private enum PAMResponseCode { PAM_SYSTEM_ERR = 4, PAM_BUF_ERR = 5, PAM_PERM_DENIED = 7, PAM_MAXTRIES = 8, PAM_AUTH_ERR = 9, PAM_CRED_INSUFFICIENT = 11, PAM_AUTHINFO_UNAVAIL = 12, PAM_USER_UNKNOWN = 13, PAM_AUTHTOK_ERR = 20, PAM_AUTHTOK_RECOVERY_ERR = 21, PAM_AUTHTOK_LOCK_BUSY = 22, PAM_AUTHTOK_DISABLE_AGING = 23, PAM_ABORT = 26, PAM_TRY_AGAIN = 27 } // Constants private const int MaxAccountNameLength = 256; // DllImport code is in GSF.POSIX.c #if USE_SHARED_OBJECT private const string ImportFileName = "./GSF.POSIX.so"; #else private const string ImportFileName = "__Internal"; #endif // Fields private readonly UserInfo m_parent; private LdapConnection m_connection; private LdapEntry m_userEntry; private string m_ldapRoot; private bool m_domainRespondsForUser; private bool m_isLocalAccount; private bool m_enabled; private bool m_initialized; private bool m_disposed; #endregion #region [ Constructors ] public UnixUserInfo(UserInfo parent) { m_parent = parent; } ~UnixUserInfo() { Dispose(false); } #endregion #region [ Properties ] public bool DomainRespondsForUser { get { if (!m_initialized) { // Attempt to initialize try { Initialize(); } catch (InitializationException) { // Initialization failures are due to domain not responding, // member flag is set in Initialize() method. } } return m_domainRespondsForUser; } } public bool Exists { get { bool exists = false; if (!m_initialized) { // Attempt to initialize try { Initialize(); } catch (InitializationException) { // User could not be found - this could simply mean that ActiveDirectory is unavailable (e.g., laptop disconnected from the domain). // In this case, if user logged in with cached credentials they are at least authenticated so we can assume that the user exists... WindowsPrincipal windowsPrincipal = Thread.CurrentPrincipal as WindowsPrincipal; exists = (object)windowsPrincipal != null && ((!string.IsNullOrEmpty(m_parent.LoginID) && windowsPrincipal.Identity.Name.Equals(m_parent.LoginID, StringComparison.OrdinalIgnoreCase)) || (!string.IsNullOrEmpty(m_parent.UserName) && windowsPrincipal.Identity.Name.Equals(m_parent.UserName, StringComparison.OrdinalIgnoreCase))) && windowsPrincipal.Identity.IsAuthenticated; } } if (!exists) { if (IsLocalAccount) { try { exists = LocalUserExists(m_parent.UserName); } catch { exists = false; } } else { exists = ((object)m_userEntry != null); } } return exists; } } public bool Enabled { get { return m_enabled; } set { m_enabled = value; } } public DateTime LastLogon { get { DateTime lastLogon = DateTime.MinValue; if (m_enabled) { try { CommandResponse response = Command.Execute("lastlog", "-u " + EncodeAccountName(m_isLocalAccount ? m_parent.UserName : m_parent.LoginID)); if (response.ExitCode == 0) { string[] lines = response.StandardOutput.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length < 2 || lines[1].Length < 44 || !DateTime.TryParseExact(lines[1].Substring(43), "ddd MMM d HH:mm:ss zzff yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite, out lastLogon)) lastLogon = DateTime.MinValue; } else { if (!m_isLocalAccount) lastLogon = DateTime.FromFileTime(long.Parse(GetUserPropertyValue("lastLogon"))); } } catch { lastLogon = DateTime.MinValue; } } return lastLogon; } } public DateTime AccountCreationDate { get { DateTime creationDate = DateTime.MinValue; if (m_enabled) { try { creationDate = Directory.GetCreationTime(Environment.GetEnvironmentVariable("HOME") ?? EncodeAccountName("/home/" + m_parent.UserName)); } catch { if (!m_isLocalAccount || !DateTime.TryParseExact(GetUserPropertyValue("whenCreated"), "yyyyMMddHHmmss.fZ", CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite | DateTimeStyles.AssumeUniversal, out creationDate)) creationDate = DateTime.MinValue; } } return creationDate; } } public DateTime NextPasswordChangeDate { get { DateTime passwordChangeDate = DateTime.MaxValue; if (m_enabled) { if (IsLocalAccount) { UserPasswordInformation userPasswordInfo; AccountStatus status; if (GetCachedLocalUserPasswordInformation(m_isLocalAccount ? m_parent.UserName : m_parent.LoginID, out userPasswordInfo, out status) == 0) { if (userPasswordInfo.maxDaysForChange >= 99999) { passwordChangeDate = DateTime.MaxValue; } else { // From chage.c source: // The password expiration date is determined from the last change // date plus the number of days the password is valid for. UnixTimeTag expirationDate = new UnixTimeTag((uint)(userPasswordInfo.lastChangeDate + userPasswordInfo.maxDaysForChange)); passwordChangeDate = expirationDate.ToDateTime(); } } } else { try { long passwordSetOn = long.Parse(GetUserPropertyValue("pwdLastSet")); if (passwordSetOn == 0) { // User must change password on next logon.