Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

add @chao-mu enumusers module #1159

Closed
wants to merge 4 commits into from

3 participants

@mubix

Submitting on behalf of @chao-mu from patch supplied here:
https://dev.metasploit.com/redmine/issues/3723

Module functions as expected:

show options

Module options (post/windows/gather/enum_users_details):

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   GROUP_INFO  false            yes       Display local and global group information
   LEVEL       0                yes       Corresponds to the "levels" used by NetUserEnum (accepted: 0, 1, 2, 3)
   SERVER                       no        Server on which to run this command
   SESSION     6                yes       The session to run this module on.
   SHOWN       normal_account   yes       What kind of accounts will be enumerated (accepted: all, normal_account, temp_duplicate_account, workstation_trust_account, server_trust_account, interdomain_trust_account)

msf  post(enum_users_details) > run

[*] Enumerating users...
[+] Account name: Administrator
[+] Account name: evilattacker
[+] Account name: Guest
[+] Account name: HelpAssistant
[+] Account name: SUPPORT_388945a0
[*] Post module execution completed

lib/msf/core/post/windows/accounts.rb
((5 lines not shown))
module Msf
class Post
module Windows
module Accounts
+ #include Msf::Post::Windows::Railgun::NetAPI32
@jlee-r7 Collaborator
jlee-r7 added a note

Remove, please.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7
Collaborator

I think Post methods that rely on railgun need to test for its existence. As it is, you'll get a bunch of NoMethodErrors on nil for sessions that aren't native meterpreter.

@wvu-r7
Collaborator

@mubix: Ping? :)

@wvu-r7
Collaborator

Gotta agree about @jlee-r7's Railgun suggestion. Closing this till @mubix has time to implement the check.

@wvu-r7 wvu-r7 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 11, 2012
  1. @mubix

    add @chao-mu enumusers module

    mubix authored
  2. @mubix
  3. @mubix

    msftidy fix

    mubix authored
Commits on Dec 12, 2012
  1. @mubix

    remove include

    mubix authored
