Initial import
[samba] / examples / LDAP / smbldap-tools-0.9.1 / doc / smbldap-migrate-unix-accounts
1 #!/usr/bin/perl -w
2
3 # Created by P.Wieleba@iem.pw.edu.pl in 2004
4
5 use strict;
6 use Getopt::Std;
7 use FindBin;
8 use FindBin qw($RealBin);
9 use lib "$RealBin/";
10 use smbldap_tools;
11
12 # function declaration
13 sub migrate_user;
14 sub migrate_shadow_user;
15 sub get_user_entry;
16 sub exist_in_tab;
17 sub del_from_tab;
18 sub add_to_tab;
19 sub read_shadow_file;
20
21 # smbldap-migrate-unix-accounts (-? or -h for help)
22 #
23 #
24
25 my %Options;
26
27 my $ok = getopts('M:P:S:vn?hd:a', \%Options);
28
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";  
39   exit (1);
40 }
41
42 my $INFILE = undef;
43 my %shadowUsers;
44
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" ); 
56   }
57 } elsif ( $Options{'S'} ) {
58   open($INFILE,$Options{'S'}) or
59     die "I cannot open file: " . $Options{'S'} . "\n";
60 }
61
62 my $ldap_master=connect_ldap_master();
63
64 while ( my $line=<$INFILE> ) {
65   chop($line);
66   next if ( $line =~ /^\s*$/ ); # whitespace
67   next if ( $line =~ /^#/ );
68   next if ( $line =~ /^\+/ );
69   my $entry = undef;
70   if ($Options{'M'}) {
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)
76     if ($entry) {
77       my @objectClass = $entry->get_value( 'objectClass' );
78       $entry->replace( 'objectClass' => [add_to_tab(\@objectClass,'shadowAccount')] );
79     }
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);
85                 
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);
91     }
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);
97   }
98
99   if ($entry) {
100     # objectClass $Options{'d'} will be removed
101     # from entry if it exists
102     if ($Options{'d'}) {
103       my @objectClass = $entry->get_value( 'objectClass' );
104       $entry->replace( 'objectClass' => [del_from_tab(\@objectClass,$Options{'d'})] );
105       #$entry->delete( 'objectClass' => [ $Options{'d'} ] );
106     }
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')] );
111                         
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) {
124           my $dn= $entry->dn;
125           chomp($dn);
126           print "$dn\n";
127         }
128       } else {
129         $entry->replace( 'sambaSID' => $user_sid );
130       }
131     }
132     if ($Options{'v'}) {
133       $entry->dump();
134     }
135     if (!$Options{'n'}) {
136       my $mesg;
137       if ( $Options{'d'} ) {
138                                 # delete entry from LDAP if it exists
139         $mesg = $ldap_master->search( base => $entry->dn(),
140                                       scope => 'sub',
141                                       filter => '(objectClass=*)'
142                                     );
143         if ( $mesg->count() == 1 ) {
144           $mesg = $ldap_master->delete($entry->dn());
145           if ($mesg->is_error()) {
146             print "Error: " . $mesg->error() . "\n";
147           }
148           $entry->changetype('add');
149         }
150       }
151       $mesg = $entry->update($ldap_master);
152       if ($mesg->is_error()) {
153         print "Error: " . $mesg->error() . "\n";
154       }
155     }
156   }
157 }
158
159 $INFILE and close($INFILE);
160 # take down the session
161 $ldap_master and $ldap_master->unbind;
162
163 # returns updated $entry
164 sub migrate_user
165   {
166     my($entry,$user,$pwd,$uid,$gid,$gecos,$homedir,$shell) = @_;
167     my($name,$office,$wphone,$hphone)=split(/,/,$gecos);
168     my($cn);
169         
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 );
175
176     $entry->replace( 'uid' => $user );
177     if ($name) {
178       $cn = $name;
179     } else {
180       $cn = $user;
181     }
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 ???????
191     }
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 );
198
199     return $entry;
200   }
201
202 # returns updated $entry
203 sub migrate_shadow_user
204   {
205     my($entry,$user,$pwd,$lastchg,$min,$max,$warn,$inactive,$expire,$flag) = @_;
206         
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')] );
212         
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 );
222         
223     return $entry;
224   }
225
226 # creates a _new_entry_ if user doesn't exist in ldap
227 # else return's ldap user entry
228 sub get_user_entry
229   {
230     my($ldap_master,$user) = @_;
231         
232     # do not use read_user_entry()
233     my $mesg = $ldap_master->search( base => $config{usersdn},
234                                      scope => 'one',
235                                      filter => "(uid=$user)"
236                                    );
237     my $entry;
238     if ( $mesg->count() != 1 ) {
239       $entry = Net::LDAP::Entry->new();
240       $entry->dn("uid=$user,$config{usersdn}");
241     } else {
242       $entry = $mesg->entry(0); # ????
243     }
244     return $entry;
245   }
246
247 # Check if a $text element exists in @table
248 # eg. exist_in_tab(\@table,$text);
249 sub exist_in_tab
250   {
251     my($ref_tab,$text) = @_;
252     my @tab = @$ref_tab;
253
254     foreach my $elem (@tab) {
255       if ( lc($elem) eq lc($text) ) {
256         return 1;
257       }
258     }
259     return 0;
260   }
261
262 # Delete $text element from @table
263 # eg. del_from_tab(\@table,$text);
264 sub del_from_tab
265   {
266     my($ref_tab,$text) = @_;
267     my @tab = @$ref_tab;
268     my @new_tab;
269                 
270     foreach my $elem (@tab) {
271       if ( lc($elem) ne lc($text) ) {
272         push(@new_tab,$elem);
273       }
274     }
275     return @new_tab;
276   }
277
278 # Add $text to tab if it doesn't exist there
279 sub add_to_tab
280   {
281     my($ref_tab,$text) = @_;
282     my @tab = @$ref_tab;
283         
284     if ( !exist_in_tab(\@tab,$text) ) {
285       push(@tab,$text);
286     }
287     return @tab;
288   }
289
290 # reads shadow file entries and places them in a hash
291 sub read_shadow_file
292   {
293     my($shadow) = @_;
294
295     my $shadowUser;
296     my %shadowUsers;
297     open(SHADOW,$shadow) or
298       return ;
299     while (my $line=<SHADOW>) {
300       chop($line);
301       next if ( $line =~ /^\s*$/ ); # whitespace
302       next if ( $line =~ /^#/ );
303       ($shadowUser) = split(/:/, $line);
304       $shadowUsers{$shadowUser} = $line;
305     }
306     close(SHADOW);
307     return %shadowUsers;
308   }
309
310 ########################################
311
312 =head1 NAME
313
314 smbldap-migrate-unix-accounts - Migrate unix accounts to LDAP
315
316 =head1 SYNOPSIS
317
318 smbldap-migrate-unix-accounts [-P file] [-S file] [-M file] [-n] [-v]
319 [-h] [-?] [-d]
320
321 =head1 DESCRIPTION
322
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.
327
328 -P passwd_file
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.
332
333 -S shadow_file
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.
337
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.
343
344 -h     show the help message
345
346 -?     the same as -h
347
348 -v     displayes modified entries to STDOUT
349
350 -n     do everything execpt updating LDAP. It is useful when used
351        with -v switch.
352
353 -d objeClass_name
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'
362
363 -a     adds sambaSamAccount objectClass and generates sambaSID attribute
364
365 =cut
366
367 #'
368
369 # The End
370
371