Initial import
[samba] / examples / LDAP / smbldap-tools-0.9.1 / smbldap-passwd
1 #!/usr/bin/perl -w
2
3 # LDAP to unix password sync script for samba
4 # $Id: smbldap-passwd,v 1.17 2005/05/27 14:21:00 jtournier Exp $
5
6 #  This code was developped by IDEALX (http://IDEALX.org/) and
7 #  contributors (their names can be found in the CONTRIBUTORS file).
8 #
9 #                 Copyright (C) 2001-2002 IDEALX
10 #
11 #  This program is free software; you can redistribute it and/or
12 #  modify it under the terms of the GNU General Public License
13 #  as published by the Free Software Foundation; either version 2
14 #  of the License, or (at your option) any later version.
15 #
16 #  This program is distributed in the hope that it will be useful,
17 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
18 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 #  GNU General Public License for more details.
20 #
21 #  You should have received a copy of the GNU General Public License
22 #  along with this program; if not, write to the Free Software
23 #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
24 #  USA.
25
26 #  Purpose :
27 #       . ldap-unix passwd sync for SAMBA>2.2.2 + LDAP
28 #       . may also replace /bin/passwd
29
30 # untaint environment
31 $ENV{'PATH'}='/bin:/usr/bin';
32 $ENV{'SHELL'} = '/bin/sh';
33 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
34
35 use strict;
36 use FindBin;
37 use FindBin qw($RealBin);
38 use lib "$RealBin/";
39 use smbldap_tools;
40
41 use Crypt::SmbHash;
42 use Digest::MD5 qw(md5);
43 use Digest::SHA1 qw(sha1);
44 use MIME::Base64 qw(encode_base64);
45
46 # function declaration
47 sub make_hash;
48 sub make_salt;
49
50 my $user;
51 my $oldpass;
52 my $ret;
53
54 my $arg;
55 my $update_only_userPasswd=0;
56
57 foreach $arg (@ARGV) {
58   if ($< != 0) {
59     die "Only root can specify parameters\n";
60   } else {
61     if ( ($arg eq '-?') || ($arg eq '--help') ) {
62       print_banner;
63       print "Usage: $0 [username]\n";
64       print "  -u             update only unix password (userPasswd)\n";
65       print "  -?, --help     show this help message\n";
66       exit (6);
67     } elsif ($arg eq '-u') {
68       $update_only_userPasswd=1;
69     } elsif (substr($arg,0) ne '-') {
70       $user = $arg;
71     }
72     $oldpass = 1;
73   }
74 }
75
76 if (!defined($user)) {
77   $user = getpwuid($<);         # $user=$ENV{"USER"};
78 }
79
80 # check if $user variable is not tainted
81 # [TODO] create proper user mask
82 $user =~ /^([-\@\ \w.]+\$?)$/ and $user = $1 or
83   die "$0: username '$user' is tainted\n";
84
85
86 my ($dn,$ldap_master);
87 # First, connecting to the directory
88 if ($< != 0) {
89   # non-root user
90   if (!defined($oldpass)) {
91     # prompt for password
92     print "UNIX password: ";
93     system "stty -echo" if (-t STDIN);
94     chomp($oldpass=<STDIN>); 
95     system "stty echo" if (-t STDIN);
96     print "\n";
97
98     $config{masterDN}="uid=$user,$config{usersdn}";
99     $config{masterPw}="$oldpass";
100     $ldap_master=connect_ldap_master();
101     $dn=$config{masterDN};
102     if (!is_user_valid($user, $dn, $oldpass)) {
103       print "Authentication failure\n";
104       exit (10);
105     }
106   }
107 } else {
108   # root user
109   $ldap_master=connect_ldap_master();
110   # test existence of user in LDAP
111   my $dn_line;
112   if (!defined($dn_line = get_user_dn($user))) {
113     print "$0: user $user doesn't exist\n";
114     exit (10);
115   }
116   $dn = get_dn_from_line($dn_line);
117 }
118
119 my $samba = is_samba_user($user);
120 print "Changing password for $user\n";
121
122 # prompt for new password
123
124 my $pass;
125 my $pass2;
126
127 print "New password : ";
128 system "stty -echo" if (-t STDIN);
129 chomp($pass=<STDIN>);
130 system "stty echo" if (-t STDIN);
131 print "\n";
132
133 print "Retype new password : ";
134 system "stty -echo" if (-t STDIN);
135 chomp($pass2=<STDIN>);
136 system "stty echo" if (-t STDIN);
137 print "\n";
138
139 if ($pass ne $pass2) {
140   print "New passwords don't match!\n";
141   exit (10);
142 }
143
144 # Prepare '$hash_password' for 'userPassword'
145 my $hash_password;
146 # Generate password hash
147 if ($config{with_slappasswd}) {
148   # checking if password is tainted: nothing is changed!!!!
149   # essential for perl 5.8
150   ($pass =~ /^(.*)$/ and $pass=$1) or
151     die "$0: user password is tainted\n";
152
153   # use slappasswd to generate hash
154   if ( $config{hash_encrypt} eq "CRYPT" && defined($config{crypt_salt_format}) ) {
155     open BUF, "-|" or
156       exec "$config{slappasswd}",
157         "-h","{$config{hash_encrypt}}",
158           "-c","$config{crypt_salt_format}",
159             "-s","$pass";
160     $hash_password = <BUF>;
161     close BUF;
162   } else {
163     open(BUF, "-|") or
164       exec "$config{slappasswd}",
165         "-h","{$config{hash_encrypt}}",
166           "-s","$pass";
167     $hash_password = <BUF>;
168     close BUF;
169   }
170 } else {
171   # use perl libraries to generate hash
172   $hash_password = make_hash($pass,$config{hash_encrypt},$config{crypt_salt_format});
173 }
174 # check if a hash was generated, otherwise die
175 defined($hash_password) or
176   die "I cannot generate the proper hash!\n";
177 chomp($hash_password);
178
179 # First, connecting to the directory
180 if ($< != 0) {
181   # if we are not root, we close the connection to re-open it as a normal user
182   $ldap_master->unbind;
183   $config{masterDN}="uid=$user,$config{usersdn}";
184   $config{masterPw}="$oldpass";
185   $ldap_master=connect_ldap_master();
186 }
187
188 # only modify smb passwords if smb user
189 if ($samba == 1 and $update_only_userPasswd == 0) {
190   if (!$config{with_smbpasswd}) {
191     # generate LanManager and NT clear text passwords
192     my ($sambaLMPassword,$sambaNTPassword) = ntlmgen $pass;
193     # the sambaPwdLastSet must be updating
194     my $date=time;
195     my @mods;
196     push(@mods, 'sambaLMPassword' => $sambaLMPassword);
197     push(@mods, 'sambaNTPassword' => $sambaNTPassword);
198     push(@mods, 'sambaPwdLastSet' => $date);
199     if (defined $config{defaultMaxPasswordAge}) {
200       my $new_sambaPwdMustChange=$date+$config{defaultMaxPasswordAge}*24*60*60;
201       push(@mods, 'sambaPwdMustChange' => $new_sambaPwdMustChange);
202       if ($< ==0) {
203         push(@mods, 'sambaAcctFlags' => '[U]');
204       }
205     }
206     # Let's change nt/lm passwords
207     my $modify = $ldap_master->modify ( "$dn",
208                                         'replace' => { @mods }
209                                       );
210     $modify->code && warn "failed to modify entry: ", $modify->error ;
211
212   } else {
213     if ($< != 0) {
214       my $FILE="|$config{smbpasswd} -s >/dev/null";
215       open (FILE, $FILE) || die "$!\n";
216       print FILE <<EOF;
217 $oldpass
218 $pass
219 $pass
220 EOF
221       ;
222       close FILE;
223     } else {
224       open FILE,"|-" or
225         exec "$config{smbpasswd}","$user","-s";
226       local $SIG{PIPE} = sub {die "buffer pipe terminated" };
227       print FILE <<EOF;
228 $pass
229 $pass
230 EOF
231       ;
232       close FILE;
233     }
234   }
235 }
236
237 # Update 'userPassword' field
238 my $modify = $ldap_master->modify ( "$dn",
239                                     changes => [
240                                                 replace => [userPassword => "$hash_password"]
241                                                ]
242                                   );
243 $modify->code && warn "Unable to change password : ", $modify->error ;
244
245 # take down session
246 $ldap_master->unbind;
247
248 exit 0;
249
250 # Generates hash to be one of the following RFC 2307 schemes:
251 # CRYPT,  MD5,  SMD5,  SHA, SSHA,  and  CLEARTEXT
252 # SSHA is default
253 # '%s' is a default crypt_salt_format
254 # A substitute for slappasswd tool
255 sub make_hash
256   {
257     my $hash_encrypt;
258     my $crypt_salt_format;
259
260     my $clear_pass=$_[0] or return undef;
261     $hash_encrypt='{' . $_[1] . '}' or $hash_encrypt = "{SSHA}";
262     $crypt_salt_format=$_[2] or $crypt_salt_format = '%s';
263
264     my $hash_pass;
265     if ($hash_encrypt eq "{CRYPT}" && defined($crypt_salt_format)) {
266       # Generate CRYPT hash
267       # for unix md5crypt $crypt_salt_format = '$1$%.8s'
268       my $salt = sprintf($crypt_salt_format,make_salt());
269       $hash_pass = "{CRYPT}" . crypt($clear_pass,$salt);
270
271     } elsif ($hash_encrypt eq "{MD5}") {
272       # Generate MD5 hash
273       $hash_pass = "{MD5}" . encode_base64( md5($clear_pass),'' );
274
275     } elsif ($hash_encrypt eq "{SMD5}") {
276       # Generate SMD5 hash (MD5 with salt)
277       my $salt = make_salt(4);
278       $hash_pass = "{SMD5}" . encode_base64( md5($clear_pass . $salt) . $salt,'');
279
280     } elsif ($hash_encrypt eq "{SHA}") {
281       # Generate SHA1 hash
282       $hash_pass = "{SHA}" . encode_base64( sha1($clear_pass),'' );
283
284     } elsif ($hash_encrypt eq "{SSHA}") {
285       # Generate SSHA hash (SHA1 with salt)
286       my $salt = make_salt(4);
287       $hash_pass = "{SSHA}" . encode_base64( sha1($clear_pass . $salt) . $salt,'' );
288
289     } elsif ($hash_encrypt eq "{CLEARTEXT}") {
290       $hash_pass=$clear_pass;
291
292     } else {
293       $hash_pass=undef;
294     }
295     return $hash_pass;
296   }
297
298 # Generates salt
299 # Similar to Crypt::Salt module from CPAN
300 sub make_salt
301   {
302     my $length=32;
303     $length = $_[0] if exists($_[0]);
304   
305     my @tab = ('.', '/', 0..9, 'A'..'Z', 'a'..'z');
306     return join "",@tab[map {rand 64} (1..$length)];
307   }
308
309 # - The End
310
311 =head1 NAME
312
313 smbldap-passwd - change user password
314
315 =head1 SYNOPSIS
316
317 smbldap-passwd [name]
318
319 =head1 DESCRIPTION
320
321 smbldap-passwd changes passwords for user accounts. A normal user may only change the password for their own account, the super user may change the password for any account.
322
323 Password Changes
324  The user is first prompted for their old password, if one is present. This password is then tested against the stored password by binding to the server. The user has only one chance to enter the correct passwword. The super user is permitted to bypass this step so that forgotten passwords may be changed.
325  The user is then prompted for a replacement password. As a general guideline, passwords should consist of 6 to 8 characters including one or more from each of following sets:
326
327 Lower case alphabetics
328
329 Upper case alphabetics
330
331 Digits 0 thru 9
332
333 Punctuation marks
334
335 Password will prompt again and compare the second entry against the first. Both entries are require to match in order for the password to be changed.
336
337 =head1 SEE ALSO
338
339        passwd(1)
340
341 =cut
342
343 #'