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