1#!/usr/bin/perl -w 2 3# $Id: smbldap-useradd,v 1.25 2005/01/29 15:00:54 jtournier Exp $ 4# 5# This code was developped by IDEALX (http://IDEALX.org/) and 6# contributors (their names can be found in the CONTRIBUTORS file). 7# 8# Copyright (C) 2002 IDEALX 9# 10# This program is free software; you can redistribute it and/or 11# modify it under the terms of the GNU General Public License 12# as published by the Free Software Foundation; either version 2 13# of the License, or (at your option) any later version. 14# 15# This program is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU General Public License for more details. 19# 20# You should have received a copy of the GNU General Public License 21# along with this program; if not, write to the Free Software 22# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 23# USA. 24 25# Purpose of smbldap-useradd : user (posix,shadow,samba) add 26 27use strict; 28 29use FindBin; 30use FindBin qw($RealBin); 31use lib "$RealBin/"; 32use smbldap_tools; 33use Crypt::SmbHash; 34##################### 35 36 37use Getopt::Std; 38my %Options; 39 40my $ok = getopts('o:anmwiPG:u:g:d:s:c:k:A:B:C:D:E:F:H:M:N:S:T:?', \%Options); 41 42if ( (!$ok) || (@ARGV < 1) || ($Options{'?'}) ) { 43 print_banner; 44 print "Usage: $0 [-awmugdsckABCDEFGHMNPST?] username\n"; 45 print " -o add the user in the organazional unit (relative to the user suffix)\n"; 46 print " -a is a Windows User (otherwise, Posix stuff only)\n"; 47 print " -w is a Windows Workstation (otherwise, Posix stuff only)\n"; 48 print " -i is a trust account (Windows Workstation)\n"; 49 print " -u uid\n"; 50 print " -g gid\n"; 51 print " -G supplementary comma-separated groups\n"; 52 print " -n do not create a group\n"; 53 print " -d home\n"; 54 print " -s shell\n"; 55 print " -c gecos\n"; 56 print " -m creates home directory and copies /etc/skel\n"; 57 print " -k skeleton dir (with -m)\n"; 58 print " -P ends by invoking smbldap-passwd\n"; 59 print " -A can change password ? 0 if no, 1 if yes\n"; 60 print " -B must change password ? 0 if no, 1 if yes\n"; 61 print " -C sambaHomePath (SMB home share, like '\\\\PDC-SRV\\homes')\n"; 62 print " -D sambaHomeDrive (letter associated with home share, like 'H:')\n"; 63 print " -E sambaLogonScript (DOS script to execute on login)\n"; 64 print " -F sambaProfilePath (profile directory, like '\\\\PDC-SRV\\profiles\\foo')\n"; 65 print " -H sambaAcctFlags (samba account control bits like '[NDHTUMWSLKI]')\n"; 66 print " -N canonical name\n"; 67 print " -S surname\n"; 68 print " -M local mailAddress (comma seperated)\n"; 69 print " -T mailToAddress (forward address) (comma seperated)\n"; 70 print " -? show this help message\n"; 71 exit (1); 72} 73 74my $ldap_master=connect_ldap_master(); 75 76 77# cause problems when dealing with getpwuid because of the 78# negative ttl and ldap modification 79my $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1"; 80 81if ($nscd_status == 0) { 82 system "/etc/init.d/nscd stop > /dev/null 2>&1"; 83} 84 85 86# Read only first @ARGV 87my $userName = $ARGV[0]; 88 89# For computers account, add a trailing dollar if missing 90if (defined($Options{'w'})) { 91 if ($userName =~ /[^\$]$/s) { 92 $userName .= "\$"; 93 } 94} 95 96# untaint $userName (can finish with one or two $) 97if ($userName =~ /^([\w -.]+\$?)$/) { 98 $userName = $1; 99} else { 100 print "$0: illegal username\n"; 101 exit (1); 102} 103 104# user must not exist in LDAP (should it be nss-wide ?) 105my ($rc, $dn) = get_user_dn2($userName); 106if ($rc and defined($dn)) { 107 print "$0: user $userName exists\n"; 108 exit (9); 109} elsif (!$rc) { 110 print "$0: error in get_user_dn2\n"; 111 exit(10); 112} 113 114# Read options 115# we create the user in the specified ou (relative to the users suffix) 116my $user_ou=$Options{'o'}; 117if (defined $user_ou) { 118 $config{usersdn}="$user_ou,$config{usersdn}"; 119} 120 121my $userUidNumber = $Options{'u'}; 122if (!defined($userUidNumber)) { 123 $userUidNumber=get_next_id($config{usersdn},"uidNumber"); 124} elsif (getpwuid($userUidNumber)) { 125 die "Uid already exists.\n"; 126} 127 128if ($nscd_status == 0) { 129 system "/etc/init.d/nscd start > /dev/null 2>&1"; 130} 131 132my $createGroup = 0; 133my $userGidNumber = $Options{'g'}; 134# gid not specified ? 135if (!defined($userGidNumber)) { 136 # windows machine => $config{defaultComputerGid} 137 if (defined($Options{'w'})) { 138 $userGidNumber = $config{defaultComputerGid}; 139 # } elsif (!defined($Options{'n'})) { 140 # create new group (redhat style) 141 # find first unused gid starting from $config{GID_START} 142 # while (defined(getgrgid($config{GID_START}))) { 143 # $config{GID_START}++; 144 # } 145 # $userGidNumber = $config{GID_START}; 146 147 # $createGroup = 1; 148 149 } else { 150 # user will have gid = $config{defaultUserGid} 151 $userGidNumber = $config{defaultUserGid}; 152 } 153} else { 154 my $gid; 155 if (($gid = parse_group($userGidNumber)) < 0) { 156 print "$0: unknown group $userGidNumber\n"; 157 exit (6); 158 } 159 $userGidNumber = $gid; 160} 161 162my $group_entry; 163my $userGroupSID; 164my $userRid; 165my $user_sid; 166if (defined $Options{'a'} or defined $Options{'i'}) { 167 # as grouprid we use the value of the sambaSID attribute for 168 # group of gidNumber=$userGidNumber 169 $group_entry = read_group_entry_gid($userGidNumber); 170 $userGroupSID = $group_entry->get_value('sambaSID'); 171 unless ($userGroupSID) { 172 print "Error: SID not set for unix group $userGidNumber\n"; 173 print "check if your unix group is mapped to an NT group\n"; 174 exit (7); 175 } 176 177 # as rid we use 2 * uid + 1000 178 $userRid = 2 * $userUidNumber + 1000; 179 # let's test if this SID already exist 180 $user_sid="$config{SID}-$userRid"; 181 my $test_exist_sid=does_sid_exist($user_sid,$config{usersdn}); 182 if ($test_exist_sid->count == 1) { 183 print "User SID already owned by\n"; 184 # there should not exist more than one entry, but ... 185 foreach my $entry ($test_exist_sid->all_entries) { 186 my $dn= $entry->dn; 187 chomp($dn); 188 print "$dn\n"; 189 } 190 exit(7); 191 } 192} 193 194my $userHomeDirectory; 195my ($userCN, $userSN); 196my @userMailLocal; 197my @userMailTo; 198my $tmp; 199if (!defined($userHomeDirectory = $Options{'d'})) { 200 $userHomeDirectory = &subst_user($config{userHome}, $userName); 201} 202$userHomeDirectory=~s/\/\//\//; 203$config{userLoginShell} = $tmp if (defined($tmp = $Options{'s'})); 204$config{userGecos} = $tmp if (defined($tmp = $Options{'c'})); 205$config{skeletonDir} = $tmp if (defined($tmp = $Options{'k'})); 206$userCN = ($Options{'c'} || $userName); 207$userCN = $tmp if (defined($tmp = $Options{'N'})); 208$userSN = $userName; 209$userSN = $tmp if (defined($tmp = $Options{'S'})); 210@userMailLocal = &split_arg_comma($Options{'M'}); 211@userMailTo = &split_arg_comma($Options{'T'}); 212 213######################## 214 215# MACHINE ACCOUNT 216if (defined($Options{'w'}) or defined($Options{'i'})) { 217 218 #print "About to create machine $userName:\n"; 219 220 if (!add_posix_machine ($userName, $userUidNumber, $userGidNumber)) { 221 die "$0: error while adding posix account\n"; 222 } 223 224 if (defined($Options{'i'})) { 225 # For machine trust account 226 # Objectclass sambaSAMAccount must be added now ! 227 my $pass; 228 my $pass2; 229 230 system "stty -echo"; 231 print "New password : "; 232 chomp($pass=<STDIN>); 233 print "\n"; 234 system "stty echo"; 235 236 system "stty -echo"; 237 print "Retype new password : "; 238 chomp($pass2=<STDIN>); 239 print "\n"; 240 system "stty echo"; 241 242 if ($pass ne $pass2) { 243 print "New passwords don't match!\n"; 244 exit (10); 245 } 246 my ($lmpassword,$ntpassword) = ntlmgen $pass; 247 my $date=time; 248 my $modify = $ldap_master->modify ( "uid=$userName,$config{computersdn}", 249 changes => [ 250 replace => [objectClass => ['inetOrgPerson', 'posixAccount', 'sambaSAMAccount']], 251 add => [sambaLogonTime => '0'], 252 add => [sambaLogoffTime => '2147483647'], 253 add => [sambaKickoffTime => '2147483647'], 254 add => [sambaPwdCanChange => '0'], 255 add => [sambaPwdMustChange => '2147483647'], 256 add => [sambaPwdLastSet => "$date"], 257 add => [sambaAcctFlags => '[I ]'], 258 add => [sambaLMPassword => "$lmpassword"], 259 add => [sambaNTPassword => "$ntpassword"], 260 add => [sambaSID => "$user_sid"], 261 add => [sambaPrimaryGroupSID => "$config{SID}-515"] 262 ] 263 ); 264 265 $modify->code && die "failed to add entry: ", $modify->error ; 266 } 267 268 $ldap_master->unbind; 269 exit 0; 270} 271 272# USER ACCOUNT 273# add posix account first 274 275my $add = $ldap_master->add ("uid=$userName,$config{usersdn}", 276 attr => [ 277 'objectclass' => ['top','inetOrgPerson','posixAccount','shadowAccount'], 278 'cn' => "$userCN", 279 'sn' => "$userSN", 280 'uid' => "$userName", 281 'uidNumber' => "$userUidNumber", 282 'gidNumber' => "$userGidNumber", 283 'homeDirectory' => "$userHomeDirectory", 284 'loginShell' => "$config{userLoginShell}", 285 'gecos' => "$config{userGecos}", 286 'description' => "$config{userGecos}", 287 'userPassword' => "{crypt}x" 288 ] 289 ); 290 291$add->code && warn "failed to add entry: ", $add->error ; 292 293 294#if ($createGroup) { 295# group_add($userName, $userGidNumber); 296#} 297 298group_add_user($userGidNumber, $userName); 299 300my $grouplist; 301# adds to supplementary groups 302if (defined($grouplist = $Options{'G'})) { 303 add_grouplist_user($grouplist, $userName); 304} 305 306# If user was created successfully then we should create his/her home dir 307if (defined($tmp = $Options{'m'})) { 308 unless ( $userName =~ /\$$/ ) { 309 if ( !(-e $userHomeDirectory) ) { 310 system "mkdir $userHomeDirectory 2>/dev/null"; 311 system "cp -a $config{skeletonDir}/.[a-z,A-Z]* $config{skeletonDir}/* $userHomeDirectory 2>/dev/null"; 312 system "chown -R $userUidNumber:$userGidNumber $userHomeDirectory 2>/dev/null"; 313 system "chmod 700 $userHomeDirectory 2>/dev/null"; 314 } 315 } 316} 317 318# we start to defined mail adresses if option M or T is given in option 319my @adds; 320if (@userMailLocal) { 321 my @mail; 322 foreach my $m (@userMailLocal) { 323 my $domain = $config{mailDomain}; 324 if ($m =~ /^(.+)@/) { 325 push (@mail, $m); 326 # mailLocalAddress contains only the first part 327 $m= $1; 328 } else { 329 push(@mail, $m.($domain ? '@'.$domain : '')); 330 } 331 } 332 push(@adds, 'mailLocalAddress' => [ @userMailLocal ]); 333 push(@adds, 'mail' => [ @mail ]); 334} 335if (@userMailTo) { 336 push(@adds, 'mailRoutingAddress' => [ @userMailTo ]); 337} 338if (@userMailLocal || @userMailTo) { 339 push(@adds, 'objectClass' => 'inetLocalMailRecipient'); 340} 341 342# Add Samba user infos 343if (defined($Options{'a'})) { 344 if (!$config{with_smbpasswd}) { 345 346 my $winmagic = 2147483647; 347 my $valpwdcanchange = 0; 348 my $valpwdmustchange = $winmagic; 349 my $valpwdlastset = 0; 350 my $valacctflags = "[UX]"; 351 352 if (defined($tmp = $Options{'A'})) { 353 if ($tmp != 0) { 354 $valpwdcanchange = "0"; 355 } else { 356 $valpwdcanchange = "$winmagic"; 357 } 358 } 359 360 if (defined($tmp = $Options{'B'})) { 361 if ($tmp != 0) { 362 $valpwdmustchange = "0"; 363 # To force a user to change his password: 364 # . the attribut sambaPwdLastSet must be != 0 365 # . the attribut sambaAcctFlags must not match the 'X' flag 366 $valpwdlastset=$winmagic; 367 $valacctflags = "[U]"; 368 } else { 369 $valpwdmustchange = "$winmagic"; 370 } 371 } 372 373 if (defined($tmp = $Options{'H'})) { 374 $valacctflags = "$tmp"; 375 } 376 377 378 my $modify = $ldap_master->modify ( "uid=$userName,$config{usersdn}", 379 changes => [ 380 add => [objectClass => 'sambaSAMAccount'], 381 add => [sambaPwdLastSet => "$valpwdlastset"], 382 add => [sambaLogonTime => '0'], 383 add => [sambaLogoffTime => '2147483647'], 384 add => [sambaKickoffTime => '2147483647'], 385 add => [sambaPwdCanChange => "$valpwdcanchange"], 386 add => [sambaPwdMustChange => "$valpwdmustchange"], 387 add => [displayName => "$config{userGecos}"], 388 add => [sambaAcctFlags => "$valacctflags"], 389 add => [sambaSID => "$config{SID}-$userRid"] 390 ] 391 ); 392 393 $modify->code && die "failed to add entry: ", $modify->error ; 394 395 } else { 396 my $FILE="|smbpasswd -s -a $userName >/dev/null" ; 397 open (FILE, $FILE) || die "$!\n"; 398 print FILE <<EOF; 399x 400x 401EOF 402 ; 403 close FILE; 404 if ($?) { 405 print "$0: error adding samba account\n"; 406 exit (10); 407 } 408 } # with_smbpasswd 409 410 $tmp = defined($Options{'E'}) ? $Options{'E'} : $config{userScript}; 411 my $valscriptpath = &subst_user($tmp, $userName); 412 413 $tmp = defined($Options{'C'}) ? $Options{'C'} : $config{userSmbHome}; 414 my $valsmbhome = &subst_user($tmp, $userName); 415 416 my $valhomedrive = defined($Options{'D'}) ? $Options{'D'} : $config{userHomeDrive}; 417 # if the letter is given without the ":" symbol, we add it 418 $valhomedrive .= ':' if ($valhomedrive && $valhomedrive !~ /:$/); 419 420 $tmp = defined($Options{'F'}) ? $Options{'F'} : $config{userProfile}; 421 my $valprofilepath = &subst_user($tmp, $userName); 422 423 if ($valhomedrive) { 424 push(@adds, 'sambaHomeDrive' => $valhomedrive); 425 } 426 if ($valsmbhome) { 427 push(@adds, 'sambaHomePath' => $valsmbhome); 428 } 429 430 if ($valprofilepath) { 431 push(@adds, 'sambaProfilePath' => $valprofilepath); 432 } 433 if ($valscriptpath) { 434 push(@adds, 'sambaLogonScript' => $valscriptpath); 435 } 436 push(@adds, 'sambaPrimaryGroupSID' => $userGroupSID); 437 push(@adds, 'sambaLMPassword' => "XXX"); 438 push(@adds, 'sambaNTPassword' => "XXX"); 439 my $modify = $ldap_master->modify ( "uid=$userName,$config{usersdn}", 440 add => { 441 @adds 442 } 443 ); 444 445 $modify->code && die "failed to add entry: ", $modify->error ; 446} 447 448$ldap_master->unbind; # take down session 449 450 451if (defined($Options{'P'})) { 452 exec "$RealBin/smbldap-passwd $userName" 453} 454 455exit 0; 456 457######################################## 458 459=head1 NAME 460 461smbldap-useradd - Create a new user 462 463=head1 SYNOPSIS 464 465smbldap-useradd [-o user_ou] [-c comment] [-d home_dir] [-g initial_group] [-G group[,...]] [-m [-k skeleton_dir]] [-s shell] [-u uid [ -o]] [-P] [-A canchange] [-B mustchange] [-C smbhome] [-D homedrive] [-E scriptpath] [-F profilepath] [-H acctflags] login 466 467=head1 DESCRIPTION 468 469Creating New Users 470 The smbldap-useradd command creates a new user account using the values specified on the command line and the default values from the system and from the configuration files (in /etc/smbldap-tools directory). 471 472For Samba users, rid is '2*uidNumber+1000', and sambaPrimaryGroupSID is '$SID-2*gidNumber+1001', where $SID is the domain SID. Thus you may want to use : 473 $ smbldap-useradd -a -g "Domain Admins" -u 500 Administrator 474 to create an domain administrator account (admin rid is 0x1F4 = 500 and grouprid is 0x200 = 512). 475 476Without any option, the account created will be an Unix (Posix) account. The following options may be used to add information: 477 478-o 479The user's account will be created in the specified organazional unit. It is relative to the user suffix dn ($usersdn) defined in the configuration file. 480 481-a 482The user will have a Samba account (and Unix). 483 484-w 485 Creates an account for a Samba machine (Workstation), so that it can join a sambaDomainName. 486 487-i 488 Creates an interdomain trust account (machine Workstation). A password will be asked for the trust account. 489 490-c "comment" 491 The new user's comment field (gecos). 492 493-d home_dir 494 The new user will be created using home_dir as the value for the user's login directory. The default is to append the login name to userHomePrefix (defined in the configuration file) and use that as the login directory name. 495 496-g initial_group 497 The group name or number of the user's initial login group. The group name must exist. A group number must refer to an already existing group. The default group number is defined in the configuration file (defaultUserGid="513"). 498 499-G group,[...] 500 A list of supplementary groups which the user is also a member of. Each group is separated to the next by a comma, with no intervening whitespace. The groups are subject to the same restrictions as the group given with the -g option. The default is for the user to belong only to the initial group. 501 502-m 503The user's home directory will be created if it does not exist. The files contained in skeletonDir will be copied to the home directory if the -k option is used, otherwise the files contained in /etc/skel will be used instead. Any directories contained in skeletonDir or /etc/skel will be created in the user's home directory as well. The -k option is only valid in conjunction with the -m option. The default is to not create the directory and to not copy any files. 504 505-s shell 506 The name of the user's login shell. The default is to leave this field blank, which causes the system to select the default login shell. 507 508-u uid 509 The numerical value of the user's ID. This value must be unique, unless the -o option is used. The value must be nonnegative. The default is to use the smallest ID value greater than 1000 and greater than every other user. 510 511-P 512 ends by invoking smbldap-passwd 513 514-A 515 can change password ? 0 if no, 1 if yes 516 517-B 518 must change password ? 0 if no, 1 if yes 519 520-C sambaHomePath 521 SMB home share, like '\\\\PDC-SRV\\homes' 522 523-D sambaHomeDrive 524 letter associated with home share, like 'H:' 525 526-E sambaLogonScript 527 relative to the [netlogon] share (DOS script to execute on login, like 'foo.bat' 528 529-F sambaProfilePath 530 profile directory, like '\\\\PDC-SRV\\profiles\\foo' 531 532-H sambaAcctFlags 533 spaces and trailing bracket are ignored (samba account control bits like '[NDHTUMWSLKI]' 534 535-M local mail aliases (multiple addresses are seperated by spaces) 536 537-N canonical name 538 defaults to gecos or username, if gecos not set 539 540-S surname 541 defaults to username 542 543-T mailToAddress (forward address) (multiple addresses are seperated by spaces) 544 545-n do not print banner message 546 547=head1 SEE ALSO 548 549 useradd(1) 550 551=cut 552 553#' 554