This page is out of date. Refresh to see the latest.
View
278 lib/msf/core/post/windows/accounts.rb
@@ -1,9 +1,287 @@
# -*- coding: binary -*-
+
+require 'rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32'
+
module Msf
class Post
module Windows
module Accounts
+
+ # We will want to access the data types defined therein, but not have to type it out
+ NETAPI32_DEF = Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::Def::Def_netapi32
+
+ # WinAPI constant name to friendly symbol
+ USER_AUTH_FLAGS = {
+ 'AF_OP_PRINT' => :printer_operator,
+ 'AF_OP_COMM' => :communications_operator,
+ 'AF_OP_SERVER' => :server_operator,
+ 'AF_OP_COMM' => :accounts_operator,
+ }
+
+ # WinAPI constant name to friendly symbol
+ USER_FLAGS = {
+ 'UF_SCRIPT' => :logon_script_executed,
+ 'UF_ACCOUNTDISABLE' => :account_disabled,
+ 'UF_HOMEDIR_REQUIRED' => :homedir_required,
+ 'UF_PASSWD_NOTREQD' => :password_not_required,
+ 'UF_PASSWD_CANT_CHANGE' => :password_cant_change,
+ 'UF_LOCKOUT' => :locked_out,
+ 'UF_DONT_EXPIRE_PASSWD' => :dont_expire_password,
+ 'UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED' => :encrypted_text_password_allowed,
+ 'UF_NOT_DELEGATED' => :not_delegated,
+ 'UF_SMARTCARD_REQUIRED' => :smartcard_required,
+ 'UF_USE_DES_KEY_ONLY' => :use_des_key_only,
+ 'UF_DONT_REQUIRE_PREAUTH' => :dont_require_preauth,
+ 'UF_TRUSTED_FOR_DELEGATION' => :trusted_for_delegation,
+ # Windows 2000: This value is not supported.
+ 'UF_PASSWORD_EXPIRED' => :password_expired,
+ # Windows XP/2000: This value is not supported.
+ 'UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION' => :trusted_to_authenticate_for_delegation,
+ 'UF_NORMAL_ACCOUNT' => :normal_account,
+ 'UF_TEMP_DUPLICATE_ACCOUNT' => :temp_duplicate_account,
+ 'UF_WORKSTATION_TRUST_ACCOUNT' => :workstation_trust_account,
+ 'UF_SERVER_TRUST_ACCOUNT' => :server_trust_account,
+ 'UF_INTERDOMAIN_TRUST_ACCOUNT' => :interdomain_trust_account
+ }
+
+ # Symbols to integers recognized by NetUserEnum for the 'filter' param
+ # or WinAPI constant names for such values
+ ENUM_USERS_FILTERS = {
+ :all => 0,
+ :normal_account => 'FILTER_NORMAL_ACCOUNT',
+ :temp_duplicate_account => 'FILTER_TEMP_DUPLICATE_ACCOUNT',
+ :workstation_trust_account => 'FILTER_WORKSTATION_TRUST_ACCOUNT',
+ :server_trust_account => 'FILTER_SERVER_TRUST_ACCOUNT',
+ :interdomain_trust_account => 'FILTER_INTERDOMAIN_TRUST_ACCOUNT',
+ }
+
+ # Levels enum_users(...) supports and the corresponding data structure type
+ USER_INFO_LEVELS = {
+ 0 => NETAPI32_DEF::USER_INFO_0,
+ 1 => NETAPI32_DEF::USER_INFO_1,
+ 2 => NETAPI32_DEF::USER_INFO_2,
+ 3 => NETAPI32_DEF::USER_INFO_3,
+ }
+
+ ###
+ # enum_users(level, filter_sym = :all, server_name = nil)
+ #
+ # Summary:
+ # Enumerates information regarding all of this system's users.
+ #
+ # Parameters
+ # level: Numeric level of information. USER_INFO_LEVELS contains supported levels
+ # To see a description of levels and the information they provide,
+ # see http://msdn.microsoft.com/en-us/library/aa370652%28v=vs.85%29.aspx
+ # filter_sym (opt): Specifies the type of accounts included. See ENUM_USERS_FILTERS
+ # server_name (opt): The computer on which to run this. nil is this/victim computer
+ #
+ # Returns:
+ # An array of hashes containing user information relative to the given "level"
+ #
+ # Caveats:
+ # On errors, error information is printed and nil is returned
+ # You will get Computer accounts
+ # Underprivileged users will get partial information or errors
+ ##
+ def enum_users(level, filter_sym = :all, server_name = nil)
+ util = client.railgun.util
+ netapi = client.railgun.netapi32
+
+ user_info_type = USER_INFO_LEVELS[level]
+ filter = ENUM_USERS_FILTERS[filter_sym]
+
+ # Call http://msdn.microsoft.com/en-us/library/aa370652%28v=vs.85%29.aspx
+ result = netapi.NetUserEnum(
+ server_name,
+ level,
+ filter,
+ 8,#bufptr - allocate 8 bytes for both x86 and x64 pointers
+ -1,#prefmaxlen
+ 4,#entriesread - railgun wants us to pass in 4 since this is PDWORD
+ 4,#totalentries - railgun wants us to pass in 4 since this is PDWORD
+ 0) #resume_handle
+
+ # NET_API_STATUS
+ status = result['return']
+
+ # get the pointer to the USER_INFO_X array
+ bufptr = util.read_pointer(result['bufptr'])
+
+ # Check that the call succeeded.
+ unless status == 0
+ print_error 'Unable to enumerate users:' <<
+ " Failed with error code #{result['GetLastError']}" <<
+ " and NET_API_STATUS of #{status}"
+
+ unless util.is_null_pointer(bufptr)
+ netapi.NetApiBufferFree(bufptr)
+ end
+
+ return nil
+ end
+
+
+ # The hashes we will return to the caller after beautification
+ clean_user_info_hashes = []
+
+ # get the ammount of entries in the USER_INFO_X array
+ entries_read = result['entriesread']
+
+ # loop through the read entries in the USER_INFO_X array
+ util.read_array(user_info_type, entries_read, bufptr).each do |info|
+ # We want to "clean" the info, as in make it useful outside of WinAPI
+ clean_user_info_hashes.push(clean_USER_INFO_N(info))
+ end
+
+ # Clean up time! LNT
+ netapi.NetApiBufferFree(bufptr)
+
+ return clean_user_info_hashes
+ end
+
+ ###
+ # get_user_groups(username, type, servername = nil)
+ #
+ # Summary:
+ # Enumerates local or global groups
+ #
+ # Parameters:
+ # username - The user's account name
+ # type - Dictates the type of group to list. either :global or :local
+ # servername - The computer onwhich to run the WinAPI calls
+ #
+ # Caveats:
+ # On errors, error information is printed and nil is returned
+ #
+ # Returns:
+ # An array containing a hash for each group. Currently :name is the only key
+ ##
+ def get_user_groups(username, type, servername = nil)
+ netapi = client.railgun.netapi32
+ util = client.railgun.util
+
+ result = case type
+ when :global
+ netapi.NetUserGetGroups(servername, username, 0, 8, 0xFFFFFFFF, 4, 4)
+ when :local
+ netapi.NetUserGetLocalGroups(servername, username, 0, 0, 8, 0xFFFFFFFF, 4, 4)
+ else
+ raise ArgumentError, "get_user_groups type must be :global or :local"
+ end
+
+ # NET_API_STATUS
+ status = result['return']
+
+ # get the pointer to the GROUP_USERS_INFO_0 array
+ bufptr = util.unpack_pointer(result['bufptr'])
+
+ # Check that the call succeeded.
+ unless status == 0
+ print_error 'Unable to enumerate groups:' <<
+ " Failed with error code #{result['GetLastError']}" <<
+ " and NET_API_STATUS of #{status}"
+
+ unless util.is_null_pointer(bufptr)
+ netapi.NetApiBufferFree(bufptr)
+ end
+
+ return nil
+ end
+
+ entries_read = result['entriesread']
+
+ groups = util.read_array(NETAPI32_DEF::GROUP_USERS_INFO_0, entries_read, bufptr)
+
+ if groups.length == 1 && groups[0][:name] == 'None'
+ return []
+ end
+
+ return groups
+ end
+
+ ###
+ # clean_USER_INFO_N(struct)
+ #
+ # Summary:
+ # Takes a USER_INFO_ hash and strips out or translates the parts
+ # that would be useless outside of interacting with railgun/winapi
+ #
+ # Mappings:
+ # :priv => :guest, :user, or :admin
+ # :auth_flags => Becomes a hash with symbols to true/false, see USER_AUTH_FLAGS
+ # :flags => Becomes a hash with symbols to true/false, see USER_FLAGS
+ # :acct_expires => Same value, except :never if never
+ # :max_storage => Same value, except :unlimited if unlimited
+ # :last_logon, :last_logoff, :password_age => Same value, except 'unknown' if unknown
+ #
+ # Deleted:
+ # :password (will be unavailable. Please prove me wrong!)
+ # :units_per_week, logon_hours (TODO: write the algorithm to calculate the latter)
+ ##
+ def clean_USER_INFO_N(struct)
+ user_info = {}
+ rg = client.railgun
+
+ # Copy over everything (using a symbol as the key) before pruning
+ struct.each do |key, value|
+ user_info[key] = value
+ end
+
+ # usriX_password will be empty
+ # LPWSTR usri3_password
+ user_info.delete(:password)
+
+ if struct.has_key?(:priv)
+ user_info[:priv] = case struct[:priv]
+ when rg.const('USER_PRIV_GUEST') then :guest
+ when rg.const('USER_PRIV_USER') then :user
+ when rg.const('USER_PRIV_ADMIN') then :admin
+ end
+ end
+
+ # Break apart the flags into symbols
+ {
+ :auth_flags => USER_AUTH_FLAGS,
+ :flags => USER_FLAGS
+ }.each do |key, flag_mappings|
+ if user_info.has_key?(key)
+ user_info[key] = rg.util.judge_bit_field(user_info[key], flag_mappings)
+ end
+ end
+
+ [:last_logon, :last_logoff, :password_age].each do |key|
+ if user_info.has_key?(key)
+ time = user_info[key]
+
+ user_info[key] = (time == 0 ? :unknown : time)
+ end
+ end
+
+ if user_info.has_key?(:acct_expires)
+ expiry = user_info[:acct_expires]
+
+ #TODO: Add TIMEQ_FOREVER to constant manager? -1
+ user_info[:acct_expires] = (expiry == 4294967295 ? :never : expiry)
+ end
+
+ if user_info.has_key?(:max_storage)
+ limit = user_info[:max_storage]
+
+ #TODO: Add USER_MAXSTORAGE_UNLIMITED to constant manager? -1
+ user_info[:max_storage] = (limit == 4294967295 ? :unlimited : limit)
+ end
+
+ # TODO: Implement the algorithm to calculate logon_hours (see USER_INFO_3 docs)
+ [:units_per_week, :logon_hours].each do |x|
+ user_info.delete(x)
+ end
+
+ return user_info
+ end
+
+
##
# delete_user(username, server_name = nil)
View
4 lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb
@@ -3666,11 +3666,11 @@ def self.create_dll(dll_path = 'kernel32')
# ])
dll.add_function( 'lstrlenA', 'DWORD',[
- ["PCHAR","lpString","in"],
+ ["LPVOID","lpString","in"],
])
dll.add_function( 'lstrlenW', 'DWORD',[
- ["PWCHAR","lpString","in"],
+ ["LPVOID","lpString","in"],
])
View
151 lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb
@@ -9,6 +9,109 @@ module Def
class Def_netapi32
+ GROUP_USERS_INFO_0 = [
+ [:name, :LPWSTR]
+ ]
+
+ USER_INFO_0 = [
+ [:name, :LPWSTR]
+ ]
+
+ USER_INFO_1 = [
+ [:name, :LPWSTR],
+ [:password, :LPWSTR],
+ [:password_age, :DWORD],
+ [:priv, :DWORD],
+ [:home_dir, :LPWSTR],
+ [:comment, :LPWSTR],
+ [:flags, :DWORD],
+ [:script_path, :LPWSTR]
+ ]
+
+ USER_INFO_2 = [
+ [:name, :LPWSTR],
+ [:password, :LPWSTR],
+ [:password_age, :DWORD],
+ [:priv, :DWORD],
+ [:home_dir, :LPWSTR],
+ [:comment, :LPWSTR],
+ [:flags, :DWORD],
+ [:script_path, :LPWSTR],
+ [:auth_flags, :DWORD],
+ [:full_name, :LPWSTR],
+ [:usr_comment, :LPWSTR],
+ [:parms, :LPWSTR],
+ [:workstations, :LPWSTR],
+ [:last_logon, :DWORD],
+ [:last_logoff, :DWORD],
+ [:acct_expires, :DWORD],
+ [:max_storage, :DWORD],
+ [:units_per_week, :DWORD],
+ [:logon_hours, :PBYTE],
+ [:bad_pw_count, :DWORD],
+ [:num_logons, :DWORD],
+ [:logon_server, :LPWSTR],
+ [:country_code, :DWORD],
+ [:code_page, :DWORD]
+ ]
+
+ USER_INFO_3 = [
+ [:name, :LPWSTR],
+ [:password, :LPWSTR],
+ [:password_age, :DWORD],
+ [:priv, :DWORD],
+ [:home_dir, :LPWSTR],
+ [:comment, :LPWSTR],
+ [:flags, :DWORD],
+ [:script_path, :LPWSTR],
+ [:auth_flags, :DWORD],
+ [:full_name, :LPWSTR],
+ [:usr_comment, :LPWSTR],
+ [:parms, :LPWSTR],
+ [:workstations, :LPWSTR],
+ [:last_logon, :DWORD],
+ [:last_logoff, :DWORD],
+ [:acct_expires, :DWORD],
+ [:max_storage, :DWORD],
+ [:units_per_week, :DWORD],
+ [:logon_hours, :PBYTE],
+ [:bad_pw_count, :DWORD],
+ [:num_logons, :DWORD],
+ [:logon_server, :LPWSTR],
+ [:country_code, :DWORD],
+ [:code_page, :DWORD],
+ [:user_id, :DWORD],
+ [:primary_group_id, :DWORD],
+ [:profile, :LPWSTR],
+ [:home_dir_drive, :LPWSTR],
+ [:password_expired, :DWORD]
+ ]
+
+ SERVER_INFO_101 = [
+ [:platform_id, :DWORD],
+ [:name, :LPWSTR],
+ [:version_major, :DWORD],
+ [:version_minor, :DWORD],
+ [:type, :DWORD],
+ [:comment, :LPWSTR]
+ ]
+
+ SERVER_INFO_102 = [
+ [:platform_id, :DWORD],
+ [:name, :LPWSTR],
+ [:version_major, :DWORD],
+ [:version_minor, :DWORD],
+ [:type, :DWORD],
+ [:comment, :LPWSTR],
+ [:users, :DWORD],
+ [:disc, :LONG],
+ [:hidden, :BOOL],
+ [:announce, :DWORD],
+ [:anndelta, :DWORD],
+ [:licenses, :DWORD],
+ [:userpath, :LPWSTR]
+ ]
+
def self.create_dll(dll_path = 'netapi32')
dll = DLL.new(dll_path, ApiConstants.manager)
@@ -23,6 +126,54 @@ def self.create_dll(dll_path = 'netapi32')
["PDWORD","BufferType","out"]
])
+ dll.add_function('NetServerGetInfo', 'DWORD',[
+ ["PWCHAR","servername","in"],
+ ["DWORD","level","in"],
+ ["PDWORD","bufptr","out"],
+ ])
+
+ # http://msdn.microsoft.com/en-us/library/aa370653%28v=vs.85%29.aspx
+ dll.add_function('NetUserGetGroups', 'DWORD',[
+ ["PWCHAR","servername","in"],
+ ["PWCHAR","username","in"],
+ ["DWORD","level","in"],
+ # __out LPBYTE *bufptr,
+ ["PBLOB","bufptr","out"],
+ ["DWORD","prefmaxlen","in"],
+ ["PDWORD","entriesread","out"],
+ ["PDWORD","totalentries","out"],
+ ])
+
+ # http://msdn.microsoft.com/en-us/library/aa370655(v=VS.85).aspx
+ dll.add_function('NetUserGetLocalGroups', 'DWORD',[
+ ["PWCHAR","servername","in"],
+ ["PWCHAR","username","in"],
+ ["DWORD","level","in"],
+ ["DWORD","flags","in"],
+ # __out LPBYTE *bufptr,
+ ["PBLOB","bufptr","out"],
+ ["DWORD","prefmaxlen","in"],
+ ["PDWORD","entriesread","out"],
+ ["PDWORD","totalentries","out"],
+ ])
+
+ # http://msdn.microsoft.com/en-us/library/aa370652(VS.85).aspx
+ dll.add_function('NetUserEnum', 'DWORD',[
+ ["PCHAR","servername","in"],
+ ["DWORD", "level", 'in'],
+ ["DWORD", "filter","in"],
+ ['PBLOB', 'bufptr', 'out'],
+ ['DWORD', 'prefmaxlen','in'],
+ ["PDWORD", "entriesread",'out'],
+ ["PDWORD", "totalentries",'out'],
+ ["DWORD", "resume_handle",'inout']
+ ])
+
+ dll.add_function('NetApiBufferFree', 'LPVOID',[
+ ["LPVOID","Buffer","in"],
+ ])
+
+
dll.add_function('NetServerEnum', 'DWORD',[
["PWCHAR","servername","in"],
["DWORD","level","in"],
View
152 modules/post/windows/gather/enum_users_details.rb
@@ -0,0 +1,152 @@
+require 'msf/core'
+require 'rex/text'
+require 'msf/core/post/windows/accounts'
+
+class Metasploit3 < Msf::Post
+
+ include Msf::Post::Windows::Accounts
+
+ def initialize(info={})
+ super( update_info( info,
+ 'Name' => 'Windows Enumerate Users and Details',
+ 'Description' => %q{ This module enumerates users and their details.
+ The information is gathered purely through WinAPI calls, meaning this
+ module leaves less of a trace than executing commands. The information
+ reported (with the exception of group info) corresponds to the various
+ USER_INFO data structures that NetAPI32's NetUserEnum function returns
+ and can be controlled in the same manner with this module's 'LEVEL' option.
+ Not all levels are supported on all operating systems, see the documentation
+ for NetUserEnum (http://goo.gl/JdjKa) for more information. Also, the
+ amount of information may further be restricted depending on the
+ session's privileges. Note, Computer accounts are shown (and designated as such).
+ },
+ 'License' => MSF_LICENSE,
+ 'Author' => [ 'chao-mu'],
+ 'Platform' => [ 'windows' ],
+ 'SessionTypes' => [ 'meterpreter' ]
+ ))
+
+ register_options(
+ [
+ OptEnum.new('LEVEL', [true, 'Corresponds to the "levels" used by NetUserEnum',
+ 3, USER_INFO_LEVELS.keys]),
+ OptBool.new('GROUP_INFO', [true, 'Display local and global group information', false]),
+ OptEnum.new('SHOWN', [true, 'What kind of accounts will be enumerated',
+ :all, ENUM_USERS_FILTERS.keys]),
+ OptString.new('SERVER', [false, 'Server on which to run this command']),
+ ], self.class)
+ end
+
+ # Descriptions are from http://msdn.microsoft.com/en-us/library/aa371338%28v=vs.85%29.aspx
+ USER_FLAG_TO_DESCRIPTION = {
+ :logon_script_executed => 'The logon script executed',
+ :account_disabled => 'Acount is disabled',
+ :homedir_required => 'The home directory is required',
+ :password_not_required => 'No password required',
+ :password_cant_change => 'User cannot change their password',
+ :locked_out => 'Account is currently locked out',
+ :dont_expire_password => 'Password should never expire',
+ :encrypted_text_password_allowed => 'Password is stored under reversible encryption in the Active Directory',
+ :not_delegated => 'Other users cannot act as delegates of this user account',
+ :smartcard_required => 'User is required to log on with a smart card',
+ :use_des_key_only => 'Principal restricted to use only DES encryption types for keys',
+ :dont_require_preauth => 'Acount does not require Kerberos preauthentication for logon',
+ :trusted_for_delegation => 'Account is enabled for delegation',
+ :password_expired => 'Password has expired',
+ :trusted_to_authenticate_for_delegation => 'Account is trusted to authenticate a user outside of the Kerberos security package and delegate that user through constrained delegation',
+ :normal_account => 'Account type represents a typical user',
+ :temp_duplicate_account => 'This account is for a user whose primary account is in another domain',
+ :workstation_trust_account => 'This is a computer account for a computer that is a member of this domain',
+ :server_trust_account => 'This is a computer account for a backup domain controller that is a member of this domain',
+ :interdomain_trust_account => 'This is a permit to trust account for a domain that trusts other domains',
+ }
+
+ USER_INFO_REPORTS = [
+ [:comment, 'Comment'],
+ [:priv, 'Privilege level'],
+ [:password_age, 'Seconds since password last changed'],
+ [:home_dir, 'Home directory'],
+ [:script_path, 'Logon script'],
+ [:full_name, 'Full name'],
+ [:workstations, 'Workstations user can log into'],
+ [:last_logon, 'Seconds since last logon (known to us)'],
+ [:last_logoff, 'Seconds since last logoff (known to us)'],
+ [:acct_expires, 'Account expiration date in POSIX time'],
+ [:max_storage, 'Max storage'],
+ [:bad_pw_count, 'Number of times user entered incorrect password (known to us)'],
+ [:num_logons, 'Number of successful logons (known to us)'],
+ [:country_code, 'Country/region code for language choice'],
+ [:code_page, 'Code page for language choice'],
+ [:user_id, 'Relative ID (RID) of user'],
+ [:primary_group_id, 'RID of Primary Global Group for the user'],
+ [:profile, 'Path to the user\'s profile'],
+ [:home_dir_drive, 'Drive letter of user\'s home directory'],
+ ]
+
+ def run
+ server = datastore['SERVER']
+
+ print_status 'Enumerating users...'
+ users = enum_users(datastore['LEVEL'].to_i, :all, server) or return
+
+ users.each do |user_info|
+ account_name = user_info[:name]
+ print_good 'Account name: ' << account_name
+
+ USER_INFO_REPORTS.each do |key, description|
+ if user_info.has_key?(key) && user_info[key] != :unknown
+ value = user_info[key]
+
+ if value.class == String && value.empty?
+ next
+ end
+
+ print_line "#{description}: #{value.to_s}"
+ end
+ end
+
+ if user_info.has_key?(:password_expired) && user_info[:password_expired]
+ print_line 'Password has expired'
+ end
+
+ if user_info.has_key?(:flags)
+ flag_descriptions = []
+
+ user_info[:flags].each do |flag, value|
+ if value && ![:normal_account, :logon_script_executed].include?(flag)
+ flag_descriptions.push(USER_FLAG_TO_DESCRIPTION[flag])
+ end
+ end
+
+ unless flag_descriptions.empty?
+ print_line 'Notable flags: '
+ flag_descriptions.each do |description|
+ print_line ' ' << description
+ end
+ end
+ end
+
+ if user_info.has_key?(:auth_flags) && !user_info[:auth_flags].empty?
+ print_line 'Auth flags: '
+
+ user_info[:auth_flags].each do |flag, value|
+ print_line " #{flag.to_s}: #{value.to_s}"
+ end
+ end
+
+ if datastore['GROUP_INFO']
+ [:local, :global].each do |group_type|
+ groups = get_user_groups(account_name, group_type, server) or next
+
+ unless groups.empty?
+ group_names = groups.map{|g| g[:name]}.join(', ')
+ friendly_type = group_type.to_s.capitalize
+
+ print_line friendly_type << ' groups: ' << group_names
+ end
+ end
+ end
+ end
+
+ end
+end
Something went wrong with that request. Please try again.