1#!/usr/bin/perl -w 2 3# $Id: smbldap-usermod,v 1.11 2005/01/08 12:04:45 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) 2001-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-usermod : user (posix,shadow,samba) modification 26 27use strict; 28use FindBin; 29use FindBin qw($RealBin); 30use lib "$RealBin/"; 31use smbldap_tools; 32 33##################### 34 35use Getopt::Std; 36my %Options; 37my $nscd_status; 38 39my $ok = getopts('A:B:C:D:E:F:H:IJM:N:S:PT:ame:f:u:g:G:d:l:r:s:c:ok:?h', \%Options); 40if ( (!$ok) || (@ARGV < 1) || ($Options{'?'}) || ($Options{'h'}) ) { 41 print_banner; 42 print "Usage: $0 [-awmugdsckABCDEFGHIPSMT?h] username\n"; 43 print "Available options are:\n"; 44 print " -c gecos\n"; 45 print " -d home directory\n"; 46 #print " -m move home directory\n"; 47 #print " -f inactive days\n"; 48 print " -r new username (cn, sn and dn are updated)\n"; 49 print " -u uid\n"; 50 print " -o uid can be non unique\n"; 51 print " -g gid\n"; 52 print " -G supplementary groups (comma separated)\n"; 53 print " -s shell\n"; 54 print " -N canonical name\n"; 55 print " -S surname\n"; 56 print " -P ends by invoking smbldap-passwd\n"; 57 print " For samba users:\n"; 58 print " -a add sambaSAMAccount objectclass\n"; 59 print " -e expire date (\"YYYY-MM-DD HH:MM:SS\")\n"; 60 print " -A can change password ? 0 if no, 1 if yes\n"; 61 print " -B must change password ? 0 if no, 1 if yes\n"; 62 print " -C sambaHomePath (SMB home share, like '\\\\PDC-SRV\\homes')\n"; 63 print " -D sambaHomeDrive (letter associated with home share, like 'H:')\n"; 64 print " -E sambaLogonScript (DOS script to execute on login)\n"; 65 print " -F sambaProfilePath (profile directory, like '\\\\PDC-SRV\\profiles\\foo')\n"; 66 print " -H sambaAcctFlags (samba account control bits like '[NDHTUMWSLKI]')\n"; 67 print " -I disable an user. Can't be used with -H or -J\n"; 68 print " -J enable an user. Can't be used with -H or -I\n"; 69 print " -M mailAddresses (comma seperated)\n"; 70 print " -T mailToAddress (forward address) (comma seperated)\n"; 71 print " -?|-h show this help message\n"; 72 exit (1); 73} 74 75if ($< != 0) { 76 print "You must be root to modify an user\n"; 77 exit (1); 78} 79# Read only first @ARGV 80my $user = $ARGV[0]; 81 82# Let's connect to the directory first 83my $ldap_master=connect_ldap_master(); 84 85# Read user data 86my $user_entry = read_user_entry($user); 87if (!defined($user_entry)) { 88 print "$0: user $user doesn't exist\n"; 89 exit (1); 90} 91 92my $samba = 0; 93if (grep ($_ =~ /^sambaSamAccount$/i, $user_entry->get_value('objectClass'))) { 94 $samba = 1; 95} 96 97# get the dn of the user 98my $dn= $user_entry->dn(); 99 100my $tmp; 101my @mods; 102my @dels; 103if (defined($tmp = $Options{'a'})) { 104 # Let's connect to the directory first 105 my $winmagic = 2147483647; 106 my $valpwdcanchange = 0; 107 my $valpwdmustchange = $winmagic; 108 my $valpwdlastset = 0; 109 my $valacctflags = "[UX]"; 110 my $user_entry=read_user_entry($user); 111 my $uidNumber = $user_entry->get_value('uidNumber'); 112 my $userRid = 2 * $uidNumber + 1000; 113 # apply changes 114 my $modify = $ldap_master->modify ( "$dn", 115 changes => [ 116 add => [objectClass => 'sambaSAMAccount'], 117 add => [sambaPwdLastSet => "$valpwdlastset"], 118 add => [sambaLogonTime => '0'], 119 add => [sambaLogoffTime => '2147483647'], 120 add => [sambaKickoffTime => '2147483647'], 121 add => [sambaPwdCanChange => "$valpwdcanchange"], 122 add => [sambaPwdMustChange => "$valpwdmustchange"], 123 add => [displayName => "$config{userGecos}"], 124 add => [sambaSID=> "$config{SID}-$userRid"], 125 add => [sambaAcctFlags => "$valacctflags"], 126 ] 127 ); 128 $modify->code && warn "failed to modify entry: ", $modify->error ; 129} 130 131# Process options 132my $changed_uid; 133my $_userUidNumber; 134my $_userRid; 135if (defined($tmp = $Options{'u'})) { 136 if (defined($Options{'o'})) { 137 $nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1"; 138 139 if ($nscd_status == 0) { 140 system "/etc/init.d/nscd stop > /dev/null 2>&1"; 141 } 142 143 if (getpwuid($tmp)) { 144 if ($nscd_status == 0) { 145 system "/etc/init.d/nscd start > /dev/null 2>&1"; 146 } 147 148 print "$0: uid number $tmp exists\n"; 149 exit (6); 150 } 151 if ($nscd_status == 0) { 152 system "/etc/init.d/nscd start > /dev/null 2>&1"; 153 } 154 155 } 156 push(@mods, 'uidNumber', $tmp); 157 $_userUidNumber = $tmp; 158 if ($samba) { 159 # as rid we use 2 * uid + 1000 160 my $_userRid = 2 * $_userUidNumber + 1000; 161 if (defined($Options{'x'})) { 162 $_userRid= sprint("%x", $_userRid); 163 } 164 push(@mods, 'sambaSID', $config{SID}.'-'.$_userRid); 165 } 166 $changed_uid = 1; 167} 168 169my $changed_gid; 170my $_userGidNumber; 171my $_userGroupSID; 172if (defined($tmp = $Options{'g'})) { 173 $_userGidNumber = parse_group($tmp); 174 if ($_userGidNumber < 0) { 175 print "$0: group $tmp doesn't exist\n"; 176 exit (6); 177 } 178 push(@mods, 'gidNumber', $_userGidNumber); 179 if ($samba) { 180 # as grouprid we use the sambaSID attribute's value of the group 181 my $group_entry = read_group_entry_gid($_userGidNumber); 182 my $_userGroupSID = $group_entry->get_value('sambaSID'); 183 unless ($_userGroupSID) { 184 print "Error: sambaPrimaryGroupSid could not be set (sambaSID for group $_userGidNumber does not exist\n"; 185 exit (7); 186 } 187 push(@mods, 'sambaPrimaryGroupSid', $_userGroupSID); 188 } 189 $changed_gid = 1; 190} 191 192if (defined($tmp = $Options{'s'})) { 193 push(@mods, 'loginShell' => $tmp); 194} 195 196 197if (defined($tmp = $Options{'c'})) { 198 push(@mods, 'gecos' => $tmp, 199 'description' => $tmp); 200 if ($samba == 1) { 201 push(@mods, 'displayName' => $tmp); 202 } 203} 204 205if (defined($tmp = $Options{'d'})) { 206 push(@mods, 'homeDirectory' => $tmp); 207} 208 209if (defined($tmp = $Options{'N'})) { 210 push(@mods, 'cn' => $tmp); 211} 212 213if (defined($tmp = $Options{'S'})) { 214 push(@mods, 'sn' => $tmp); 215} 216 217my $mailobj = 0; 218if ($tmp= $Options{'M'}) { 219 # action si + or - for adding or deleting an entry 220 my $action= ''; 221 if ($tmp =~ s/^([+-])+\s*//) { 222 $action= $1; 223 } 224 my @userMailLocal = &split_arg_comma($tmp); 225 my @mail; 226 foreach my $m (@userMailLocal) { 227 my $domain = $config{mailDomain}; 228 if ($m =~ /^(.+)@/) { 229 push (@mail, $m); 230 # mailLocalAddress contains only the first part 231 $m= $1; 232 } else { 233 push(@mail, $m.($domain ? '@'.$domain : '')); 234 } 235 } 236 if ($action) { 237 my @old_MailLocal; 238 my @old_mail; 239 @old_mail = $user_entry->get_value('mail'); 240 @old_MailLocal = $user_entry->get_value('mailLocalAddress'); 241 if ($action eq '+') { 242 @userMailLocal = &list_union(\@old_MailLocal, \@userMailLocal); 243 @mail = &list_union(\@old_mail, \@mail); 244 } elsif ($action eq '-') { 245 @userMailLocal = &list_minus(\@old_MailLocal, \@userMailLocal); 246 @mail = &list_minus(\@old_mail, \@mail); 247 } 248 } 249 push(@mods, 'mailLocalAddress', [ @userMailLocal ]); 250 push(@mods, 'mail' => [ @mail ]); 251 $mailobj = 1; 252} 253 254if ($tmp= $Options{'T'}) { 255 my $action= ''; 256 my @old; 257 # action si + or - for adding or deleting an entry 258 if ($tmp =~ s/^([+-])+\s*//) { 259 $action= $1; 260 } 261 my @userMailTo = &split_arg_comma($tmp); 262 if ($action) { 263 @old = $user_entry->get_value('mailRoutingAddress'); 264 } 265 if ($action eq '+') { 266 @userMailTo = &list_union(\@old, \@userMailTo); 267 } elsif ($action eq '-') { 268 @userMailTo = &list_minus(\@old, \@userMailTo); 269 } 270 push(@mods, 'mailRoutingAddress', [ @userMailTo ]); 271 $mailobj = 1; 272} 273if ($mailobj) { 274 my @objectclass = $user_entry->get_value('objectClass'); 275 if (! grep ($_ =~ /^inetLocalMailRecipient$/i, @objectclass)) { 276 push(@mods, 'objectClass' => [ @objectclass, 'inetLocalMailRecipient' ]); 277 } 278} 279 280 281if (defined($tmp = $Options{'G'})) { 282 my $action= ''; 283 if ($tmp =~ s/^([+-])+\s*//) { 284 $action= $1; 285 } 286 if ($action eq '-') { 287 # remove user from specified groups 288 foreach my $gname (&split_arg_comma($tmp)) { 289 group_remove_member($gname, $user); 290 } 291 } else { 292 if ($action ne '+') { 293 my @old = &find_groups_of($user); 294 # remove user from old groups 295 foreach my $gname (@old) { 296 if ($gname ne "") { 297 group_remove_member($gname, $user); 298 } 299 } 300 } 301 # add user to new groups 302 add_grouplist_user($tmp, $user); 303 } 304} 305 306# 307# A : sambaPwdCanChange 308# B : sambaPwdMustChange 309# C : sambaHomePath 310# D : sambaHomeDrive 311# E : sambaLogonScript 312# F : sambaProfilePath 313# H : sambaAcctFlags 314 315my $attr; 316my $winmagic = 2147483647; 317 318$samba = is_samba_user($user); 319 320if (defined($tmp = $Options{'e'})) { 321 if ($samba == 1) { 322 my $kickoffTime=`date --date='$tmp' +%s`; 323 chomp($kickoffTime); 324 push(@mods, 'sambakickoffTime' => $kickoffTime); 325 } else { 326 print "User $user is not a samba user\n"; 327 } 328} 329 330my $_sambaPwdCanChange; 331if (defined($tmp = $Options{'A'})) { 332 if ($samba == 1) { 333 $attr = "sambaPwdCanChange"; 334 if ($tmp != 0) { 335 $_sambaPwdCanChange=0; 336 } else { 337 $_sambaPwdCanChange=$winmagic; 338 } 339 push(@mods, 'sambaPwdCanChange' => $_sambaPwdCanChange); 340 } else { 341 print "User $user is not a samba user\n"; 342 } 343} 344 345my $_sambaPwdMustChange; 346if (defined($tmp = $Options{'B'})) { 347 if ($samba == 1) { 348 if ($tmp != 0) { 349 $_sambaPwdMustChange=0; 350 # To force a user to change his password: 351 # . the attribut sambaPwdLastSet must be != 0 352 # . the attribut sambaAcctFlags must not match the 'X' flag 353 my $_sambaAcctFlags; 354 my $flags = $user_entry->get_value('sambaAcctFlags'); 355 if ( $flags =~ /X/ ) { 356 my $letters; 357 if ($flags =~ /(\w+)/) { 358 $letters = $1; 359 } 360 $letters =~ s/X//; 361 $_sambaAcctFlags="\[$letters\]"; 362 push(@mods, 'sambaAcctFlags' => $_sambaAcctFlags); 363 } 364 my $_sambaPwdLastSet = $user_entry->get_value('sambaPwdLastSet'); 365 if ($_sambaPwdLastSet == 0) { 366 push(@mods, 'sambaPwdLastSet' => $winmagic); 367 } 368 } else { 369 $_sambaPwdMustChange=$winmagic; 370 } 371 push(@mods, 'sambaPwdMustChange' => $_sambaPwdMustChange); 372 } else { 373 print "User $user is not a samba user\n"; 374 } 375} 376 377if (defined($tmp = $Options{'C'})) { 378 if ($samba == 1) { 379 if ($tmp eq "" and defined $user_entry->get_value('sambaHomePath')) { 380 push(@dels, 'sambaHomePath' => []); 381 } elsif ($tmp ne "") { 382 push(@mods, 'sambaHomePath' => $tmp); 383 } 384 } else { 385 print "User $user is not a samba user\n"; 386 } 387} 388 389my $_sambaHomeDrive; 390if (defined($tmp = $Options{'D'})) { 391 if ($samba == 1) { 392 if ($tmp eq "" and defined $user_entry->get_value('sambaHomeDrive')) { 393 push(@dels, 'sambaHomeDrive' => []); 394 } elsif ($tmp ne "") { 395 $tmp = $tmp.":" unless ($tmp =~ /:/); 396 push(@mods, 'sambaHomeDrive' => $tmp); 397 } 398 } else { 399 print "User $user is not a samba user\n"; 400 } 401} 402 403if (defined($tmp = $Options{'E'})) { 404 if ($samba == 1) { 405 if ($tmp eq "" and defined $user_entry->get_value('sambaLogonScript')) { 406 push(@dels, 'sambaLogonScript' => []); 407 } elsif ($tmp ne "") { 408 push(@mods, 'sambaLogonScript' => $tmp); 409 } 410 } else { 411 print "User $user is not a samba user\n"; 412 } 413} 414 415if (defined($tmp = $Options{'F'})) { 416 if ($samba == 1) { 417 if ($tmp eq "" and defined $user_entry->get_value('sambaProfilePath')) { 418 push(@dels, 'sambaProfilePath' => []); 419 } elsif ($tmp ne "") { 420 push(@mods, 'sambaProfilePath' => $tmp); 421 } 422 } else { 423 print "User $user is not a samba user\n"; 424 } 425} 426 427if ($samba == 1 and (defined $Options{'H'} or defined $Options{'I'} or defined $Options{'J'})) { 428 my $_sambaAcctFlags; 429 if (defined($tmp = $Options{'H'})) { 430 #$tmp =~ s/\\/\\\\/g; 431 $_sambaAcctFlags=$tmp; 432 } else { 433 # I or J 434 my $flags; 435 $flags = $user_entry->get_value('sambaAcctFlags'); 436 437 if (defined($tmp = $Options{'I'})) { 438 if ( !($flags =~ /D/) ) { 439 my $letters; 440 if ($flags =~ /(\w+)/) { 441 $letters = $1; 442 } 443 $_sambaAcctFlags="\[D$letters\]"; 444 } 445 } elsif (defined($tmp = $Options{'J'})) { 446 if ( $flags =~ /D/ ) { 447 my $letters; 448 if ($flags =~ /(\w+)/) { 449 $letters = $1; 450 } 451 $letters =~ s/D//; 452 $_sambaAcctFlags="\[$letters\]"; 453 } 454 } 455 } 456 457 458 if ("$_sambaAcctFlags" ne '') { 459 push(@mods, 'sambaAcctFlags' => $_sambaAcctFlags); 460 } 461 462} elsif (!$samba == 1 and (defined $Options{'H'} or defined $Options{'I'} or defined $Options{'J'})) { 463 print "User $user is not a samba user\n"; 464} 465 466 467# apply changes 468my $modify = $ldap_master->modify ( "$dn", 469 'replace' => { @mods } 470 ); 471$modify->code && warn "failed to modify entry: ", $modify->error ; 472 473# we can delete only if @dels is not empty: we check the number of elements 474my $nb_to_del=scalar(@dels); 475if ($nb_to_del != 0) { 476 $modify = $ldap_master->modify ( "$dn", 477 'delete' => { @dels } 478 ); 479 $modify->code && warn "failed to modify entry: ", $modify->error ; 480} 481# take down session 482$ldap_master->unbind; 483 484if (defined(my $new_user= $Options{'r'})) { 485 my $ldap_master=connect_ldap_master(); 486 chomp($new_user); 487 # read eventual new user entry 488 my $new_user_entry = read_user_entry($new_user); 489 if (defined($new_user_entry)) { 490 print "$0: user $new_user already exists, cannot rename\n"; 491 exit (1); 492 } 493 my $modify = $ldap_master->moddn ( 494 "uid=$user,$config{usersdn}", 495 newrdn => "uid=$new_user", 496 deleteoldrdn => "1", 497 newsuperior => "$config{usersdn}" 498 ); 499 $modify->code && die "failed to change dn", $modify->error; 500 501 # change cn, sn attributes 502 my $user_entry = read_user_entry($new_user); 503 my $dn= $user_entry->dn(); 504 my @mods; 505 push(@mods, 'sn' => $new_user); 506 push(@mods, 'cn' => $new_user); 507 $modify = $ldap_master->modify ("$dn", 508 changes => [ 509 'replace' => [ @mods ] 510 ] 511 ); 512 $modify->code && warn "failed to change cn and sn attributes: ", $modify->error; 513 514 # changing username in groups 515 my @groups = &find_groups_of($user); 516 foreach my $gname (@groups) { 517 if ($gname ne "") { 518 my $dn_line = get_group_dn($gname); 519 my $dn = get_dn_from_line("$dn_line"); 520 print "updating group $gname\n"; 521 $modify = $ldap_master->modify("$dn", 522 changes => [ 523 'delete' => [memberUid => $user], 524 'add' => [memberUid => $new_user] 525 ]); 526 $modify->code && warn "failed to change cn and sn attributes: ", $modify->error; 527 } 528 } 529 $ldap_master->unbind; 530} 531 532$nscd_status = system "/etc/init.d/nscd status >/dev/null 2>&1"; 533 534if ($nscd_status == 0) { 535 system "/etc/init.d/nscd restart > /dev/null 2>&1"; 536} 537 538if (defined($Options{'P'})) { 539 exec "$RealBin/smbldap-passwd $user" 540} 541 542 543############################################################ 544 545=head1 NAME 546 547smbldap-usermod - Modify a user account 548 549=head1 SYNOPSIS 550 551smbldap-usermod [-a] [-c comment] [-d home_dir] [-e expiration_date] [-g initial_group] [-l login_name] [-p passwd] [-s shell] [-u uid [ -o]] [-x] [-A canchange] [-B mustchange] [-C smbhome] [-D homedrive] [-E scriptpath] [-F profilepath] [-G group[,...]] [-H acctflags] [-N canonical_name] [-S surname] [-P] login 552 553=head1 DESCRIPTION 554 555The smbldap-usermod command modifies the system account files to reflect the changes that are specified on the command line. The options which apply to the usermod command are 556 557-a 558 Add the sambaSAMAccount objectclass to the specified user account. This allow the user to become a samba user. 559 560-c comment 561 The new value of the user's comment field (gecos). 562 563-d home_dir 564 The user's new login directory. 565 566-e expiration_date 567 Set the expiration date for the user account. This only affect samba account. The date must be in the following format : YYYY-MM-DD HH:MM:SS. This option call the external 'date' command to set calculate the number of seconds from Junary 1 1970 to the specified date. 568 569-g initial_group 570 The group name or number of the user's new initial login group. The group name must exist. A group number must refer to an already existing group. The default group number is 1. 571 572-G group,[...] 573 A list of supplementary groups which the user is also a member of. Each group is separated from 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. If the user is currently a member of a group which is not listed, the user will be removed from the group 574 575-l login_name 576 The name of the user will be changed from login to login_name. Nothing else is changed. In particular, the user's home directory name should probably be changed to reflect the new login name. 577 578-s shell 579 The name of the user's new login shell. Setting this field to blank causes the system to select the default login shell. 580 581-u uid 582 The numerical value of the user's ID. This value must be unique, unless the -o option is used. The value must be non negative. Any files which the user owns and which are located in the directory tree rooted at the user's home directory will have the file user ID changed automatically. Files outside of the user's home directory must be altered manually. 583 584-r new_user 585 Allow to rename a user. This option will update the cn, sn and dn attribute for the user. You can 586 also update others attributes using the corresponding script options. 587 588-x 589 Creates rid and primaryGroupID in hex instead of decimal (for Samba 2.2.2 unpatched only - higher versions always use decimal) 590 591-A 592 can change password ? 0 if no, 1 if yes 593 594-B 595 must change password ? 0 if no, 1 if yes 596 597-C 598 sambaHomePath (SMB home share, like '\\\\PDC-SRV\\homes') 599 600-D 601 sambaHomeDrive (letter associated with home share, like 'H:') 602 603-E 604 sambaLogonScript, relative to the [netlogon] share (DOS script to execute on login, like 'foo.bat') 605 606-F 607 sambaProfilePath (profile directory, like '\\\\PDC-SRV\\profiles\\foo') 608 609-H 610 sambaAcctFlags, spaces and trailing bracket are ignored (samba account control bits like '[NDHTUMWSLKI]') 611 612-I 613 disable user. Can't be used with -H or -J 614 615-J 616 enable user. Can't be used with -H or -I 617 618-N 619 set the canonical name (attribut cn) 620 621-S 622 Set the surname 623 624-P 625 End by invoking smbldap-passwd to change the user password (both unix and samba passwords) 626 627=head1 SEE ALSO 628 629 usermod(1) 630 631=cut 632 633#' 634