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 |
|
---|