1# 2# Copyright (c) 2009-2015, Ashok P. Nadkarni 3# All rights reserved. 4# 5# See the file LICENSE for license 6 7package require twapi_security 8 9namespace eval twapi { 10 record USER_INFO_0 {-name} 11 record USER_INFO_1 [concat [USER_INFO_0] { 12 -password -password_age -priv -home_dir -comment -flags -script_path 13 }] 14 record USER_INFO_2 [concat [USER_INFO_1] { 15 -auth_flags -full_name -usr_comment -parms 16 -workstations -last_logon -last_logoff -acct_expires -max_storage 17 -units_per_week -logon_hours -bad_pw_count -num_logons 18 -logon_server -country_code -code_page 19 }] 20 record USER_INFO_3 [concat [USER_INFO_2] { 21 -user_id -primary_group_id -profile -home_dir_drive -password_expired 22 }] 23 record USER_INFO_4 [concat [USER_INFO_2] { 24 -sid -primary_group_id -profile -home_dir_drive -password_expired 25 }] 26 27 record GROUP_INFO_0 {-name} 28 record GROUP_INFO_1 {-name -comment} 29 record GROUP_INFO_2 {-name -comment -group_id -attributes} 30 record GROUP_INFO_3 {-name -comment -sid -attributes} 31 32 record NetEnumResult {moredata hresume totalentries entries} 33 34} 35 36# Add a new user account 37proc twapi::new_user {username args} { 38 array set opts [parseargs args [list \ 39 system.arg \ 40 password.arg \ 41 comment.arg \ 42 [list priv.arg "user" [array names twapi::priv_level_map]] \ 43 home_dir.arg \ 44 script_path.arg \ 45 ] \ 46 -nulldefault] 47 48 if {$opts(priv) ne "user"} { 49 error "Option -priv is deprecated and values other than 'user' are not allowed" 50 } 51 52 # 1 -> priv level 'user'. NetUserAdd mandates this as only allowed value 53 NetUserAdd $opts(system) $username $opts(password) 1 \ 54 $opts(home_dir) $opts(comment) 0 $opts(script_path) 55 56 57 # Backward compatibility - add to 'Users' local group 58 # but only if -system is local 59 if {$opts(system) eq "" || 60 ([info exists ::env(COMPUTERNAME)] && 61 [string equal -nocase $opts(system) $::env(COMPUTERNAME)])} { 62 trap { 63 _set_user_priv_level $username $opts(priv) -system $opts(system) 64 } onerror {} { 65 # Remove the previously created user account 66 catch {delete_user $username -system $opts(system)} 67 rethrow 68 } 69 } 70} 71 72 73# Delete a user account 74proc twapi::delete_user {username args} { 75 array set opts [parseargs args {system.arg} -nulldefault] 76 77 # Remove the user from the LSA rights database. 78 _delete_rights $username $opts(system) 79 80 NetUserDel $opts(system) $username 81} 82 83 84# Define various functions to set various user account fields 85foreach twapi::_field_ { 86 {name 0} 87 {password 1003} 88 {home_dir 1006} 89 {comment 1007} 90 {script_path 1009} 91 {full_name 1011} 92 {country_code 1024} 93 {profile 1052} 94 {home_dir_drive 1053} 95} { 96 proc twapi::set_user_[lindex $::twapi::_field_ 0] {username fieldval args} " 97 array set opts \[parseargs args { 98 system.arg 99 } -nulldefault \] 100 Twapi_NetUserSetInfo [lindex $::twapi::_field_ 1] \$opts(system) \$username \$fieldval" 101} 102unset twapi::_field_ 103 104# Set account expiry time 105proc twapi::set_user_expiration {username time args} { 106 array set opts [parseargs args {system.arg} -nulldefault] 107 108 if {![string is integer -strict $time]} { 109 if {[string equal $time "never"]} { 110 set time -1 111 } else { 112 set time [clock scan $time] 113 } 114 } 115 Twapi_NetUserSetInfo 1017 $opts(system) $username $time 116} 117 118# Unlock a user account 119proc twapi::unlock_user {username args} { 120 # UF_LOCKOUT -> 0x10 121 _change_user_info_flags $username 0x10 0 {*}$args 122} 123 124# Enable a user account 125proc twapi::enable_user {username args} { 126 # UF_ACCOUNTDISABLE -> 0x2 127 _change_user_info_flags $username 0x2 0 {*}$args 128} 129 130# Disable a user account 131proc twapi::disable_user {username args} { 132 # UF_ACCOUNTDISABLE -> 0x2 133 _change_user_info_flags $username 0x2 0x2 {*}$args 134} 135 136 137# Return the specified fields for a user account 138proc twapi::get_user_account_info {account args} { 139 # Define each option, the corresponding field, and the 140 # information level at which it is returned 141 array set fields { 142 comment 1 143 password_expired 4 144 full_name 2 145 parms 2 146 units_per_week 2 147 primary_group_id 4 148 flags 1 149 logon_server 2 150 country_code 2 151 home_dir 1 152 password_age 1 153 home_dir_drive 4 154 num_logons 2 155 acct_expires 2 156 last_logon 2 157 usr_comment 2 158 bad_pw_count 2 159 code_page 2 160 logon_hours 2 161 workstations 2 162 last_logoff 2 163 name 0 164 script_path 1 165 profile 4 166 max_storage 2 167 } 168 # Left out - auth_flags 2 169 # Left out (always returned as NULL) - password {usri3_password 1} 170 # Note sid is available at level 4 as well but don't want to set 171 # level 4 just for that since we can get it by other means. Hence 172 # not listed above 173 174 array set opts [parseargs args \ 175 [concat [array names fields] sid \ 176 internet_identity \ 177 status type password_attrs \ 178 [list local_groups global_groups system.arg all]] \ 179 -nulldefault] 180 181 if {$opts(all)} { 182 set level 4 183 set opts(local_groups) 1 184 set opts(global_groups) 1 185 } else { 186 # Based on specified fields, figure out what level info to ask for 187 set level -1 188 foreach {opt optval} [array get opts] { 189 if {[info exists fields($opt)] && 190 $optval && 191 $fields($opt) > $level 192 } { 193 set level $fields($opt) 194 } 195 } 196 if {$opts(status) || $opts(type) || $opts(password_attrs)} { 197 # These fields are based on the flags field 198 if {$level < 1} { 199 set level 1 200 } 201 } 202 } 203 204 array set result [list ] 205 206 if {$level > -1} { 207 set rawdata [NetUserGetInfo $opts(system) $account $level] 208 array set data [USER_INFO_$level $rawdata] 209 210 # Extract the requested data 211 foreach opt [array names fields] { 212 if {$opts(all) || $opts($opt)} { 213 set result(-$opt) $data(-$opt) 214 } 215 } 216 if {$level == 4 && ($opts(all) || $opts(sid))} { 217 set result(-sid) $data(-sid) 218 } 219 220 # Map internal values to more friendly formats 221 if {$opts(all) || $opts(status) || $opts(type) || $opts(password_attrs)} { 222 array set result [_map_userinfo_flags $data(-flags)] 223 if {! $opts(all)} { 224 if {! $opts(status)} {unset result(-status)} 225 if {! $opts(type)} {unset result(-type)} 226 if {! $opts(password_attrs)} {unset result(-password_attrs)} 227 } 228 } 229 230 if {[info exists result(-logon_hours)]} { 231 binary scan $result(-logon_hours) b* result(-logon_hours) 232 } 233 234 foreach time_field {-acct_expires -last_logon -last_logoff} { 235 if {[info exists result($time_field)]} { 236 if {$result($time_field) == -1 || $result($time_field) == 4294967295} { 237 set result($time_field) "never" 238 } elseif {$result($time_field) == 0} { 239 set result($time_field) "unknown" 240 } 241 } 242 } 243 } 244 245 if {$opts(all) || $opts(internet_identity)} { 246 set result(-internet_identity) {} 247 if {[min_os_version 6 2]} { 248 set inet_ident [NetUserGetInfo $opts(system) $account 24] 249 if {[llength $inet_ident]} { 250 set result(-internet_identity) [twine { 251 internet_provider_name internet_principal_name sid 252 } [lrange $inet_ident 1 end]] 253 } 254 } 255 } 256 257 # The Net* calls always return structures as lists even when the struct 258 # contains only one field so we need to lpick to extract the field 259 260 if {$opts(local_groups)} { 261 set result(-local_groups) [lpick [NetEnumResult entries [NetUserGetLocalGroups $opts(system) $account 0 0]] 0] 262 } 263 264 if {$opts(global_groups)} { 265 set result(-global_groups) [lpick [NetEnumResult entries [NetUserGetGroups $opts(system) $account 0]] 0] 266 } 267 268 if {$opts(sid) && ! [info exists result(-sid)]} { 269 set result(-sid) [lookup_account_name $account -system $opts(system)] 270 } 271 272 return [array get result] 273} 274 275proc twapi::get_user_global_groups {account args} { 276 parseargs args { 277 system.arg 278 denyonly 279 all 280 } -nulldefault -maxleftover 0 -setvars 281 282 set groups {} 283 foreach elem [NetEnumResult entries [NetUserGetGroups $system [map_account_to_name $account -system $system] 1]] { 284 # 0x10 -> SE_GROUP_USE_FOR_DENY_ONLY 285 set marked_denyonly [expr {[lindex $elem 1] & 0x10}] 286 if {$all || ($denyonly && $marked_denyonly) || !($denyonly || $marked_denyonly)} { 287 lappend groups [lindex $elem 0] 288 } 289 } 290 return $groups 291} 292 293proc twapi::get_user_local_groups {account args} { 294 parseargs args { 295 system.arg 296 {recurse.bool 0} 297 } -nulldefault -maxleftover 0 -setvars 298 299 # The Net* calls always return structures as lists even when the struct 300 # contains only one field so we need to lpick to extract the field 301 return [lpick [NetEnumResult entries [NetUserGetLocalGroups $system [map_account_to_name $account -system $system] 0 $recurse]] 0] 302} 303 304proc twapi::get_user_local_groups_recursive {account args} { 305 return [get_user_local_groups $account {*}$args -recurse 1] 306} 307 308 309# Set the specified fields for a user account 310proc twapi::set_user_account_info {account args} { 311 312 # Define each option, the corresponding field, and the 313 # information level at which it is returned 314 array set opts [parseargs args { 315 {system.arg ""} 316 comment.arg 317 full_name.arg 318 country_code.arg 319 home_dir.arg 320 home_dir.arg 321 acct_expires.arg 322 name.arg 323 script_path.arg 324 profile.arg 325 }] 326 327 # TBD - rewrite this to be atomic 328 329 if {[info exists opts(comment)]} { 330 set_user_comment $account $opts(comment) -system $opts(system) 331 } 332 333 if {[info exists opts(full_name)]} { 334 set_user_full_name $account $opts(full_name) -system $opts(system) 335 } 336 337 if {[info exists opts(country_code)]} { 338 set_user_country_code $account $opts(country_code) -system $opts(system) 339 } 340 341 if {[info exists opts(home_dir)]} { 342 set_user_home_dir $account $opts(home_dir) -system $opts(system) 343 } 344 345 if {[info exists opts(home_dir_drive)]} { 346 set_user_home_dir_drive $account $opts(home_dir_drive) -system $opts(system) 347 } 348 349 if {[info exists opts(acct_expires)]} { 350 set_user_expiration $account $opts(acct_expires) -system $opts(system) 351 } 352 353 if {[info exists opts(name)]} { 354 set_user_name $account $opts(name) -system $opts(system) 355 } 356 357 if {[info exists opts(script_path)]} { 358 set_user_script_path $account $opts(script_path) -system $opts(system) 359 } 360 361 if {[info exists opts(profile)]} { 362 set_user_profile $account $opts(profile) -system $opts(system) 363 } 364} 365 366 367proc twapi::get_global_group_info {grpname args} { 368 array set opts [parseargs args { 369 {system.arg ""} 370 comment 371 name 372 members 373 sid 374 attributes 375 all 376 } -maxleftover 0] 377 378 set result {} 379 if {[expr {$opts(comment) || $opts(name) || $opts(sid) || $opts(attributes) || $opts(all)}]} { 380 # 3 -> GROUP_INFO level 3 381 lassign [NetGroupGetInfo $opts(system) $grpname 3] name comment sid attributes 382 if {$opts(all) || $opts(sid)} { 383 lappend result -sid $sid 384 } 385 if {$opts(all) || $opts(name)} { 386 lappend result -name $name 387 } 388 if {$opts(all) || $opts(comment)} { 389 lappend result -comment $comment 390 } 391 if {$opts(all) || $opts(attributes)} { 392 lappend result -attributes [map_token_group_attr $attributes] 393 } 394 } 395 396 if {$opts(all) || $opts(members)} { 397 lappend result -members [get_global_group_members $grpname -system $opts(system)] 398 } 399 400 return $result 401} 402 403# Get info about a local or global group 404proc twapi::get_local_group_info {name args} { 405 array set opts [parseargs args { 406 {system.arg ""} 407 comment 408 name 409 members 410 sid 411 all 412 } -maxleftover 0] 413 414 set result [list ] 415 if {$opts(all) || $opts(sid)} { 416 lappend result -sid [lookup_account_name $name -system $opts(system)] 417 } 418 if {$opts(all) || $opts(comment) || $opts(name)} { 419 lassign [NetLocalGroupGetInfo $opts(system) $name 1] name comment 420 if {$opts(all) || $opts(name)} { 421 lappend result -name $name 422 } 423 if {$opts(all) || $opts(comment)} { 424 lappend result -comment $comment 425 } 426 } 427 if {$opts(all) || $opts(members)} { 428 lappend result -members [get_local_group_members $name -system $opts(system)] 429 } 430 return $result 431} 432 433# Get list of users on a system 434proc twapi::get_users {args} { 435 parseargs args { 436 level.int 437 } -setvars -ignoreunknown 438 439 # TBD -allow user to specify filter 440 lappend args -filter 0 441 if {[info exists level]} { 442 lappend args -level $level -fields [USER_INFO_$level] 443 } 444 return [_net_enum_helper NetUserEnum $args] 445} 446 447proc twapi::get_global_groups {args} { 448 parseargs args { 449 level.int 450 } -setvars -ignoreunknown 451 452 # TBD - level 3 returns an ERROR_INVALID_LEVEL even though 453 # MSDN says its valid for NetGroupEnum 454 455 if {[info exists level]} { 456 lappend args -level $level -fields [GROUP_INFO_$level] 457 } 458 return [_net_enum_helper NetGroupEnum $args] 459} 460 461proc twapi::get_local_groups {args} { 462 parseargs args { 463 level.int 464 } -setvars -ignoreunknown 465 466 if {[info exists level]} { 467 lappend args -level $level -fields [dict get {0 {-name} 1 {-name -comment}} $level] 468 } 469 return [_net_enum_helper NetLocalGroupEnum $args] 470} 471 472# Create a new global group 473proc twapi::new_global_group {grpname args} { 474 array set opts [parseargs args { 475 system.arg 476 comment.arg 477 } -nulldefault] 478 479 NetGroupAdd $opts(system) $grpname $opts(comment) 480} 481 482# Create a new local group 483proc twapi::new_local_group {grpname args} { 484 array set opts [parseargs args { 485 system.arg 486 comment.arg 487 } -nulldefault] 488 489 NetLocalGroupAdd $opts(system) $grpname $opts(comment) 490} 491 492 493# Delete a global group 494proc twapi::delete_global_group {grpname args} { 495 array set opts [parseargs args {system.arg} -nulldefault] 496 497 # Remove the group from the LSA rights database. 498 _delete_rights $grpname $opts(system) 499 500 NetGroupDel $opts(system) $grpname 501} 502 503# Delete a local group 504proc twapi::delete_local_group {grpname args} { 505 array set opts [parseargs args {system.arg} -nulldefault] 506 507 # Remove the group from the LSA rights database. 508 _delete_rights $grpname $opts(system) 509 510 NetLocalGroupDel $opts(system) $grpname 511} 512 513 514# Enumerate members of a global group 515proc twapi::get_global_group_members {grpname args} { 516 parseargs args { 517 level.int 518 } -setvars -ignoreunknown 519 520 if {[info exists level]} { 521 lappend args -level $level -fields [dict! {0 {-name} 1 {-name -attributes}} $level] 522 } 523 524 lappend args -preargs [list $grpname] -namelevel 1 525 return [_net_enum_helper NetGroupGetUsers $args] 526} 527 528# Enumerate members of a local group 529proc twapi::get_local_group_members {grpname args} { 530 parseargs args { 531 level.int 532 } -setvars -ignoreunknown 533 534 if {[info exists level]} { 535 lappend args -level $level -fields [dict! {0 {-sid} 1 {-sid -sidusage -name} 2 {-sid -sidusage -domainandname} 3 {-domainandname}} $level] 536 } 537 538 lappend args -preargs [list $grpname] -namelevel 1 -namefield 2 539 return [_net_enum_helper NetLocalGroupGetMembers $args] 540} 541 542# Add a user to a global group 543proc twapi::add_user_to_global_group {grpname username args} { 544 array set opts [parseargs args {system.arg} -nulldefault] 545 546 # No error if already member of the group 547 trap { 548 NetGroupAddUser $opts(system) $grpname $username 549 } onerror {TWAPI_WIN32 1320} { 550 # Ignore 551 } 552} 553 554 555# Remove a user from a global group 556proc twapi::remove_user_from_global_group {grpname username args} { 557 array set opts [parseargs args {system.arg} -nulldefault] 558 559 trap { 560 NetGroupDelUser $opts(system) $grpname $username 561 } onerror {TWAPI_WIN32 1321} { 562 # Was not in group - ignore 563 } 564} 565 566 567# Add a user to a local group 568proc twapi::add_member_to_local_group {grpname username args} { 569 array set opts [parseargs args { 570 system.arg 571 {type.arg name} 572 } -nulldefault] 573 574 # No error if already member of the group 575 trap { 576 Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] 577 } onerror {TWAPI_WIN32 1378} { 578 # Ignore 579 } 580} 581 582proc twapi::add_members_to_local_group {grpname accts args} { 583 array set opts [parseargs args { 584 system.arg 585 {type.arg name} 586 } -nulldefault] 587 588 Twapi_NetLocalGroupMembers 0 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts 589} 590 591 592# Remove a user from a local group 593proc twapi::remove_member_from_local_group {grpname username args} { 594 array set opts [parseargs args { 595 system.arg 596 {type.arg name} 597 } -nulldefault] 598 599 trap { 600 Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] [list $username] 601 } onerror {TWAPI_WIN32 1377} { 602 # Was not in group - ignore 603 } 604} 605 606proc twapi::remove_members_from_local_group {grpname accts args} { 607 array set opts [parseargs args { 608 system.arg 609 {type.arg name} 610 } -nulldefault] 611 612 Twapi_NetLocalGroupMembers 1 $opts(system) $grpname [expr {$opts(type) eq "sid" ? 0 : 3}] $accts 613} 614 615 616# Get rights for an account 617proc twapi::get_account_rights {account args} { 618 array set opts [parseargs args { 619 {system.arg ""} 620 } -maxleftover 0] 621 622 set sid [map_account_to_sid $account -system $opts(system)] 623 624 trap { 625 set lsah [get_lsa_policy_handle -system $opts(system) -access policy_lookup_names] 626 return [Twapi_LsaEnumerateAccountRights $lsah $sid] 627 } onerror {TWAPI_WIN32 2} { 628 # No specific rights for this account 629 return [list ] 630 } finally { 631 if {[info exists lsah]} { 632 close_lsa_policy_handle $lsah 633 } 634 } 635} 636 637# Get accounts having a specific right 638proc twapi::find_accounts_with_right {right args} { 639 array set opts [parseargs args { 640 {system.arg ""} 641 name 642 } -maxleftover 0] 643 644 trap { 645 set lsah [get_lsa_policy_handle \ 646 -system $opts(system) \ 647 -access { 648 policy_lookup_names 649 policy_view_local_information 650 }] 651 set accounts [list ] 652 foreach sid [Twapi_LsaEnumerateAccountsWithUserRight $lsah $right] { 653 if {$opts(name)} { 654 if {[catch {lappend accounts [lookup_account_sid $sid -system $opts(system)]}]} { 655 # No mapping for SID - can happen if account has been 656 # deleted but LSA policy not updated accordingly 657 lappend accounts $sid 658 } 659 } else { 660 lappend accounts $sid 661 } 662 } 663 return $accounts 664 } onerror {TWAPI_WIN32 259} { 665 # No accounts have this right 666 return [list ] 667 } finally { 668 if {[info exists lsah]} { 669 close_lsa_policy_handle $lsah 670 } 671 } 672 673} 674 675# Add/remove rights to an account 676proc twapi::_modify_account_rights {operation account rights args} { 677 set switches { 678 system.arg 679 handle.arg 680 } 681 682 switch -exact -- $operation { 683 add { 684 # Nothing to do 685 } 686 remove { 687 lappend switches all 688 } 689 default { 690 error "Invalid operation '$operation' specified" 691 } 692 } 693 694 array set opts [parseargs args $switches -maxleftover 0] 695 696 if {[info exists opts(system)] && [info exists opts(handle)]} { 697 error "Options -system and -handle may not be specified together" 698 } 699 700 if {[info exists opts(handle)]} { 701 set lsah $opts(handle) 702 set sid $account 703 } else { 704 if {![info exists opts(system)]} { 705 set opts(system) "" 706 } 707 708 set sid [map_account_to_sid $account -system $opts(system)] 709 # We need to open a policy handle ourselves. First try to open 710 # with max privileges in case the account needs to be created 711 # and then retry with lower privileges if that fails 712 catch { 713 set lsah [get_lsa_policy_handle \ 714 -system $opts(system) \ 715 -access { 716 policy_lookup_names 717 policy_create_account 718 }] 719 } 720 if {![info exists lsah]} { 721 set lsah [get_lsa_policy_handle \ 722 -system $opts(system) \ 723 -access policy_lookup_names] 724 } 725 } 726 727 trap { 728 if {$operation == "add"} { 729 LsaAddAccountRights $lsah $sid $rights 730 } else { 731 LsaRemoveAccountRights $lsah $sid $opts(all) $rights 732 } 733 } finally { 734 # Close the handle if we opened it 735 if {! [info exists opts(handle)]} { 736 close_lsa_policy_handle $lsah 737 } 738 } 739} 740 741interp alias {} twapi::add_account_rights {} twapi::_modify_account_rights add 742interp alias {} twapi::remove_account_rights {} twapi::_modify_account_rights remove 743 744# Return list of logon sesionss 745proc twapi::find_logon_sessions {args} { 746 array set opts [parseargs args { 747 user.arg 748 type.arg 749 tssession.arg 750 } -maxleftover 0] 751 752 set luids [LsaEnumerateLogonSessions] 753 if {! ([info exists opts(user)] || [info exists opts(type)] || 754 [info exists opts(tssession)])} { 755 return $luids 756 } 757 758 759 # Need to get the data for each session to see if it matches 760 set result [list ] 761 if {[info exists opts(user)]} { 762 set sid [map_account_to_sid $opts(user)] 763 } 764 if {[info exists opts(type)]} { 765 set logontypes [list ] 766 foreach logontype $opts(type) { 767 lappend logontypes [_logon_session_type_code $logontype] 768 } 769 } 770 771 foreach luid $luids { 772 trap { 773 unset -nocomplain session 774 array set session [LsaGetLogonSessionData $luid] 775 776 # For the local system account, no data is returned on some 777 # platforms 778 if {[array size session] == 0} { 779 set session(Sid) S-1-5-18; # SYSTEM 780 set session(Session) 0 781 set session(LogonType) 0 782 } 783 if {[info exists opts(user)] && $session(Sid) ne $sid} { 784 continue; # User id does not match 785 } 786 787 if {[info exists opts(type)] && [lsearch -exact $logontypes $session(LogonType)] < 0} { 788 continue; # Type does not match 789 } 790 791 if {[info exists opts(tssession)] && $session(Session) != $opts(tssession)} { 792 continue; # Term server session does not match 793 } 794 795 lappend result $luid 796 797 } onerror {TWAPI_WIN32 1312} { 798 # Session no longer exists. Just skip 799 continue 800 } 801 } 802 803 return $result 804} 805 806 807# Return data for a logon session 808proc twapi::get_logon_session_info {luid args} { 809 array set opts [parseargs args { 810 all 811 authpackage 812 dnsdomain 813 logondomain 814 logonid 815 logonserver 816 logontime 817 type 818 usersid 819 user 820 tssession 821 userprincipal 822 } -maxleftover 0] 823 824 array set session [LsaGetLogonSessionData $luid] 825 826 # Some fields may be missing on Win2K 827 foreach fld {LogonServer DnsDomainName Upn} { 828 if {![info exists session($fld)]} { 829 set session($fld) "" 830 } 831 } 832 833 array set result [list ] 834 foreach {opt index} { 835 authpackage AuthenticationPackage 836 dnsdomain DnsDomainName 837 logondomain LogonDomain 838 logonid LogonId 839 logonserver LogonServer 840 logontime LogonTime 841 type LogonType 842 usersid Sid 843 user UserName 844 tssession Session 845 userprincipal Upn 846 } { 847 if {$opts(all) || $opts($opt)} { 848 set result(-$opt) $session($index) 849 } 850 } 851 852 if {[info exists result(-type)]} { 853 set result(-type) [_logon_session_type_symbol $result(-type)] 854 } 855 856 return [array get result] 857} 858 859 860 861 862# Set/reset the given bits in the usri3_flags field for a user account 863# mask indicates the mask of bits to set. values indicates the values 864# of those bits 865proc twapi::_change_user_info_flags {username mask values args} { 866 array set opts [parseargs args { 867 system.arg 868 } -nulldefault -maxleftover 0] 869 870 # Get current flags 871 set flags [USER_INFO_1 -flags [NetUserGetInfo $opts(system) $username 1]] 872 873 # Turn off mask bits and write flags back 874 set flags [expr {$flags & (~ $mask)}] 875 # Set the specified bits 876 set flags [expr {$flags | ($values & $mask)}] 877 878 # Write new flags back 879 Twapi_NetUserSetInfo 1008 $opts(system) $username $flags 880} 881 882# Returns the logon session type value for a symbol 883twapi::proc* twapi::_logon_session_type_code {type} { 884 variable _logon_session_type_map 885 # Variable that maps logon session type codes to integer values 886 # Position of each symbol gives its corresponding type value 887 # See ntsecapi.h for definitions 888 set _logon_session_type_map { 889 0 890 1 891 interactive 892 network 893 batch 894 service 895 proxy 896 unlockworkstation 897 networkclear 898 newcredentials 899 remoteinteractive 900 cachedinteractive 901 cachedremoteinteractive 902 cachedunlockworkstation 903 } 904} { 905 variable _logon_session_type_map 906 907 # Type may be an integer or a token 908 set code [lsearch -exact $_logon_session_type_map $type] 909 if {$code >= 0} { 910 return $code 911 } 912 913 if {![string is integer -strict $type]} { 914 badargs! "Invalid logon session type '$type' specified" 3 915 } 916 return $type 917} 918 919# Returns the logon session type symbol for an integer value 920proc twapi::_logon_session_type_symbol {code} { 921 variable _logon_session_type_map 922 _logon_session_type_code interactive; # Just to init _logon_session_type_map 923 set symbol [lindex $_logon_session_type_map $code] 924 if {$symbol eq ""} { 925 return $code 926 } else { 927 return $symbol 928 } 929} 930 931proc twapi::_set_user_priv_level {username priv_level args} { 932 933 array set opts [parseargs args {system.arg} -nulldefault] 934 935 if {0} { 936 # FOr some reason NetUserSetInfo cannot change priv level 937 # Tried it separately with a simple C program. So this code 938 # is commented out and we use group membership to achieve 939 # the desired result 940 # Note: - latest MSDN confirms above 941 if {![info exists twapi::priv_level_map($priv_level)]} { 942 error "Invalid privilege level value '$priv_level' specified. Must be one of [join [array names twapi::priv_level_map] ,]" 943 } 944 set priv $twapi::priv_level_map($priv_level) 945 946 Twapi_NetUserSetInfo_priv $opts(system) $username $priv 947 } else { 948 # Don't hardcode group names - reverse map SID's instead for 949 # non-English systems. Also note that since 950 # we might be lowering privilege level, we have to also 951 # remove from higher privileged groups 952 953 switch -exact -- $priv_level { 954 guest { 955 # administrators users 956 set outgroups {S-1-5-32-544 S-1-5-32-545} 957 # guests 958 set ingroup S-1-5-32-546 959 } 960 user { 961 # administrators 962 set outgroups {S-1-5-32-544} 963 # users 964 set ingroup S-1-5-32-545 965 } 966 admin { 967 set outgroups {} 968 set ingroup S-1-5-32-544 969 } 970 default {error "Invalid privilege level '$priv_level'. Must be one of 'guest', 'user' or 'admin'"} 971 } 972 # Remove from higher priv groups 973 foreach outgroup $outgroups { 974 # Get the potentially localized name of the group 975 set group [lookup_account_sid $outgroup -system $opts(system)] 976 # Catch since may not be member of that group 977 catch {remove_member_from_local_group $group $username -system $opts(system)} 978 } 979 980 # Get the potentially localized name of the group to be added 981 set group [lookup_account_sid $ingroup -system $opts(system)] 982 add_member_to_local_group $group $username -system $opts(system) 983 } 984} 985 986proc twapi::_map_userinfo_flags {flags} { 987 # UF_LOCKOUT -> 0x10, UF_ACCOUNTDISABLE -> 0x2 988 if {$flags & 0x2} { 989 set status disabled 990 } elseif {$flags & 0x10} { 991 set status locked 992 } else { 993 set status enabled 994 } 995 996 #define UF_TEMP_DUPLICATE_ACCOUNT 0x0100 997 #define UF_NORMAL_ACCOUNT 0x0200 998 #define UF_INTERDOMAIN_TRUST_ACCOUNT 0x0800 999 #define UF_WORKSTATION_TRUST_ACCOUNT 0x1000 1000 #define UF_SERVER_TRUST_ACCOUNT 0x2000 1001 if {$flags & 0x0200} { 1002 set type normal 1003 } elseif {$flags & 0x0100} { 1004 set type duplicate 1005 } elseif {$flags & 0x0800} { 1006 set type interdomain_trust 1007 } elseif {$flags & 0x1000} { 1008 set type workstation_trust 1009 } elseif {$flags & 0x2000} { 1010 set type server_trust 1011 } else { 1012 set type unknown 1013 } 1014 1015 set pw {} 1016 #define UF_PASSWD_NOTREQD 0x0020 1017 if {$flags & 0x0020} { 1018 lappend pw not_required 1019 } 1020 #define UF_PASSWD_CANT_CHANGE 0x0040 1021 if {$flags & 0x0040} { 1022 lappend pw cannot_change 1023 } 1024 #define UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x0080 1025 if {$flags & 0x0080} { 1026 lappend pw encrypted_text_allowed 1027 } 1028 #define UF_DONT_EXPIRE_PASSWD 0x10000 1029 if {$flags & 0x10000} { 1030 lappend pw no_expiry 1031 } 1032 #define UF_SMARTCARD_REQUIRED 0x40000 1033 if {$flags & 0x40000} { 1034 lappend pw smartcard_required 1035 } 1036 #define UF_PASSWORD_EXPIRED 0x800000 1037 if {$flags & 0x800000} { 1038 lappend pw expired 1039 } 1040 1041 return [list -status $status -type $type -password_attrs $pw] 1042} 1043 1044twapi::proc* twapi::_define_user_modals {} { 1045 struct _USER_MODALS_INFO_0 { 1046 DWORD min_passwd_len; 1047 DWORD max_passwd_age; 1048 DWORD min_passwd_age; 1049 DWORD force_logoff; 1050 DWORD password_hist_len; 1051 } 1052 struct _USER_MODALS_INFO_1 { 1053 DWORD role; 1054 LPWSTR primary; 1055 } 1056 struct _USER_MODALS_INFO_2 { 1057 LPWSTR domain_name; 1058 PSID domain_id; 1059 } 1060 struct _USER_MODALS_INFO_3 { 1061 DWORD lockout_duration; 1062 DWORD lockout_observation_window; 1063 DWORD lockout_threshold; 1064 } 1065 struct _USER_MODALS_INFO_1001 { 1066 DWORD min_passwd_len; 1067 } 1068 struct _USER_MODALS_INFO_1002 { 1069 DWORD max_passwd_age; 1070 } 1071 struct _USER_MODALS_INFO_1003 { 1072 DWORD min_passwd_age; 1073 } 1074 struct _USER_MODALS_INFO_1004 { 1075 DWORD force_logoff; 1076 } 1077 struct _USER_MODALS_INFO_1005 { 1078 DWORD password_hist_len; 1079 } 1080 struct _USER_MODALS_INFO_1006 { 1081 DWORD role; 1082 } 1083 struct _USER_MODALS_INFO_1007 { 1084 LPWSTR primary; 1085 } 1086} { 1087} 1088 1089twapi::proc* twapi::get_password_policy {{server_name ""}} { 1090 _define_user_modals 1091} { 1092 set result [NetUserModalsGet $server_name 0 [_USER_MODALS_INFO_0]] 1093 dict with result { 1094 if {$force_logoff == 4294967295 || $force_logoff == -1} { 1095 set force_logoff never 1096 } 1097 if {$max_passwd_age == 4294967295 || $max_passwd_age == -1} { 1098 set max_passwd_age none 1099 } 1100 } 1101 return $result 1102} 1103 1104# TBD - doc & test 1105twapi::proc* twapi::get_system_role {{server_name ""}} { 1106 _define_user_modals 1107} { 1108 set result [NetUserModalsGet $server_name 1 [_USER_MODALS_INFO_1]] 1109 dict set result role [dict* { 1110 0 standalone 1 member 2 backup 3 primary 1111 } [dict get $result role]] 1112 return $result 1113} 1114 1115# TBD - doc & test 1116twapi::proc* twapi::get_system_domain {{server_name ""}} { 1117 _define_user_modals 1118} { 1119 return [NetUserModalsGet $server_name 2 [_USER_MODALS_INFO_2]] 1120} 1121 1122twapi::proc* twapi::get_lockout_policy {{server_name ""}} { 1123 _define_user_modals 1124} { 1125 return [NetUserModalsGet $server_name 3 [_USER_MODALS_INFO_3]] 1126} 1127 1128# TBD - doc & test 1129twapi::proc* twapi::set_password_policy {name val {server_name ""}} { 1130 _define_user_modals 1131} { 1132 switch -exact $name { 1133 min_passwd_len { 1134 NetUserModalsSet $server_name 1001 [_USER_MODALS_INFO_1001 $val] 1135 } 1136 max_passwd_age { 1137 if {$val eq "none"} { 1138 set val 4294967295 1139 } 1140 NetUserModalsSet $server_name 1002 [_USER_MODALS_INFO_1002 $val] 1141 } 1142 min_passwd_age { 1143 NetUserModalsSet $server_name 1003 [_USER_MODALS_INFO_1003 $val] 1144 } 1145 force_logoff { 1146 if {$val eq "never"} { 1147 set val 4294967295 1148 } 1149 NetUserModalsSet $server_name 1004 [_USER_MODALS_INFO_1004 $val] 1150 } 1151 password_hist_len { 1152 NetUserModalsSet $server_name 1005 [_USER_MODALS_INFO_1005 $val] 1153 } 1154 } 1155} 1156 1157# TBD - doc & test 1158twapi::proc* twapi::set_lockout_policy {duration observe_window threshold {server_name ""}} { 1159 _define_user_modals 1160} { 1161 NetUserModalsSet $server_name 3 [_USER_MODALS_INFO_3 $duration $observe_window $threshold] 1162} 1163