Initial import
[samba] / examples / LDAP / smbldap-tools-0.9.1 / smbldap-usermod
1 #!/usr/bin/perl -w
2
3 # $Id: smbldap-usermod,v 1.13 2005/05/27 14:21:00 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
27 use strict;
28 use FindBin;
29 use FindBin qw($RealBin);
30 use lib "$RealBin/";
31 use smbldap_tools;
32
33 #####################
34
35 use Getopt::Std;
36 my %Options;
37 my $nscd_status;
38
39 my $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);
40 if ( (!$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
75 if ($< != 0) {
76   print "You must be root to modify an user\n";
77   exit (1);
78 }
79 # Read only first @ARGV
80 my $user = $ARGV[0];
81
82 # Let's connect to the directory first
83 my $ldap_master=connect_ldap_master();
84
85 # Read user data
86 my $user_entry = read_user_entry($user);
87 if (!defined($user_entry)) {
88   print "$0: user $user doesn't exist\n";
89   exit (1);
90 }
91
92 my $samba = 0;
93 if (grep ($_ =~ /^sambaSamAccount$/i, $user_entry->get_value('objectClass'))) {
94   $samba = 1;
95 }
96
97 # get the dn of the user
98 my $dn= $user_entry->dn();
99
100 my $tmp;
101 my @mods;
102 my @dels;
103 if (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
132 my $changed_uid;
133 my $_userUidNumber;
134 my $_userRid;
135 if (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
169 my $changed_gid;
170 my $_userGidNumber;
171 my $_userGroupSID;
172 if (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
192 if (defined($tmp = $Options{'s'})) {
193   push(@mods, 'loginShell' => $tmp);
194 }
195
196
197 if (defined($tmp = $Options{'c'})) {
198   push(@mods, 'gecos' => $tmp,
199        'description' => $tmp);
200   if ($samba == 1) {
201     push(@mods, 'displayName' => $tmp);
202   }
203 }
204
205 if (defined($tmp = $Options{'d'})) {
206   push(@mods, 'homeDirectory' => $tmp);
207 }
208
209 if (defined($tmp = $Options{'N'})) { 
210   push(@mods, 'cn' => $tmp);
211 }
212
213 if (defined($tmp = $Options{'S'})) { 
214   push(@mods, 'sn' => $tmp);
215 }
216
217 my $mailobj = 0;
218 if ($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
254 if ($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 }
273 if ($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
281 if (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
315 my $attr;
316 my $winmagic = 2147483647;
317
318 $samba = is_samba_user($user);
319
320 if (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
330 my $_sambaPwdCanChange;
331 if (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
345 my $_sambaPwdMustChange;
346 if (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 ( defined $flags and $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
377 if (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
389 my $_sambaHomeDrive;
390 if (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
403 if (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
415 if (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
427 if ($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
468 my $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
474 my $nb_to_del=scalar(@dels);
475 if ($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
484 if (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
534 if ($nscd_status == 0) {
535   system "/etc/init.d/nscd restart > /dev/null 2>&1";
536 }
537
538 if (defined($Options{'P'})) {
539   exec "$RealBin/smbldap-passwd $user"
540 }
541
542
543 ############################################################
544
545 =head1 NAME
546
547 smbldap-usermod - Modify a user account
548
549 =head1 SYNOPSIS
550
551 smbldap-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
555 The  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 #'