Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 20 Mar 2009 01:10:02 -0400 (EDT)
From:      Toby Burress <kurin@delete.org>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   docs/132839: Fix example script in ldap-auth
Message-ID:  <20090320051002.C1F197E83F@lithium.delete.org>
Resent-Message-ID: <200903200530.n2K5U1Xp021468@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         132839
>Category:       docs
>Synopsis:       Fix example script in ldap-auth
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-doc
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          maintainer-update
>Submitter-Id:   current-users
>Arrival-Date:   Fri Mar 20 05:30:01 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator:     Toby Burress
>Release:        FreeBSD 6.3-RELEASE-p2 amd64
>Organization:
>Environment:
System: FreeBSD lithium.delete.org 6.3-RELEASE-p2 FreeBSD 6.3-RELEASE-p2 #2: Sun May 4 03:12:43 EDT 2008 root@lithium.delete.org:/usr/obj/usr/src/sys/LITHIUMv3 amd64


>Description:
	This article (it turns out) has a few errors, two of which
	are fixed in this patch.

	(a) some object classes given in the examples are implicit
	as they are the parents of other object classes explicitly
	named, and it is apparently gouche to include them.
	Specifically "top", maybe others.

	(b) the Ruby script that was here before used an LDAP Modify
	operation to change a user's password.  This is not the
	Done Thing for a number of reasons which interested parties
	can read about in RFC 3062.  It turns out the entire ruby-ldap
	library is not very well maintained and does not support
	the proper operation.  The example has been replaced with
	a Python script that implements the proper procedure.

see http://lists.freebsd.org/pipermail/freebsd-doc/2008-November/015026.html for more info

>How-To-Repeat:
>Fix:

patch:

--- patch begins here ---
--- article.sgml.old	2009-03-20 00:57:22.000000000 -0400
+++ article.sgml	2009-03-20 01:03:08.000000000 -0400
@@ -307,7 +307,6 @@
 	organizational unit will look like:</para>
 
       <programlisting>dn: ou=people,dc=example,dc=org
-objectClass: top
 objectClass: organizationalUnit
 ou: people</programlisting>
 
@@ -336,7 +335,6 @@
 objectClass: person
 objectClass: posixAccount
 objectClass: shadowAccount
-objectClass: top
 uidNumber: 10000
 gidNumber: 10000
 homeDirectory: /home/tuser
@@ -352,13 +350,11 @@
 	user entries, but we will use the defaults below:</para>
 
       <programlisting>dn: ou=groups,dc=example,dc=org
-objectClass: top
 objectClass: organizationalUnit
 ou: groups
 
 dn: cn=tuser,ou=groups,dc=example,dc=org
 objectClass: posixGroup
-objectClass: top
 gidNumber: 10000
 cn: tuser</programlisting>
 
@@ -604,51 +600,74 @@
 	<screen>&prompt.root; <userinput>sysctl security.bsd.see_other_uids=0</userinput>.</screen>
       </caution>
 
-      <para>A more flexible (and probably more secure) approach can be
-	used by writing a custom program, or even a web interface.  The
-	following is part of a <application>Ruby</application> library
-	that can change LDAP passwords.  It sees use both on the command
-	line, and on the web.</para>
+  <para>A more flexible (and probably more secure) approach can be
+	used by writing a custom program, or even a web interface.
+	The following is modeled on a <application>Python</application>
+	library that can change LDAP passwords.  It sees use both
+	on the command line, and on the web.</para>
 
-      <example id="chpw-ruby">
-	<title>Ruby script for changing passwords</title>
+      <example id="chpw-python">
+	<title>Python script for changing passwords</title>
         
-	<programlisting><![CDATA[require 'ldap'
-require 'base64'
-require 'digest'
-require 'password' # ruby-password
-
-ldap_server = "ldap.example.org"
-luser = "uid=#{ENV['USER']},ou=people,dc=example,dc=org"
-
-# get the new password, check it, and create a salted hash from it
-def get_password
-  pwd1 = Password.get("New Password: ")
-  pwd2 = Password.get("Retype New Password: ")
-
-  raise if pwd1 != pwd2
-  pwd1.check # check password strength
-  
-  salt = rand.to_s.gsub(/0\./, '')
-  pass = pwd1.to_s
-  hash = "{SSHA}"+Base64.encode64(Digest::SHA1.digest("#{pass}#{salt}")+salt).chomp!
-  return hash
-end
-
-oldp = Password.get("Old Password: ")
-newp = get_password
-
-# We'll just replace it.  That we can bind proves that we either know
-# the old password or are an admin.
-
-replace = LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE | LDAP::LDAP_MOD_BVALUES,
-                        "userPassword",
-                        [newp])
-
-conn = LDAP::SSLConn.new(ldap_server, 389, true)
-conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
-conn.bind(luser, oldp)
-conn.modify(luser, [replace])]]></programlisting>
+	<programlisting><![CDATA[import ldap # python-ldap
+import os, sys
+from getpass import getpass
+
+uri = "ldap://ldap1.dimins.com"
+searchbase = "ou=people,dc=dimins,dc=com"
+filter = "(&(objectClass=posixAccount)(uid=%s))"
+
+# get the username; if none is given, use the current user
+user = os.environ['USER']
+if len(sys.argv) > 1:
+    user = sys.argv[1]
+
+ldapobj = ldap.initialize(uri)
+ldapobj.start_tls_s() # this is pretty important
+
+# Get the users DN, and then bind as that.
+# The way to do this is first bind anonymously (if you don't allow anon
+# binds, there's probably some standard account you use for this.
+ldapobj.simple_bind_s()
+
+# Search for a user with the uid we gave.  We search everything under
+# the "base" we configure above (as there may be other users with the same
+# UID elsewhere in the tree; we don't want to return those.
+result = ldapobj.search_s(searchbase, ldap.SCOPE_SUBTREE, filter%user)
+
+if len(result) > 1:
+    # This is kind of suspicious; we only want one user.
+    print "I found several users that match that user id."
+    print "Talk to your sysadmin."
+    sys.exit(1)
+
+# The results are an array of (dn, attrlist) tuples.
+dn = result[0][0]
+
+# Now we get the user's old password, and bind to the server with it
+# and his DN.  If it succeeds, he (and we) have the proper credentials to
+# change his password.
+passwd = getpass("current password: ")
+try:
+    ldapobj.simple_bind_s(dn, passwd)
+except ldap.INVALID_CREDENTIALS:
+    print "Bad password."
+    sys.exit(1)
+
+# Get and confirm new password.
+npass1 = 'a'
+npass2 = 'b'
+while npass1 != npass2:
+    npass1 = getpass("new password: ")
+    npass2 = getpass("new password (again): ")
+
+# This is the key.  This uses the LDAP Password Modify Extended Operation.
+# It is important to use this when you can, although not all libraries
+# (e.g. ruby-ldap) support it.  See rfc3062.
+ldapobj.passwd_s(dn, passwd, npass1)
+
+# And we're done.
+ldapobj.unbind()]]></programlisting>
       </example>
 
       <para>Although not guaranteed to be free of security holes (the
@@ -759,7 +778,6 @@
 	  <title>Creating a management group</title>
 
 	  <programlisting>dn: cn=homemanagement,dc=example,dc=org
-objectClass: top
 objectClass: posixGroup
 cn: homemanagement
 gidNumber: 121 # required for posixGroup
--- patch ends here ---


>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20090320051002.C1F197E83F>