3 # Created by P.Wieleba@iem.pw.edu.pl in 2004
8 use FindBin qw($RealBin);
12 # function declaration
14 sub migrate_shadow_user;
21 # smbldap-migrate-unix-accounts (-? or -h for help)
27 my $ok = getopts('M:P:S:vn?hd:a', \%Options);
29 if ( (!$ok) || ($Options{'?'}) || ($Options{'h'}) || (!keys(%Options)) ) {
30 print "Usage: $0 [-PSMvn?hda]\n";
31 print " -?|-h show this help message\n";
32 print " -P file import passwd file\n";
33 print " -S file import shadow file\n";
34 print " -M file import FreeBSD master.passwd\n";
35 print " -v displays modified entries to STDOUT\n";
36 print " -n do everything execpt updating LDAP\n";
37 print " -d obj_nam delete and add (not just update) existing entry in LDAP\n";
38 print " -a adds sambaSamAccount objectClass\n";
45 if ( $Options{'M'} ) {
46 open($INFILE,$Options{'M'}) or
47 die "I cannot open file: " . $Options{'M'} . "\n";
48 } elsif ( $Options{'P'} ) {
49 open($INFILE,$Options{'P'}) or
50 die "I cannot open file: " . $Options{'P'} . "\n";
51 # if defined -S option also read shadow file
52 if ( $Options{'S'} ) {
53 %shadowUsers = read_shadow_file($Options{'S'});
54 (%shadowUsers) or ( close($INFILE) and
55 die "I cannot open file: " . $Options{'S'} . "\n" );
57 } elsif ( $Options{'S'} ) {
58 open($INFILE,$Options{'S'}) or
59 die "I cannot open file: " . $Options{'S'} . "\n";
62 my $ldap_master=connect_ldap_master();
64 while ( my $line=<$INFILE> ) {
66 next if ( $line =~ /^\s*$/ ); # whitespace
67 next if ( $line =~ /^#/ );
68 next if ( $line =~ /^\+/ );
71 my($user,$pwd,$uid,$gid,$class,$change,$expire,$gecos,$homedir,$shell) = split(/:/,$line);
72 # if user is not in LDAP new entry will be created
73 $entry = get_user_entry($ldap_master,$user);
74 $entry = migrate_user($entry,$user,$pwd,$uid,$gid,$gecos,$homedir,$shell);
75 # for master.passwd file (nss_ldap)
77 my @objectClass = $entry->get_value( 'objectClass' );
78 $entry->replace( 'objectClass' => [add_to_tab(\@objectClass,'shadowAccount')] );
80 } elsif ($Options{'P'}) {
81 my($user,$pwd,$uid,$gid,$gecos,$homedir,$shell) = split(/:/,$line);
82 # if user is not in LDAP new entry will be created
83 $entry = get_user_entry($ldap_master,$user);
84 $entry = migrate_user($entry,$user,$pwd,$uid,$gid,$gecos,$homedir,$shell,undef);
86 # should I delete next functionality
87 # add shadow entries if also -S defined
88 if ($Options{'S'} and $shadowUsers{$user}) {
89 my($user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag) = split(/:/,$shadowUsers{$user});
90 $entry = migrate_shadow_user($entry,$user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag);
92 } elsif ($Options{'S'}) {
93 my($user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag)=split(/:/,$line);
94 # if user is not in LDAP new entry will be created
95 $entry = get_user_entry($ldap_master,$user);
96 $entry = migrate_shadow_user($entry,$user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag);
100 # objectClass $Options{'d'} will be removed
101 # from entry if it exists
103 my @objectClass = $entry->get_value( 'objectClass' );
104 $entry->replace( 'objectClass' => [del_from_tab(\@objectClass,$Options{'d'})] );
105 #$entry->delete( 'objectClass' => [ $Options{'d'} ] );
107 # if used "-a" and sambaSamAccount doesn't exist.
108 if ( $Options{'a'} and !exist_in_tab([$entry->get_value('objectClass')],'sambaSamAccount') ) {
109 my @objectClass = $entry->get_value( 'objectClass' );
110 $entry->replace( 'objectclass' => [add_to_tab(\@objectClass,'sambaSamAccount')] );
112 # the below part comes from smbldap-useradd and
113 # maybe it should be replaced by a new subroutine.
114 my $userUidNumber = $entry->get_value('uidNumber');
115 # as rid we use 2 * uid + 1000
116 my $userRid = 2 * $userUidNumber + 1000;
117 # let's test if this SID already exist
118 my $user_sid = "$config{SID}-$userRid";
119 my $test_exist_sid = does_sid_exist($user_sid,$config{usersdn});
120 if ($test_exist_sid->count == 1) {
121 print "User SID already owned by\n";
122 # there should not exist more than one entry, but ...
123 foreach my $entry ($test_exist_sid->all_entries) {
129 $entry->replace( 'sambaSID' => $user_sid );
135 if (!$Options{'n'}) {
137 if ( $Options{'d'} ) {
138 # delete entry from LDAP if it exists
139 $mesg = $ldap_master->search( base => $entry->dn(),
141 filter => '(objectClass=*)'
143 if ( $mesg->count() == 1 ) {
144 $mesg = $ldap_master->delete($entry->dn());
145 if ($mesg->is_error()) {
146 print "Error: " . $mesg->error() . "\n";
148 $entry->changetype('add');
151 $mesg = $entry->update($ldap_master);
152 if ($mesg->is_error()) {
153 print "Error: " . $mesg->error() . "\n";
159 $INFILE and close($INFILE);
160 # take down the session
161 $ldap_master and $ldap_master->unbind;
163 # returns updated $entry
166 my($entry,$user,$pwd,$uid,$gid,$gecos,$homedir,$shell) = @_;
167 my($name,$office,$wphone,$hphone)=split(/,/,$gecos);
170 # posixAccount MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
171 my @objectClass = $entry->get_value( 'objectClass' );
172 @objectClass = add_to_tab(\@objectClass,'posixAccount');
173 @objectClass = add_to_tab(\@objectClass,'inetOrgPerson');
174 $entry->replace( 'objectClass' => \@objectClass );
176 $entry->replace( 'uid' => $user );
182 $entry->replace( 'cn' => $cn );
183 # perhaps I should delete it
184 if ( exist_in_tab(\@objectClass,'inetOrgPerson') ) {
185 # 'sn' is required by person objectClass from core.schema
186 my @tmp = split(/\s+/,$cn);
187 my $sn = $tmp[$#tmp];
188 $entry->replace( 'sn' => $sn );
189 # perhaps 'telephoneNumber' 'roomNumber' 'homePhone'
190 # and 'givenName' also should be modified ???????
192 ($pwd) and $entry->replace( 'userPassword' => "{crypt}" . $pwd );
193 ($uid ne "") and $entry->replace( 'uidNumber' => $uid );
194 ($gid ne "") and $entry->replace( 'gidNumber' => $gid );
195 ($gecos) and $entry->replace( 'gecos' => $gecos );
196 ($homedir) and $entry->replace( 'homeDirectory' => $homedir );
197 ($shell) and $entry->replace( 'loginShell' => $shell );
202 # returns updated $entry
203 sub migrate_shadow_user
205 my($entry,$user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag) = @_;
207 # shadowAccount MUST uid
208 my @objectClass = $entry->get_value( 'objectClass' );
209 # if the entry doesn't exist, it needs structural objectclass
210 (@objectClass) or push(@objectClass,'account');
211 $entry->replace( 'objectClass' => [add_to_tab(\@objectClass,'shadowAccount')] );
213 $entry->replace( 'uid' => $user );
214 ($pwd) and $entry->replace( 'userPassword' => "{crypt}" . $pwd );
215 ($lastchg) and $entry->replace( 'shadowLastChange' => $lastchg );
216 ($min) and $entry->replace( 'shadowMin' => $min );
217 ($max) and $entry->replace( 'shadowMax' => $max );
218 ($warn) and $entry->replace( 'shadowWarning' => $warn );
219 ($inactive) and $entry->replace( 'shadowInactive' => $inactive );
220 ($expire) and $entry->replace( 'shadowExpire' => $expire );
221 ($flag) and $entry->replace( 'shadowFlag' => $flag );
226 # creates a _new_entry_ if user doesn't exist in ldap
227 # else return's ldap user entry
230 my($ldap_master,$user) = @_;
232 # do not use read_user_entry()
233 my $mesg = $ldap_master->search( base => $config{usersdn},
235 filter => "(uid=$user)"
238 if ( $mesg->count() != 1 ) {
239 $entry = Net::LDAP::Entry->new();
240 $entry->dn("uid=$user,$config{usersdn}");
242 $entry = $mesg->entry(0); # ????
247 # Check if a $text element exists in @table
248 # eg. exist_in_tab(\@table,$text);
251 my($ref_tab,$text) = @_;
254 foreach my $elem (@tab) {
255 if ( lc($elem) eq lc($text) ) {
262 # Delete $text element from @table
263 # eg. del_from_tab(\@table,$text);
266 my($ref_tab,$text) = @_;
270 foreach my $elem (@tab) {
271 if ( lc($elem) ne lc($text) ) {
272 push(@new_tab,$elem);
278 # Add $text to tab if it doesn't exist there
281 my($ref_tab,$text) = @_;
284 if ( !exist_in_tab(\@tab,$text) ) {
290 # reads shadow file entries and places them in a hash
297 open(SHADOW,$shadow) or
299 while (my $line=<SHADOW>) {
301 next if ( $line =~ /^\s*$/ ); # whitespace
302 next if ( $line =~ /^#/ );
303 ($shadowUser) = split(/:/, $line);
304 $shadowUsers{$shadowUser} = $line;
310 ########################################
314 smbldap-migrate-unix-accounts - Migrate unix accounts to LDAP
318 smbldap-migrate-unix-accounts [-P file] [-S file] [-M file] [-n] [-v]
323 This command processes one file as defined by option and
324 creates new or changes existing ldap user entry.
325 New attributes are added, and existing are changed.
326 None of the existing attributes is deleted.
329 Processes passwd_file and uptades LDAP. Creates new ldap user
330 entry or just adds posixAccount objectclass and corresponding
331 attributes to the ldap user entry or just uptades their values.
334 Reads shadow_file and uptades LDAP. Creates new ldap user
335 entry or just adds shadowAccount objectclass and corresponding
336 attributes to the ldap user entry or just uptades their values.
338 -M master.passwd_file
339 Reads master.passwd_file and uptades LDAP. Creates new ldap user
340 entry or just adds shadowAccount and posixAccount objectclass
341 and corresponding attributes to the ldap user entry or just
342 uptades their values.
344 -h show the help message
348 -v displayes modified entries to STDOUT
350 -n do everything execpt updating LDAP. It is useful when used
354 In spite of just updating existing user entry, the entry will be
355 deleted from LDAP and a new one will be added.
356 It is essential to use this option if you update users in LDAP
357 and want to change their structural objectClass.
358 Use it in the example schema:
359 There are no users in LDAP, and you migrate samba first.
360 # pdbedit -e ldapsam:ldap://localhost
361 # smbldap-migrate-passwd -P passwd -d 'account'
363 -a adds sambaSamAccount objectClass and generates sambaSID attribute