|
Lines 307-313
Link Here
|
| 307 |
organizational unit will look like:</para> |
307 |
organizational unit will look like:</para> |
| 308 |
|
308 |
|
| 309 |
<programlisting>dn: ou=people,dc=example,dc=org |
309 |
<programlisting>dn: ou=people,dc=example,dc=org |
| 310 |
objectClass: top |
|
|
| 311 |
objectClass: organizationalUnit |
310 |
objectClass: organizationalUnit |
| 312 |
ou: people</programlisting> |
311 |
ou: people</programlisting> |
| 313 |
|
312 |
|
|
Lines 336-342
Link Here
|
| 336 |
objectClass: person |
335 |
objectClass: person |
| 337 |
objectClass: posixAccount |
336 |
objectClass: posixAccount |
| 338 |
objectClass: shadowAccount |
337 |
objectClass: shadowAccount |
| 339 |
objectClass: top |
|
|
| 340 |
uidNumber: 10000 |
338 |
uidNumber: 10000 |
| 341 |
gidNumber: 10000 |
339 |
gidNumber: 10000 |
| 342 |
homeDirectory: /home/tuser |
340 |
homeDirectory: /home/tuser |
|
Lines 352-364
Link Here
|
| 352 |
user entries, but we will use the defaults below:</para> |
350 |
user entries, but we will use the defaults below:</para> |
| 353 |
|
351 |
|
| 354 |
<programlisting>dn: ou=groups,dc=example,dc=org |
352 |
<programlisting>dn: ou=groups,dc=example,dc=org |
| 355 |
objectClass: top |
|
|
| 356 |
objectClass: organizationalUnit |
353 |
objectClass: organizationalUnit |
| 357 |
ou: groups |
354 |
ou: groups |
| 358 |
|
355 |
|
| 359 |
dn: cn=tuser,ou=groups,dc=example,dc=org |
356 |
dn: cn=tuser,ou=groups,dc=example,dc=org |
| 360 |
objectClass: posixGroup |
357 |
objectClass: posixGroup |
| 361 |
objectClass: top |
|
|
| 362 |
gidNumber: 10000 |
358 |
gidNumber: 10000 |
| 363 |
cn: tuser</programlisting> |
359 |
cn: tuser</programlisting> |
| 364 |
|
360 |
|
|
Lines 604-654
Link Here
|
| 604 |
<screen>&prompt.root; <userinput>sysctl security.bsd.see_other_uids=0</userinput>.</screen> |
600 |
<screen>&prompt.root; <userinput>sysctl security.bsd.see_other_uids=0</userinput>.</screen> |
| 605 |
</caution> |
601 |
</caution> |
| 606 |
|
602 |
|
| 607 |
<para>A more flexible (and probably more secure) approach can be |
603 |
<para>A more flexible (and probably more secure) approach can be |
| 608 |
used by writing a custom program, or even a web interface. The |
604 |
used by writing a custom program, or even a web interface. |
| 609 |
following is part of a <application>Ruby</application> library |
605 |
The following is modeled on a <application>Python</application> |
| 610 |
that can change LDAP passwords. It sees use both on the command |
606 |
library that can change LDAP passwords. It sees use both |
| 611 |
line, and on the web.</para> |
607 |
on the command line, and on the web.</para> |
| 612 |
|
608 |
|
| 613 |
<example id="chpw-ruby"> |
609 |
<example id="chpw-python"> |
| 614 |
<title>Ruby script for changing passwords</title> |
610 |
<title>Python script for changing passwords</title> |
| 615 |
|
611 |
|
| 616 |
<programlisting><![CDATA[require 'ldap' |
612 |
<programlisting><![CDATA[import ldap # python-ldap |
| 617 |
require 'base64' |
613 |
import os, sys |
| 618 |
require 'digest' |
614 |
from getpass import getpass |
| 619 |
require 'password' # ruby-password |
615 |
|
| 620 |
|
616 |
uri = "ldap://ldap1.dimins.com" |
| 621 |
ldap_server = "ldap.example.org" |
617 |
searchbase = "ou=people,dc=dimins,dc=com" |
| 622 |
luser = "uid=#{ENV['USER']},ou=people,dc=example,dc=org" |
618 |
filter = "(&(objectClass=posixAccount)(uid=%s))" |
| 623 |
|
619 |
|
| 624 |
# get the new password, check it, and create a salted hash from it |
620 |
# get the username; if none is given, use the current user |
| 625 |
def get_password |
621 |
user = os.environ['USER'] |
| 626 |
pwd1 = Password.get("New Password: ") |
622 |
if len(sys.argv) > 1: |
| 627 |
pwd2 = Password.get("Retype New Password: ") |
623 |
user = sys.argv[1] |
| 628 |
|
624 |
|
| 629 |
raise if pwd1 != pwd2 |
625 |
ldapobj = ldap.initialize(uri) |
| 630 |
pwd1.check # check password strength |
626 |
ldapobj.start_tls_s() # this is pretty important |
| 631 |
|
627 |
|
| 632 |
salt = rand.to_s.gsub(/0\./, '') |
628 |
# Get the users DN, and then bind as that. |
| 633 |
pass = pwd1.to_s |
629 |
# The way to do this is first bind anonymously (if you don't allow anon |
| 634 |
hash = "{SSHA}"+Base64.encode64(Digest::SHA1.digest("#{pass}#{salt}")+salt).chomp! |
630 |
# binds, there's probably some standard account you use for this. |
| 635 |
return hash |
631 |
ldapobj.simple_bind_s() |
| 636 |
end |
632 |
|
| 637 |
|
633 |
# Search for a user with the uid we gave. We search everything under |
| 638 |
oldp = Password.get("Old Password: ") |
634 |
# the "base" we configure above (as there may be other users with the same |
| 639 |
newp = get_password |
635 |
# UID elsewhere in the tree; we don't want to return those. |
| 640 |
|
636 |
result = ldapobj.search_s(searchbase, ldap.SCOPE_SUBTREE, filter%user) |
| 641 |
# We'll just replace it. That we can bind proves that we either know |
637 |
|
| 642 |
# the old password or are an admin. |
638 |
if len(result) > 1: |
| 643 |
|
639 |
# This is kind of suspicious; we only want one user. |
| 644 |
replace = LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE | LDAP::LDAP_MOD_BVALUES, |
640 |
print "I found several users that match that user id." |
| 645 |
"userPassword", |
641 |
print "Talk to your sysadmin." |
| 646 |
[newp]) |
642 |
sys.exit(1) |
| 647 |
|
643 |
|
| 648 |
conn = LDAP::SSLConn.new(ldap_server, 389, true) |
644 |
# The results are an array of (dn, attrlist) tuples. |
| 649 |
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) |
645 |
dn = result[0][0] |
| 650 |
conn.bind(luser, oldp) |
646 |
|
| 651 |
conn.modify(luser, [replace])]]></programlisting> |
647 |
# Now we get the user's old password, and bind to the server with it |
|
|
648 |
# and his DN. If it succeeds, he (and we) have the proper credentials to |
| 649 |
# change his password. |
| 650 |
passwd = getpass("current password: ") |
| 651 |
try: |
| 652 |
ldapobj.simple_bind_s(dn, passwd) |
| 653 |
except ldap.INVALID_CREDENTIALS: |
| 654 |
print "Bad password." |
| 655 |
sys.exit(1) |
| 656 |
|
| 657 |
# Get and confirm new password. |
| 658 |
npass1 = 'a' |
| 659 |
npass2 = 'b' |
| 660 |
while npass1 != npass2: |
| 661 |
npass1 = getpass("new password: ") |
| 662 |
npass2 = getpass("new password (again): ") |
| 663 |
|
| 664 |
# This is the key. This uses the LDAP Password Modify Extended Operation. |
| 665 |
# It is important to use this when you can, although not all libraries |
| 666 |
# (e.g. ruby-ldap) support it. See rfc3062. |
| 667 |
ldapobj.passwd_s(dn, passwd, npass1) |
| 668 |
|
| 669 |
# And we're done. |
| 670 |
ldapobj.unbind()]]></programlisting> |
| 652 |
</example> |
671 |
</example> |
| 653 |
|
672 |
|
| 654 |
<para>Although not guaranteed to be free of security holes (the |
673 |
<para>Although not guaranteed to be free of security holes (the |
|
Lines 759-765
Link Here
|
| 759 |
<title>Creating a management group</title> |
778 |
<title>Creating a management group</title> |
| 760 |
|
779 |
|
| 761 |
<programlisting>dn: cn=homemanagement,dc=example,dc=org |
780 |
<programlisting>dn: cn=homemanagement,dc=example,dc=org |
| 762 |
objectClass: top |
|
|
| 763 |
objectClass: posixGroup |
781 |
objectClass: posixGroup |
| 764 |
cn: homemanagement |
782 |
cn: homemanagement |
| 765 |
gidNumber: 121 # required for posixGroup |
783 |
gidNumber: 121 # required for posixGroup |