Browse files

Add missing tag at release 0.0.1

  • Loading branch information...
2 parents c56df15 + d4b093e commit 479c982796a66cc63a6a9b0aaf51ce315f7e2ff9 emiel committed Sep 18, 2008
View
272 COPYING
@@ -0,0 +1,272 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street,
+Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and
+distribute verbatim copies of this license document, but changing it is not
+allowed.
+
+ Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public
+License applies to most of the Free Software Foundation's software and to
+any other program whose authors commit to using it. (Some other Free
+Software Foundation software is covered by the GNU Lesser General Public
+License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you
+wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These
+restrictions translate to certain responsibilities for you if you distribute
+copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And you
+must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its
+recipients to know that what they have is not the original, so that any
+problems introduced by others will not reflect on the original authors'
+reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will
+individually obtain patent licenses, in effect making the program
+proprietary. To prevent this, we have made it clear that any patent must be
+licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+ placed by the copyright holder saying it may be distributed under the
+ terms of this General Public License. The "Program", below, refers to
+ any such program or work, and a "work based on the Program" means either
+ the Program or any derivative work under copyright law: that is to say, a
+ work containing the Program or a portion of it, either verbatim or with
+ modifications and/or translated into another language. (Hereinafter,
+ translation is included without limitation in the term "modification".)
+ Each licensee is addressed as "you".
+
+ Activities other than copying, distribution and modification are not
+ covered by this License; they are outside its scope. The act of running
+ the Program is not restricted, and the output from the Program is covered
+ only if its contents constitute a work based on the Program (independent
+ of having been made by running the Program). Whether that is true depends
+ on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code
+ as you receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright notice and
+ disclaimer of warranty; keep intact all the notices that refer to this
+ License and to the absence of any warranty; and give any other recipients
+ of the Program a copy of this License along with the Program.
+
+ You may charge a fee for the physical act of transferring a copy, and you
+ may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it,
+ thus forming a work based on the Program, and copy and distribute such
+ modifications or work under the terms of Section 1 above, provided that
+ you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices stating
+ that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in whole
+ or in part contains or is derived from the Program or any part
+ thereof, to be licensed as a whole at no charge to all third parties
+ under the terms of this License.
+
+ c) If the modified program normally reads commands interactively when
+ run, you must cause it, when started running for such interactive use
+ in the most ordinary way, to print or display an announcement
+ including an appropriate copyright notice and a notice that there is
+ no warranty (or else, saying that you provide a warranty) and that
+ users may redistribute the program under these conditions, and telling
+ the user how to view a copy of this License. (Exception: if the
+ Program itself is interactive but does not normally print such an
+ announcement, your work based on the Program is not required to print
+ an announcement.)
+
+ These requirements apply to the modified work as a whole. If
+ identifiable sections of that work are not derived from the Program, and
+ can be reasonably considered independent and separate works in
+ themselves, then this License, and its terms, do not apply to those
+ sections when you distribute them as separate works. But when you
+ distribute the same sections as part of a whole which is a work based on
+ the Program, the distribution of the whole must be on the terms of this
+ License, whose permissions for other licensees extend to the entire
+ whole, and thus to each and every part regardless of who wrote it.
+
+ Thus, it is not the intent of this section to claim rights or contest
+ your rights to work written entirely by you; rather, the intent is to
+ exercise the right to control the distribution of derivative or
+ collective works based on the Program.
+
+ In addition, mere aggregation of another work not based on the Program
+ with the Program (or with a work based on the Program) on a volume of a
+ storage or distribution medium does not bring the other work under the
+ scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+ Section 2) in object code or executable form under the terms of Sections
+ 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable source
+ code, which must be distributed under the terms of Sections 1 and 2
+ above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three years, to
+ give any third party, for a charge no more than your cost of
+ physically performing source distribution, a complete machine-readable
+ copy of the corresponding source code, to be distributed under the
+ terms of Sections 1 and 2 above on a medium customarily used for
+ software interchange; or,
+
+ c) Accompany it with the information you received as to the offer to
+ distribute corresponding source code. (This alternative is allowed
+ only for noncommercial distribution and only if you received the
+ program in object code or executable form with such an offer, in
+ accord with Subsection b above.)
+
+ The source code for a work means the preferred form of the work for
+ making modifications to it. For an executable work, complete source code
+ means all the source code for all modules it contains, plus any
+ associated interface definition files, plus the scripts used to control
+ compilation and installation of the executable. However, as a special
+ exception, the source code distributed need not include anything that is
+ normally distributed (in either source or binary form) with the major
+ components (compiler, kernel, and so on) of the operating system on which
+ the executable runs, unless that component itself accompanies the
+ executable.
+
+ If distribution of executable or object code is made by offering access
+ to copy from a designated place, then offering equivalent access to copy
+ the source code from the same place counts as distribution of the source
+ code, even though third parties are not compelled to copy the source
+ along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+ expressly provided under this License. Any attempt otherwise to copy,
+ modify, sublicense or distribute the Program is void, and will
+ automatically terminate your rights under this License. However, parties
+ who have received copies, or rights, from you under this License will not
+ have their licenses terminated so long as such parties remain in full
+ compliance.
+
+5. You are not required to accept this License, since you have not signed
+ it. However, nothing else grants you permission to modify or distribute
+ the Program or its derivative works. These actions are prohibited by law
+ if you do not accept this License. Therefore, by modifying or
+ distributing the Program (or any work based on the Program), you indicate
+ your acceptance of this License to do so, and all its terms and
+ conditions for copying, distributing or modifying the Program or works
+ based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+ Program), the recipient automatically receives a license from the
+ original licensor to copy, distribute or modify the Program subject to
+ these terms and conditions. You may not impose any further restrictions
+ on the recipients' exercise of the rights granted herein. You are not
+ responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+ infringement or for any other reason (not limited to patent issues),
+ conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License. If you cannot distribute
+ so as to satisfy simultaneously your obligations under this License and
+ any other pertinent obligations, then as a consequence you may not
+ distribute the Program at all. For example, if a patent license would
+ not permit royalty-free redistribution of the Program by all those who
+ receive copies directly or indirectly through you, then the only way you
+ could satisfy both it and this License would be to refrain entirely from
+ distribution of the Program.
+
+ If any portion of this section is held invalid or unenforceable under any
+ particular circumstance, the balance of the section is intended to apply
+ and the section as a whole is intended to apply in other circumstances.
+
+ It is not the purpose of this section to induce you to infringe any
+ patents or other property right claims or to contest validity of any such
+ claims; this section has the sole purpose of protecting the integrity of
+ the free software distribution system, which is implemented by public
+ license practices. Many people have made generous contributions to the
+ wide range of software distributed through that system in reliance on
+ consistent application of that system; it is up to the author/donor to
+ decide if he or she is willing to distribute software through any other
+ system and a licensee cannot impose that choice.
+
+ This section is intended to make thoroughly clear what is believed to be
+ a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+ countries either by patents or by copyrighted interfaces, the original
+ copyright holder who places the Program under this License may add an
+ explicit geographical distribution limitation excluding those countries,
+ so that distribution is permitted only in or among countries not thus
+ excluded. In such case, this License incorporates the limitation as if
+ written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of
+ the General Public License from time to time. Such new versions will be
+ similar in spirit to the present version, but may differ in detail to
+ address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the Program
+ specifies a version number of this License which applies to it and "any
+ later version", you have the option of following the terms and conditions
+ either of that version or of any later version published by the Free
+ Software Foundation. If the Program does not specify a version number of
+ this License, you may choose any version ever published by the Free
+ Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+ whose distribution conditions are different, write to the author to ask
+ for permission. For software which is copyrighted by the Free Software
+ Foundation, write to the Free Software Foundation; we sometimes make
+ exceptions for this. Our decision will be guided by the two goals of
+ preserving the free status of all derivatives of our free software and
+ of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+ THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+ EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+ YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+ NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+ DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+ DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+ (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+ INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+ THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+ OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
View
13 CVSROOT/checkoutlist
@@ -1,13 +0,0 @@
-# The "checkoutlist" file is used to support additional version controlled
-# administrative files in $CVSROOT/CVSROOT, such as template files.
-#
-# The first entry on a line is a filename which will be checked out from
-# the corresponding RCS file in the $CVSROOT/CVSROOT directory.
-# The remainder of the line is an error message to use if the file cannot
-# be checked out.
-#
-# File format:
-#
-# [<whitespace>]<filename><whitespace><error message><end-of-line>
-#
-# comment lines begin with '#'
View
15 CVSROOT/commitinfo
@@ -1,15 +0,0 @@
-# The "commitinfo" file is used to control pre-commit checks.
-# The filter on the right is invoked with the repository and a list
-# of files to check. A non-zero exit of the filter program will
-# cause the commit to be aborted.
-#
-# The first entry on a line is a regular expression which is tested
-# against the directory that the change is being committed to, relative
-# to the $CVSROOT. For the first match that is found, then the remainder
-# of the line is the name of the filter to run.
-#
-# If the repository name does not match any of the regular expressions in this
-# file, the "DEFAULT" line is used, if it is specified.
-#
-# If the name "ALL" appears as a regular expression it is always used
-# in addition to the first matching regex or "DEFAULT".
View
21 CVSROOT/config
@@ -1,21 +0,0 @@
-# Set this to "no" if pserver shouldn't check system users/passwords
-#SystemAuth=no
-
-# Put CVS lock files in this directory rather than directly in the repository.
-LockDir=/var/cvslocks/
-
-# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top
-# level of the new working directory when using the `cvs checkout'
-# command.
-#TopLevelAdmin=no
-
-# Set `LogHistory' to `all' or `TOFEWGCMAR' to log all transactions to the
-# history file, or a subset as needed (ie `TMAR' logs all write operations)
-#LogHistory=TOFEWGCMAR
-
-# Set `RereadLogAfterVerify' to `always' (the default) to allow the verifymsg
-# script to change the log message. Set it to `stat' to force CVS to verify# that the file has changed before reading it (this can take up to an extra
-# second per directory being committed, so it is not recommended for large
-# repositories. Set it to `never' (the previous CVS behavior) to prevent
-# verifymsg scripts from changing the log message.
-#RereadLogAfterVerify=always
View
75 CVSROOT/cvswrappers
@@ -1,75 +0,0 @@
-# This file affects handling of files based on their names.
-#
-# The -m option specifies whether CVS attempts to merge files.
-#
-# The -k option specifies keyword expansion (e.g. -kb for binary).
-#
-# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
-#
-# wildcard [option value][option value]...
-#
-# where option is one of
-# -f from cvs filter value: path to filter
-# -t to cvs filter value: path to filter
-# -m update methodology value: MERGE or COPY
-# -k expansion mode value: b, o, kkv, &c
-#
-# and value is a single-quote delimited value.
-# For example:
-#*.gif -k 'b'
-*.JPG -k 'b' -m 'COPY'
-*.avi -k 'b' -m 'COPY'
-*.bin -k 'b' -m 'COPY'
-*.bz -k 'b' -m 'COPY'
-*.bz2 -k 'b' -m 'COPY'
-*.doc -k 'b' -m 'COPY'
-*.exe -k 'b' -m 'COPY'
-*.gif -k 'b' -m 'COPY'
-*.gz -k 'b' -m 'COPY'
-*.hqx -k 'b' -m 'COPY'
-*.jar -k 'b' -m 'COPY'
-*.jpeg -k 'b' -m 'COPY'
-*.jpg -k 'b' -m 'COPY'
-*.mov -k 'b' -m 'COPY'
-*.mp3 -k 'b' -m 'COPY'
-*.mpg -k 'b' -m 'COPY'
-*.pdf -k 'b' -m 'COPY'
-*.png -k 'b' -m 'COPY'
-*.ppt -k 'b' -m 'COPY'
-*.rpm -k 'b' -m 'COPY'
-*.sit -k 'b' -m 'COPY'
-*.srpm -k 'b' -m 'COPY'
-*.swf -k 'b' -m 'COPY'
-*.tar -k 'b' -m 'COPY'
-*.tbz -k 'b' -m 'COPY'
-*.tgz -k 'b' -m 'COPY'
-*.tif -k 'b' -m 'COPY'
-*.tiff -k 'b' -m 'COPY'
-*.xbm -k 'b' -m 'COPY'
-*.xls -k 'b' -m 'COPY'
-*.zip -k 'b' -m 'COPY'
-*.keystore -k 'b' -m 'COPY'
-# File types added by Borland JBuilder
-*.aiff -k 'b'
-*.midi -k 'b'
-*.jds -k 'b'
-*.asx -k 'b'
-*.wav -k 'b'
-*.war -k 'b'
-*.ear -k 'b'
-*.obj -k 'b'
-*.dcu -k 'b'
-*.class -k 'b'
-*.au -k 'b'
-*.eargrp -k 'b'
-*.jpe -k 'b'
-*.o -k 'b'
-*.asf -k 'b'
-*.rar -k 'b'
-*.library -k 'b'
-*.ejbgrp -k 'b'
-*.ram -k 'b'
-*.ejbgrpx -k 'b'
-*.mid -k 'b'
-*.rmf -k 'b'
-
View
21 CVSROOT/editinfo
@@ -1,21 +0,0 @@
-# The "editinfo" file is used to allow verification of logging
-# information. It works best when a template (as specified in the
-# rcsinfo file) is provided for the logging procedure. Given a
-# template with locations for, a bug-id number, a list of people who
-# reviewed the code before it can be checked in, and an external
-# process to catalog the differences that were code reviewed, the
-# following test can be applied to the code:
-#
-# Making sure that the entered bug-id number is correct.
-# Validating that the code that was reviewed is indeed the code being
-# checked in (using the bug-id number or a seperate review
-# number to identify this particular code set.).
-#
-# If any of the above test failed, then the commit would be aborted.
-#
-# Actions such as mailing a copy of the report to each reviewer are
-# better handled by an entry in the loginfo file.
-#
-# One thing that should be noted is the the ALL keyword is not
-# supported. There can be only one entry that matches a given
-# repository.
View
27 CVSROOT/loginfo
@@ -1,27 +0,0 @@
-# The "loginfo" file controls where "cvs commit" log information
-# is sent. The first entry on a line is a regular expression which must match
-# the directory that the change is being made to, relative to the
-# $CVSROOT. If a match is found, then the remainder of the line is a filter
-# program that should expect log information on its standard input.
-#
-# If the repository name does not match any of the regular expressions in this
-# file, the "DEFAULT" line is used, if it is specified.
-#
-# If the name ALL appears as a regular expression it is always used
-# in addition to the first matching regex or DEFAULT.
-#
-# You may specify a format string as part of the
-# filter. The string is composed of a `%' followed
-# by a single format character, or followed by a set of format
-# characters surrounded by `{' and `}' as separators. The format
-# characters are:
-#
-# s = file name
-# V = old version number (pre-checkin)
-# v = new version number (post-checkin)
-#
-# For example:
-#DEFAULT (echo ""; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog
-# or
-#DEFAULT (echo ""; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog
-DEFAULT /var/cvs/commitmailer.sh %s tom@infoether.com
View
26 CVSROOT/modules
@@ -1,26 +0,0 @@
-# Three different line formats are valid:
-# key -a aliases...
-# key [options] directory
-# key [options] directory files...
-#
-# Where "options" are composed of:
-# -i prog Run "prog" on "cvs commit" from top-level of module.
-# -o prog Run "prog" on "cvs checkout" of module.
-# -e prog Run "prog" on "cvs export" of module.
-# -t prog Run "prog" on "cvs rtag" of module.
-# -u prog Run "prog" on "cvs update" of module.
-# -d dir Place module in directory "dir" instead of module name.
-# -l Top-level directory only -- do not recurse.
-#
-# NOTE: If you change any of the "Run" options above, you'll have to
-# release and re-checkout any working directories of these modules.
-#
-# And "directory" is a path to a directory relative to $CVSROOT.
-#
-# The "-a" option specifies an alias. An alias is interpreted as if
-# everything on the right of the "-a" had been typed on the command line.
-#
-# You can encode a module within a module by using the special '&'
-# character to interpose another module into the current module. This
-# can be useful for creating a module that consists of many directories
-# spread out over the entire source repository.
View
12 CVSROOT/notify
@@ -1,12 +0,0 @@
-# The "notify" file controls where notifications from watches set by
-# "cvs watch add" or "cvs edit" are sent. The first entry on a line is
-# a regular expression which is tested against the directory that the
-# change is being made to, relative to the $CVSROOT. If it matches,
-# then the remainder of the line is a filter program that should contain
-# one occurrence of %s for the user to notify, and information on its
-# standard input.
-#
-# "ALL" or "DEFAULT" can be used in place of the regular expression.
-#
-# For example:
-#ALL mail -s "CVS notification" %s
View
1 CVSROOT/passwd
@@ -1 +0,0 @@
-anonymous:$1$0H$2/LSjjwDfsSA0gaDYY5Df/:tutorials
View
13 CVSROOT/rcsinfo
@@ -1,13 +0,0 @@
-# The "rcsinfo" file is used to control templates with which the editor
-# is invoked on commit and import.
-#
-# The first entry on a line is a regular expression which is tested
-# against the directory that the change is being made to, relative to the
-# $CVSROOT. For the first match that is found, then the remainder of the
-# line is the name of the file that contains the template.
-#
-# If the repository name does not match any of the regular expressions in this
-# file, the "DEFAULT" line is used, if it is specified.
-#
-# If the name "ALL" appears as a regular expression it is always used
-# in addition to the first matching regex or "DEFAULT".
View
2 CVSROOT/readers
@@ -1,2 +0,0 @@
-anonymous::anonymous
-
View
20 CVSROOT/taginfo
@@ -1,20 +0,0 @@
-# The "taginfo" file is used to control pre-tag checks.
-# The filter on the right is invoked with the following arguments:
-#
-# $1 -- tagname
-# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d
-# $3 -- repository
-# $4-> file revision [file revision ...]
-#
-# A non-zero exit of the filter program will cause the tag to be aborted.
-#
-# The first entry on a line is a regular expression which is tested
-# against the directory that the change is being committed to, relative
-# to the $CVSROOT. For the first match that is found, then the remainder
-# of the line is the name of the filter to run.
-#
-# If the repository name does not match any of the regular expressions in this
-# file, the "DEFAULT" line is used, if it is specified.
-#
-# If the name "ALL" appears as a regular expression it is always used
-# in addition to the first matching regex or "DEFAULT".
View
21 CVSROOT/verifymsg
@@ -1,21 +0,0 @@
-# The "verifymsg" file is used to allow verification of logging
-# information. It works best when a template (as specified in the
-# rcsinfo file) is provided for the logging procedure. Given a
-# template with locations for, a bug-id number, a list of people who
-# reviewed the code before it can be checked in, and an external
-# process to catalog the differences that were code reviewed, the
-# following test can be applied to the code:
-#
-# Making sure that the entered bug-id number is correct.
-# Validating that the code that was reviewed is indeed the code being
-# checked in (using the bug-id number or a seperate review
-# number to identify this particular code set.).
-#
-# If any of the above test failed, then the commit would be aborted.
-#
-# Actions such as mailing a copy of the report to each reviewer are
-# better handled by an entry in the loginfo file.
-#
-# One thing that should be noted is the the ALL keyword is not
-# supported. There can be only one entry that matches a given
-# repository.
View
28 ChangeLog
@@ -0,0 +1,28 @@
+= Net::LDAP Changelog
+
+== Net::LDAP 0.0.1: May 1, 2006
+* Initial release.
+* Client functionality is near-complete, although the APIs
+ are not guaranteed and may change depending on feedback
+ from the community.
+* We're internally working on a Ruby-based implementation
+ of a full-featured, production-quality LDAP server,
+ which will leverage the underlying LDAP and BER functionality
+ in Net::LDAP.
+* Please tell us if you would be interested in seeing a public
+ release of the LDAP server.
+* Grateful acknowledgement to Austin Ziegler, who reviewed
+ this code and provided the release framework, including
+ minitar.
+
+#--
+# Net::LDAP for Ruby.
+# http://rubyforge.org/projects/net-ldap/
+# Copyright (C) 2006 by Francis Cianfrocca
+#
+# Available under the same terms as Ruby. See LICENCE in the main
+# distribution for full licensing information.
+#
+# $Id: ChangeLog,v 1.17.2.4 2005/09/09 12:36:42 austin Exp $
+#++
+# vim: sts=2 sw=2 ts=4 et ai tw=77
View
21 Install
@@ -0,0 +1,21 @@
+Net::LDAP is a pure Ruby LDAP client. It does not as yet require any external
+libraries. It can be installed with:
+
+ % ruby setup.rb
+
+Alternatively, you can use the RubyGems version of Net::LDAP availalble as
+ruby-net-ldap-0.0.1.gem from the usual sources.
+
+Net::LDAP:: http://rubyforge.org/projects/net-ldap/
+
+#--
+# Net::LDAP for Ruby.
+# http://rubyforge.org/projects/net-ldap/
+# Copyright (C) 2006 by Francis Cianfrocca
+#
+# Available under the same terms as Ruby. See LICENCE in the main
+# distribution for full licensing information.
+#
+# $Id: ChangeLog,v 1.17.2.4 2005/09/09 12:36:42 austin Exp $
+#++
+# vim: sts=2 sw=2 ts=4 et ai tw=77
View
55 LICENCE
@@ -0,0 +1,55 @@
+Net::LDAP is copyrighted free software by Francis Cianfrocca
+<garbagecat10@gmail.com>. You can redistribute it and/or modify it under either
+the terms of the GPL (see the file COPYING), or the conditions below:
+
+1. You may make and give away verbatim copies of the source form of the
+ software without restriction, provided that you duplicate all of the
+ original copyright notices and associated disclaimers.
+
+2. You may modify your copy of the software in any way, provided that you do
+ at least ONE of the following:
+
+ a) place your modifications in the Public Domain or otherwise make them
+ Freely Available, such as by posting said modifications to Usenet or
+ an equivalent medium, or by allowing the author to include your
+ modifications in the software.
+
+ b) use the modified software only within your corporation or
+ organization.
+
+ c) rename any non-standard executables so the names do not conflict with
+ standard executables, which must also be provided.
+
+ d) make other distribution arrangements with the author.
+
+3. You may distribute the software in object code or executable form,
+ provided that you do at least ONE of the following:
+
+ a) distribute the executables and library files of the software, together
+ with instructions (in the manual page or equivalent) on where to get
+ the original distribution.
+
+ b) accompany the distribution with the machine-readable source of the
+ software.
+
+ c) give non-standard executables non-standard names, with instructions on
+ where to get the original software distribution.
+
+ d) make other distribution arrangements with the author.
+
+4. You may modify and include the part of the software into any other
+ software (possibly commercial). But some files in the distribution are
+ not written by the author, so that they are not under this terms.
+
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
+ files under the ./missing directory. See each file for the copying
+ condition.
+
+5. The scripts and library files supplied as input to or produced as output
+ from the software do not automatically fall under the copyright of the
+ software, but belong to whomever generated them, and may be sold
+ commercially, and may be aggregated with this software.
+
+6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
View
29 README
@@ -0,0 +1,29 @@
+= Net::LDAP for Ruby
+Net::LDAP is an LDAP support library written in pure Ruby. It supports all
+LDAP client features, and a subset of server features as well.
+
+Homepage:: http://rubyforge.org/projects/net-ldap/
+Copyright:: 2006 by Francis Cianfrocca
+
+Original developer: Francis Cianfrocca
+Contributions by Austin Ziegler gratefully acknowledged.
+
+== LICENCE NOTES
+Please read the file LICENCE for licensing restrictions on this library. In
+it simplest terms, this library is available under the same terms as Ruby
+itself.
+
+== Requirements
+Net::LDAP requires Ruby 1.8.2 or better.
+
+#--
+# Net::LDAP for Ruby.
+# http://rubyforge.org/projects/net-ldap/
+# Copyright (C) 2006 by Francis Cianfrocca
+#
+# Available under the same terms as Ruby. See LICENCE in the main
+# distribution for full licensing information.
+#
+# $Id$
+#++
+# vim: sts=2 sw=2 ts=4 et ai tw=77
View
229 Rakefile
@@ -0,0 +1,229 @@
+#! /usr/bin/env rake
+#--
+# Net::LDAP for Ruby.
+# http://rubyforge.org/projects/net-ldap/
+# Copyright (C) 2006 by Francis Cianfrocca
+#
+# Available under the same terms as Ruby. See LICENCE in the main
+# distribution for full licensing information.
+#
+# $Id$
+#++
+
+require 'meta_project'
+require 'rake/gempackagetask'
+require 'rake/contrib/xforge'
+require 'rake/clean'
+
+$can_gmail = false
+begin
+ require 'gmailer'
+ $can_gmail = true
+rescue LoadError
+end
+
+$can_minitar = false
+begin
+ require 'archive/tar/minitar'
+ require 'zlib'
+ $can_minitar = true
+rescue LoadError
+end
+
+$LOAD_PATH.unshift "lib"
+require 'net/ldap'
+
+$version = Net::LDAP::VERSION
+$name = Net::LDAP.to_s
+$project = MetaProject::Project::XForge::RubyForge.new('net-ldap')
+$distdir = "ruby-net-ldap-#$version"
+$tardist = "../#$distdir.tar.gz"
+
+$release_date = nil
+$release_date = Time.parse(ENV['RELEASE_DATE']) if ENV['RELEASE_DATE']
+
+desc "Run the tests for #$name."
+task :test do |t|
+ require 'test/unit/testsuite'
+ require 'test/unit/ui/console/testrunner'
+
+ runner = Test::Unit::UI::Console::TestRunner
+
+ $LOAD_PATH.unshift('tests')
+ $stderr.puts "Checking for test cases:" if t.verbose
+ Dir['tests/tc_*.rb'].each do |testcase|
+ $stderr.puts "\t#{testcase}" if t.verbose
+ load testcase
+ end
+
+ suite = Test::Unit::TestSuite.new($name)
+
+ ObjectSpace.each_object(Class) do |testcase|
+ suite << testcase.suite if testcase < Test::Unit::TestCase
+ end
+
+ runner.run(suite)
+end
+
+spec = eval(File.read("net-ldap.gemspec"))
+spec.version = $version
+desc "Build the RubyGem for #$name."
+task :gem => [ :test ]
+Rake::GemPackageTask.new(spec) do |g|
+ if $can_minitar
+ g.need_tar = false
+ g.need_zip = false
+ end
+ g.package_dir = ".."
+end
+
+if $can_minitar
+ desc "Build a #$name .tar.gz distribution."
+ task :tar => [ $tardist ]
+ file $tardist => [ :test ] do |t|
+ current = File.basename(Dir.pwd)
+ Dir.chdir("..") do
+ begin
+ files = %W(bin/**/* lib/**/* tests/**/* ChangeLog README LICENCE
+ COPYING Rakefile net-ldap.gemspec setup.rb pre-setup.rb)
+ files = FileList[files.map { |file| File.join(current, file) }].to_a
+ files.map! do |dd|
+ ddnew = dd.gsub(/^#{current}/, $distdir)
+ mtime = $release_date || File.stat(dd).mtime
+ if File.directory?(dd)
+ { :name => ddnew, :mode => 0755, :dir => true, :mtime => mtime }
+ else
+ if dd =~ %r{bin/}
+ mode = 0755
+ else
+ mode = 0644
+ end
+ data = File.open(dd, "rb") { |ff| ff.read }
+ { :name => ddnew, :mode => mode, :data => data, :size =>
+ data.size, :mtime => mtime }
+ end
+ end
+
+ ff = File.open(t.name.gsub(%r{^\.\./}o, ''), "wb")
+ gz = Zlib::GzipWriter.new(ff)
+ tw = Archive::Tar::Minitar::Writer.new(gz)
+
+ files.each do |entry|
+ if entry[:dir]
+ tw.mkdir(entry[:name], entry)
+ else
+ tw.add_file_simple(entry[:name], entry) { |os| os.write(entry[:data]) }
+ end
+ end
+ ensure
+ tw.close if tw
+ gz.finish if gz
+ ff.close
+ end
+ end
+ end
+ task $tardist => [ :test ]
+end
+
+desc "Build the RDoc documentation for #$name."
+task :docs do
+ require 'rdoc/rdoc'
+ rdoc_options = %W(--title #$name --main README --line-numbers)
+ files = FileList[*%w(README LICENCE ChangeLog LICENCE bin/**/*.rb lib/**/*.rb)]
+ rdoc_options += files.to_a
+ RDoc::RDoc.new.document(rdoc_options)
+end
+
+task :verify_rubyforge do
+ raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
+ raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD']
+end
+
+if $can_gmail
+ task :verify_gmail do
+ raise "GMAIL_USER environment variable not set!" unless ENV['GMAIL_USER']
+ raise "GMAIL_PASSWORD environment variable not set!" unless ENV['GMAIL_PASSWORD']
+ end
+
+ desc "Post a release announcement via GMail."
+ task :email_announcement => [ :verify_gmail ] do
+ GMailer.connect(ENV["GMAIL_USER"], ENV["GMAIL_PASSWORD"]) do |gmail|
+ msg = {
+ :to => "ruby-talk@ruby-lang.org, #{ENV['GMAIL_USER']}@gmail.com",
+ :subject => "[ANN] #$name #$version",
+ :body => File.read("Release-Announcement"),
+ }
+ gmail.send msg
+ end
+ end
+end
+
+desc "Release files on RubyForge."
+task :release_files => [ :verify_rubyforge, :gem ] do
+ release_files = FileList[$tardist, "../#$distdir.gem"]
+ Rake::XForge::Release.new($project) do |release|
+ release.user_name = ENV['RUBYFORGE_USER']
+ release.password = ENV['RUBYFORGE_PASSWORD']
+ release.files = release_files.to_a
+ release.release_name = "#$name #$version"
+ release.package_name = "ruby-net-ldap"
+
+ notes = []
+ File.open("README") do |file|
+ file.each do |line|
+ line.chomp!
+ line.gsub!(/^#.*$/, '') and next
+ notes << line
+ end
+ end
+ release.release_notes = notes.join("\n")
+
+ changes = []
+ File.open("ChangeLog") do |file|
+ current = true
+
+ file.each do |line|
+ line.chomp!
+ current = false if current and line =~ /^==/
+ break if line.empty? and not current
+ changes << line
+ end
+ end
+ release.release_changes = changes.join("\n")
+ end
+end
+
+desc "Publish news on RubyForge"
+task :publish_news => [ :verify_rubyforge, :gem ] do
+ Rake::XForge::NewsPublisher.new($project) do |news|
+ news.user_name = ENV['RUBYFORGE_USER']
+ news.password = ENV['RUBYFORGE_PASSWORD']
+ news.subject = "#$name #$version Released"
+ news.changes_file = nil
+
+ details = []
+ File.open("Release-Announcement") do |file|
+ file.each do |line|
+ line.chomp!
+ break if line =~ /^=/
+ details << line
+ end
+ end
+ news.details = details.join("\n")
+ end
+end
+
+desc "Release the latest version."
+task :release => [ :verify_rubyforge, :release_files, :publish_news, :docs ]
+if $can_gmail
+ task :release => [ :verify_gmail, :email_announcment ]
+end
+
+desc "Build everything."
+task :default => [ :gem ]
+
+if $can_minitar
+ task :release_files => :tar
+ task :publish_news => :tar
+ task :default => :tar
+end
View
89 Release-Announcement
@@ -0,0 +1,89 @@
+We're pleased to announce the first release of Net::LDAP, the first
+pure-Ruby LDAP library. Net::LDAP intends to be a feature-complete
+LDAP client which can access as much as possible of the functionality
+of the most-used LDAP server implementations. This library does
+not wrap any existing native-code LDAP libraries, creates no
+Ruby extensions, and has no dependencies external to Ruby.
+
+Net::LDAP includes a full implementation of the LDAP wire-line
+protocol so it can also be used in LDAP server implementations.
+
+Thanks for Austin Ziegler for invaluable help in reviewing the
+implementation and providing the release structure.
+
+= What is Net::LDAP for Ruby?
+This library provides a pure-Ruby implementation of an LDAP client.
+It can be used to access any server which implements the LDAP protocol.
+
+Net::LDAP is intended to provide full LDAP functionality while hiding
+the more arcane aspects of the LDAP protocol itself, so as to make the
+programming interface as Ruby-like as possible.
+
+In particular, this means that there is no direct dependence on the
+structure of the various "traditional" LDAP clients. This is a ground-up
+rethinking of the LDAP API.
+
+Net::LDAP is based on RFC-1777, which specifies the Lightweight Directory
+Access Protocol, as amended and extended by subsequent RFCs and by the more
+widely-used directory implementations.
+
+Homepage:: http://rubyforge.org/projects/net-ldap/
+Download:: http://rubyforge.org/frs/?group_id=143
+Copyright:: 2006 by Francis Cianfrocca
+
+== LICENCE NOTES
+Please read the file LICENCE for licensing restrictions on this library. In
+the simplest terms, this library is available under the same terms as Ruby
+itself.
+
+== Requirements and Installation
+Net::LDAP requires Ruby 1.8.2 or better.
+
+Net::LDAP can be installed with:
+
+ % ruby setup.rb
+
+Alternatively, you can use the RubyGems version of Net::LDAP available
+as ruby-net-ldap-0.0.1.gem from the usual sources.
+
+== Whet your appetite:
+ require 'net/ldap'
+
+ ldap = Net::LDAP.new :host => server_ip_address,
+ :port => 389,
+ :auth => {
+ :method => :simple,
+ :username => "cn=manager,dc=example,dc=com",
+ :password => "opensesame"
+ }
+
+ filter = Net::LDAP::Filter.eq( "cn", "George*" )
+ treebase = "dc=example,dc=com"
+
+ ldap.search( :base => treebase, :filter => filter ) do |entry|
+ puts "DN: #{entry.dn}"
+ entry.each do |attribute, values|
+ puts " #{attribute}:"
+ values.each do |value|
+ puts " --->#{value}"
+ end
+ end
+ end
+
+ p ldap.get_operation_result
+
+== Net::LDAP 0.0.1: May 1, 2006
+* Initial release.
+* Client functionality is near-complete, although the APIs
+ are not guaranteed and may change depending on feedback
+ from the community.
+* We're internally working on a Ruby-based implementation
+ of a full-featured, production-quality LDAP server,
+ which will leverage the underlying LDAP and BER functionality
+ in Net::LDAP.
+* Please tell us if you would be interested in seeing a public
+ release of the LDAP server.
+* Grateful acknowledgement to Austin Ziegler, who reviewed
+ this code and provided the release framework, including
+ minitar.
+
View
278 lib/net/ber.rb
@@ -0,0 +1,278 @@
+# $Id$
+#
+# NET::BER
+# Mixes ASN.1/BER convenience methods into several standard classes.
+# Also provides BER parsing functionality.
+#
+#----------------------------------------------------------------------------
+#
+# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
+#
+# Gmail: garbagecat10
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#---------------------------------------------------------------------------
+#
+#
+
+
+
+
+module Net
+
+ module BER
+
+ class BerError < Exception; end
+
+
+ # This module is for mixing into IO and IO-like objects.
+ module BERParser
+
+ # The order of these follows the class-codes in BER.
+ # Maybe this should have been a hash.
+ TagClasses = [:universal, :application, :context_specific, :private]
+
+ BuiltinSyntax = {
+ :universal => {
+ :primitive => {
+ 1 => :boolean,
+ 2 => :integer,
+ 4 => :string,
+ 10 => :integer,
+ },
+ :constructed => {
+ 16 => :array,
+ 17 => :array
+ }
+ }
+ }
+
+ #
+ # read_ber
+ # TODO: clean this up so it works properly with partial
+ # packets coming from streams that don't block when
+ # we ask for more data (like StringIOs). At it is,
+ # this can throw TypeErrors and other nasties.
+ #
+ def read_ber syntax=nil
+ return nil if eof?
+
+ id = getc # don't trash this value, we'll use it later
+ tag = id & 31
+ tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
+ tagclass = TagClasses[ id >> 6 ]
+ encoding = (id & 0x20 != 0) ? :constructed : :primitive
+
+ n = getc
+ lengthlength,contentlength = if n <= 127
+ [1,n]
+ else
+ j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
+ [1 + (n & 127), j]
+ end
+
+ newobj = read contentlength
+
+ objtype = nil
+ [syntax, BuiltinSyntax].each {|syn|
+ if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
+ objtype = ot[tag]
+ break
+ end
+ }
+
+ obj = case objtype
+ when :boolean
+ newobj != "\000"
+ when :string
+ (newobj || "").dup
+ when :integer
+ j = 0
+ newobj.each_byte {|b| j = (j << 8) + b}
+ j
+ when :array
+ seq = []
+ sio = StringIO.new( newobj || "" )
+ # Interpret the subobject, but note how the loop
+ # is built: nil ends the loop, but false (a valid
+ # BER value) does not!
+ while (e = sio.read_ber(syntax)) != nil
+ seq << e
+ end
+ seq
+ else
+ raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
+ end
+
+ # Add the identifier bits into the object if it's a String or an Array.
+ # We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
+ obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
+ obj
+
+ end
+
+ end # module BERParser
+ end # module BER
+
+end # module Net
+
+
+class IO
+ include Net::BER::BERParser
+end
+
+require "stringio"
+class StringIO
+ include Net::BER::BERParser
+end
+
+
+class String
+ def read_ber syntax=nil
+ StringIO.new(self).read_ber(syntax)
+ end
+end
+
+
+
+#----------------------------------------------
+
+
+class FalseClass
+ #
+ # to_ber
+ #
+ def to_ber
+ "\001\001\000"
+ end
+end
+
+
+class TrueClass
+ #
+ # to_ber
+ #
+ def to_ber
+ "\001\001\001"
+ end
+end
+
+
+
+class Fixnum
+ #
+ # to_ber
+ #
+ def to_ber
+ i = [self].pack('w')
+ [2, i.length].pack("CC") + i
+ end
+
+ #
+ # to_ber_enumerated
+ #
+ def to_ber_enumerated
+ i = [self].pack('w')
+ [10, i.length].pack("CC") + i
+ end
+
+ #
+ # to_ber_length_encoding
+ #
+ def to_ber_length_encoding
+ if self <= 127
+ [self].pack('C')
+ else
+ i = [self].pack('N').sub(/^[\0]+/,"")
+ [0x80 + i.length].pack('C') + i
+ end
+ end
+
+end # class Fixnum
+
+
+class Bignum
+
+ def to_ber
+ i = [self].pack('w')
+ i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
+ [2, i.length].pack("CC") + i
+ end
+
+end
+
+
+
+class String
+ #
+ # to_ber
+ # A universal octet-string is tag number 4,
+ # but others are possible depending on the context, so we
+ # let the caller give us one.
+ # The preferred way to do this in user code is via to_ber_application_sring
+ # and to_ber_contextspecific.
+ #
+ def to_ber code = 4
+ [code].pack('C') + length.to_ber_length_encoding + self
+ end
+
+ #
+ # to_ber_application_string
+ #
+ def to_ber_application_string code
+ to_ber( 0x40 + code )
+ end
+
+ #
+ # to_ber_contextspecific
+ #
+ def to_ber_contextspecific code
+ to_ber( 0x80 + code )
+ end
+
+end # class String
+
+
+
+class Array
+ #
+ # to_ber_appsequence
+ # An application-specific sequence usually gets assigned
+ # a tag that is meaningful to the particular protocol being used.
+ # This is different from the universal sequence, which usually
+ # gets a tag value of 16.
+ # Now here's an interesting thing: We're adding the X.690
+ # "application constructed" code at the top of the tag byte (0x60),
+ # but some clients, notably ldapsearch, send "context-specific
+ # constructed" (0xA0). The latter would appear to violate RFC-1777,
+ # but what do I know? We may need to change this.
+ #
+
+ def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
+ def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
+ def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
+ def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
+ def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
+
+ private
+ def to_ber_seq_internal code
+ s = self.to_s
+ [code].pack('C') + s.length.to_ber_length_encoding + s
+ end
+
+end # class Array
+
+
View
1,041 lib/net/ldap.rb
@@ -0,0 +1,1041 @@
+# $Id$
+#
+# Net::LDAP for Ruby
+#
+#
+# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
+#
+# Written and maintained by Francis Cianfrocca, gmail: garbagecat10.
+#
+# This program is free software.
+# You may re-distribute and/or modify this program under the same terms
+# as Ruby itself: Ruby Distribution License or GNU General Public License.
+#
+#
+# See Net::LDAP for documentation and usage samples.
+#
+
+
+require 'socket'
+require 'ostruct'
+require 'net/ber'
+require 'net/ldap/pdu'
+require 'net/ldap/filter'
+require 'net/ldap/dataset'
+require 'net/ldap/psw'
+require 'net/ldap/entry'
+
+
+module Net
+
+
+ # == Net::LDAP
+ #
+ # This library provides a pure-Ruby implementation of the
+ # LDAP client protocol, per RFC-1777.
+ # It can be used to access any server which implements the
+ # LDAP protocol.
+ #
+ # Net::LDAP is intended to provide full LDAP functionality
+ # while hiding the more arcane aspects
+ # the LDAP protocol itself, and thus presenting as Ruby-like
+ # a programming interface as possible.
+ #
+ # === Quick-start for the Impatient
+ # require 'rubygems'
+ # require 'net/ldap'
+ #
+ # ldap = Net::LDAP.new :host => server_ip_address,
+ # :port => 389,
+ # :auth => {
+ # :method => :simple,
+ # :username => "cn=manager,dc=example,dc=com",
+ # :password => "opensesame"
+ # }
+ #
+ # filter = Net::LDAP::Filter.eq( "cn", "George*" )
+ # treebase = "dc=example,dc=com"
+ #
+ # ldap.search( :base => treebase, :filter => filter ) do |entry|
+ # puts "DN: #{entry.dn}"
+ # entry.each do |attribute, values|
+ # puts " #{attribute}:"
+ # values.each do |value|
+ # puts " --->#{value}"
+ # end
+ # end
+ # end
+ #
+ # p ldap.get_operation_result
+ #
+ #
+ # == Quick introduction to LDAP
+ #
+ # We're going to provide a quick and highly informal introduction to LDAP
+ # terminology and
+ # typical operations. If you're comfortable with this material, skip
+ # ahead to "How to use Net::LDAP." If you want a more rigorous treatment
+ # of this material, we recommend you start with the various IETF and ITU
+ # standards that control LDAP.
+ #
+ # === Entities
+ # LDAP is an Internet-standard protocol used to access directory servers.
+ # The basic search unit is the <i>entity,</i> which corresponds to
+ # a person or other domain-specific object.
+ # A directory service which supports the LDAP protocol typically
+ # stores information about a number of entities.
+ #
+ # === Principals
+ # LDAP servers are typically used to access information about people,
+ # but also very often about such items as printers, computers, and other
+ # resources. To reflect this, LDAP uses the term <i>entity,</i> or less
+ # commonly, <i>principal,</i> to denote its basic data-storage unit.
+ #
+ #
+ # === Distinguished Names
+ # In LDAP's view of the world,
+ # an entity is uniquely identified by a globally-unique text string
+ # called a <i>Distinguished Name,</i> originally defined in the X.400
+ # standards from which LDAP is ultimately derived.
+ # Much like a DNS hostname, a DN is a "flattened" text representation
+ # of a string of tree nodes. Also like DNS (and unlike Java package
+ # names), a DN expresses a chain of tree-nodes written from left to right
+ # in order from the most-resolved node to the most-general one.
+ #
+ # If you know the DN of a person or other entity, then you can query
+ # an LDAP-enabled directory for information (attributes) about the entity.
+ # Alternatively, you can query the directory for a list of DNs matching
+ # a set of criteria that you supply.
+ #
+ # === Attributes
+ #
+ # In the LDAP view of the world, a DN uniquely identifies an entity.
+ # Information about the entity is stored as a set of <i>Attributes.</i>
+ # An attribute is a text string which is associated with zero or more
+ # values. Most LDAP-enabled directories store a well-standardized
+ # range of attributes, and constrain their values according to standard
+ # rules.
+ #
+ # A good example of an attribute is <tt>cn,</tt> which stands for "Common Name."
+ # In many directories, this attribute is used to store a string consisting of
+ # a person's first and last names. Most directories enforce the convention that
+ # an entity's <tt>cn</tt> attribute have <i>exactly one</i> value. In LDAP
+ # jargon, that means that <tt>cn</tt> must be <i>present</i> and
+ # <i>single-valued.</i>
+ #
+ # Another attribute is <tt>mail,</tt> which is used to store email addresses.
+ # (No, there is no attribute called "email," perhaps because X.400 terminology
+ # predates the invention of the term <i>email.</i>) <tt>mail</tt> differs
+ # from <tt>cn</tt> in that most directories permit any number of values for the
+ # <tt>mail</tt> attribute, including zero.
+ #
+ #
+ # === Tree-Base
+ # We said above that X.400 Distinguished Names are <i>globally unique.</i>
+ # In a manner reminiscent of DNS, LDAP supposes that each directory server
+ # contains authoritative attribute data for a set of DNs corresponding
+ # to a specific sub-tree of the (notional) global directory tree.
+ # This subtree is generally configured into a directory server when it is
+ # created. It matters for this discussion because most servers will not
+ # allow you to query them unless you specify a correct tree-base.
+ #
+ # Let's say you work for the engineering department of Big Company, Inc.,
+ # whose internet domain is bigcompany.com. You may find that your departmental
+ # directory is stored in a server with a defined tree-base of
+ # ou=engineering,dc=bigcompany,dc=com
+ # You will need to supply this string as the <i>tree-base</i> when querying this
+ # directory. (Ou is a very old X.400 term meaning "organizational unit."
+ # Dc is a more recent term meaning "domain component.")
+ #
+ # === LDAP Versions
+ # (stub, discuss v2 and v3)
+ #
+ # === LDAP Operations
+ # The essential operations are: #bind, #search, #add, #modify, #delete, and #rename.
+ # ==== Bind
+ # #bind supplies a user's authentication credentials to a server, which in turn verifies
+ # or rejects them. There is a range of possibilities for credentials, but most directories
+ # support a simple username and password authentication.
+ #
+ # Taken by itself, #bind can be used to authenticate a user against information
+ # stored in a directory, for example to permit or deny access to some other resource.
+ # In terms of the other LDAP operations, most directories require a successful #bind to
+ # be performed before the other operations will be permitted. Some servers permit certain
+ # operations to be performed with an "anonymous" binding, meaning that no credentials are
+ # presented by the user. (We're glossing over a lot of platform-specific detail here.)
+ #
+ # ==== Search
+ # Calling #search against the directory involves specifying a treebase, a set of <i>search filters,</i>
+ # and a list of attribute values.
+ # The filters specify ranges of possible values for particular attributes. Multiple
+ # filters can be joined together with AND, OR, and NOT operators.
+ # A server will respond to a #search by returning a list of matching DNs together with a
+ # set of attribute values for each entity, depending on what attributes the search requested.
+ #
+ # ==== Add
+ # #add operation specifies a new DN and an initial set of attribute values. If the operation
+ # succeeds, a new entity with the corresponding DN and attributes is added to the directory.
+ #
+ # ==== Modify
+ # #modify specifies an entity DN, and a list of attribute operations. #modify is used to change
+ # the attribute values stored in the directory for a particular entity.
+ # #modify may add or delete attributes (which are lists of values) or it change attributes by
+ # adding to or deleting from their values.
+ # There are three easier methods to modify an entry's attribute values:
+ # #add_attribute, #replace_attribute, and #delete_attribute.
+ #
+ # ==== Delete
+ # #delete operation specifies an entity DN. If it succeeds, the entity and all its attributes
+ # is removed from the directory.
+ #
+ # ==== Rename (or Modify RDN)
+ # #rename (or #modify_rdn) is an operation added to version 3 of the LDAP protocol. It responds to
+ # the often-arising need to change the DN of an entity without discarding its attribute values.
+ # In earlier LDAP versions, the only way to do this was to delete the whole entity and add it
+ # again with a different DN.
+ #
+ # #rename works by taking an "old" DN (the one to change) and a "new RDN," which is the left-most
+ # part of the DN string. If successful, #rename changes the entity DN so that its left-most
+ # node corresponds to the new RDN given in the request. (RDN, or "relative distinguished name,"
+ # denotes a single tree-node as expressed in a DN, which is a chain of tree nodes.)
+ #
+ # == How to use Net::LDAP
+ #
+ # To access Net::LDAP functionality in your Ruby programs, start by requiring
+ # the library:
+ #
+ # require 'net/ldap'
+ #
+ # If you installed the Gem version of Net::LDAP, and depending on your version of
+ # Ruby and rubygems, you _may_ also need to require rubygems explicitly:
+ #
+ # require 'rubygems'
+ # require 'net/ldap'
+ #
+ # Most operations with Net::LDAP start by instantiating a Net::LDAP object.
+ # The constructor for this object takes arguments specifying the network location
+ # (address and port) of the LDAP server, and also the binding (authentication)
+ # credentials, typically a username and password.
+ # Given an object of class Net:LDAP, you can then perform LDAP operations by calling
+ # instance methods on the object. These are documented with usage examples below.
+ #
+ # The Net::LDAP library is designed to be very disciplined about how it makes network
+ # connections to servers. This is different from many of the standard native-code
+ # libraries that are provided on most platforms, which share bloodlines with the
+ # original Netscape/Michigan LDAP client implementations. These libraries sought to
+ # insulate user code from the workings of the network. This is a good idea of course,
+ # but the practical effect has been confusing and many difficult bugs have been caused
+ # by the opacity of the native libraries, and their variable behavior across platforms.
+ #
+ # In general, Net::LDAP instance methods which invoke server operations make a connection
+ # to the server when the method is called. They execute the operation (typically binding first)
+ # and then disconnect from the server. The exception is Net::LDAP#open, which makes a connection
+ # to the server and then keeps it open while it executes a user-supplied block. Net::LDAP#open
+ # closes the connection on completion of the block.
+ #
+
+ class LDAP
+
+ class LdapError < Exception; end
+
+ VERSION = "0.0.1"
+
+
+ SearchScope_BaseObject = 0
+ SearchScope_SingleLevel = 1
+ SearchScope_WholeSubtree = 2
+ SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel, SearchScope_WholeSubtree]
+
+ AsnSyntax = {
+ :application => {
+ :constructed => {
+ 0 => :array, # BindRequest
+ 1 => :array, # BindResponse
+ 2 => :array, # UnbindRequest
+ 3 => :array, # SearchRequest
+ 4 => :array, # SearchData
+ 5 => :array, # SearchResult
+ 6 => :array, # ModifyRequest
+ 7 => :array, # ModifyResponse
+ 8 => :array, # AddRequest
+ 9 => :array, # AddResponse
+ 10 => :array, # DelRequest
+ 11 => :array, # DelResponse
+ 12 => :array, # ModifyRdnRequest
+ 13 => :array, # ModifyRdnResponse
+ 14 => :array, # CompareRequest
+ 15 => :array, # CompareResponse
+ 16 => :array, # AbandonRequest
+ 24 => :array, # Unsolicited Notification
+ }
+ },
+ :context_specific => {
+ :primitive => {
+ 0 => :string, # password
+ 1 => :string, # Kerberos v4
+ 2 => :string, # Kerberos v5
+ }
+ }
+ }
+
+ DefaultHost = "127.0.0.1"
+ DefaultPort = 389
+ DefaultAuth = {:method => :anonymous}
+
+
+ ResultStrings = {
+ 0 => "Success",
+ 1 => "Operations Error",
+ 2 => "Protocol Error",
+ 16 => "No Such Attribute",
+ 17 => "Undefined Attribute Type",
+ 20 => "Attribute or Value Exists",
+ 32 => "No Such Object",
+ 34 => "Invalid DN Syntax",
+ 48 => "Invalid DN Syntax",
+ 48 => "Inappropriate Authentication",
+ 49 => "Invalid Credentials",
+ 50 => "Insufficient Access Rights",
+ 51 => "Busy",
+ 52 => "Unavailable",
+ 53 => "Unwilling to perform",
+ 65 => "Object Class Violation",
+ 68 => "Entry Already Exists"
+ }
+
+ #
+ # LDAP::result2string
+ #
+ def LDAP::result2string code
+ ResultStrings[code] || "unknown result (#{code})"
+ end
+
+ # Instantiate an object of type Net::LDAP to perform directory operations.
+ # This constructor takes a Hash containing arguments. The following arguments
+ # are supported:
+ # * :host => the LDAP server's IP-address (default 127.0.0.1)
+ # * :port => the LDAP server's TCP port (default 389)
+ # * :auth => a Hash containing authorization parameters. Currently supported values include:
+ # {:method => :anonymous} and
+ # {:method => :simple, :username => your_user_name, :password => your_password }
+ #
+ # Instantiating a Net::LDAP object does <i>not</i> result in network traffic to
+ # the LDAP server. It simply stores the connection and binding parameters in the
+ # object.
+ #
+ def initialize args = {}
+ @host = args[:host] || DefaultHost
+ @port = args[:port] || DefaultPort
+ @verbose = false # Make this configurable with a switch on the class.
+ @auth = args[:auth] || DefaultAuth
+
+ # This variable is only set when we are created with LDAP::open.
+ # All of our internal methods will connect using it, or else
+ # they will create their own.
+ @open_connection = nil
+ end
+
+ # #open takes the same parameters as #new. #open makes a network connection to the
+ # LDAP server and then passes a newly-created Net::LDAP object to the caller-supplied block.
+ # Within the block, you can call any of the instance methods of Net::LDAP to
+ # perform operations against the LDAP directory. #open will perform all the
+ # operations in the user-supplied block on the same network connection, which
+ # will be closed automatically when the block finishes.
+ #
+ # # (PSEUDOCODE)
+ # auth = {:method => :simple, :username => username, :password => password}
+ # Net::LDAP.open( :host => ipaddress, :port => 389, :auth => auth ) do |ldap|
+ # ldap.search( ... )
+ # ldap.add( ... )
+ # ldap.modify( ... )
+ # end
+ #
+ def LDAP::open args
+ ldap1 = LDAP.new args
+ ldap1.open {|ldap| yield ldap }
+ end
+
+ # Returns a meaningful result any time after
+ # a protocol operation (#bind, #search, #add, #modify, #rename, #delete)
+ # has completed.
+ # It returns an #OpenStruct containing an LDAP result code (0 means success),
+ # and a human-readable string.
+ # unless ldap.bind
+ # puts "Result: #{ldap.get_operation_result.code}"
+ # puts "Message: #{ldap.get_operation_result.message}"
+ # end
+ #
+ def get_operation_result
+ os = OpenStruct.new
+ if @result
+ os.code = @result
+ else
+ os.code = 0
+ end
+ os.message = LDAP.result2string( os.code )
+ os
+ end
+
+
+ # Opens a network connection to the server and then
+ # passes <tt>self</tt> to the caller-supplied block. The connection is
+ # closed when the block completes. Used for executing multiple
+ # LDAP operations without requiring a separate network connection
+ # (and authentication) for each one.
+ # <i>Note:</i> You do not need to log-in or "bind" to the server. This will
+ # be done for you automatically.
+ # For an even simpler approach, see the class method Net::LDAP#open.
+ #
+ # # (PSEUDOCODE)
+ # auth = {:method => :simple, :username => username, :password => password}
+ # ldap = Net::LDAP.new( :host => ipaddress, :port => 389, :auth => auth )
+ # ldap.open do |ldap|
+ # ldap.search( ... )
+ # ldap.add( ... )
+ # ldap.modify( ... )
+ # end
+ #--
+ # First we make a connection and then a binding, but we don't
+ # do anything with the bind results.
+ # We then pass self to the caller's block, where he will execute
+ # his LDAP operations. Of course they will all generate auth failures
+ # if the bind was unsuccessful.
+ def open
+ raise LdapError.new( "open already in progress" ) if @open_connection
+ @open_connection = Connection.new( :host => @host, :port => @port )
+ @open_connection.bind @auth
+ yield self
+ @open_connection.close
+ end
+
+
+ # <i>DEPRECATED.</i> Performs an LDAP search, waits for the operation to complete, and
+ # passes a result set to the caller-supplied block.
+ #--
+ # If an open call is in progress (@open_connection will be non-nil),
+ # then ASSUME a bind has been performed and accepted, and just
+ # execute the search.
+ # If @open_connection is nil, then we have to connect, bind,
+ # search, and then disconnect. (The disconnect is not strictly
+ # necessary but it's friendlier to the network to do it here
+ # rather than waiting for Ruby's GC.)
+ # Note that in the standalone case, we're permitting the caller
+ # to modify the auth parms.
+ #
+ def searchx args
+ if @open_connection
+ @result = @open_connection.searchx( args ) {|values|
+ yield( values ) if block_given?
+ }
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.searchx( args ) {|values|
+ yield( values ) if block_given?
+ }
+ end
+ conn.close
+ end
+
+ @result == 0
+ end
+
+ # Searches the LDAP directory for directory entries.
+ # Takes a hash argument with parameters. Supported parameters include:
+ # * :base (a string specifying the tree-base for the search);
+ # * :filter (an object of type Net::LDAP::Filter, defaults to objectclass=*);
+ # * :attributes (a string or array of strings specifying the LDAP attributes to return from the server);
+ # * :return_result (a boolean specifying whether to return a result set).
+ # * :attributes_only (a boolean flag, defaults false)
+ # * :scope (one of: Net::LDAP::SearchScope_BaseObject, Net::LDAP::SearchScope_SingleLevel, Net::LDAP::SearchScope_WholeSubtree. Default is WholeSubtree.)
+ #
+ # #search queries the LDAP server and passes <i>each entry</i> to the
+ # caller-supplied block, as an object of type Net::LDAP::Entry.
+ # If the search returns 1000 entries, the block will
+ # be called 1000 times. If the search returns no entries, the block will
+ # not be called.
+ #
+ # #search returns either a result-set or a boolean, depending on the
+ # value of the <tt>:return_result</tt> argument. The default behavior is to return
+ # a result set, which is a hash. Each key in the hash is a string specifying
+ # the DN of an entry. The corresponding value for each key is a Net::LDAP::Entry object.
+ # If you request a result set and #search fails with an error, it will return nil.
+ # Call #get_operation_result to get the error information returned by
+ # the LDAP server.
+ #
+ # When <tt>:return_result => false,</tt> #search will
+ # return only a Boolean, to indicate whether the operation succeeded. This can improve performance
+ # with very large result sets, because the library can discard each entry from memory after
+ # your block processes it.
+ #
+ #
+ # treebase = "dc=example,dc=com"
+ # filter = Net::LDAP::Filter.eq( "mail", "a*.com" )
+ # attrs = ["mail", "cn", "sn", "objectclass"]
+ # ldap.search( :base => treebase, :filter => filter, :attributes => attrs, :return_result => false ) do |entry|
+ # puts "DN: #{entry.dn}"
+ # entry.each do |attr, values|
+ # puts ".......#{attr}:"
+ # values.each do |value|
+ # puts " #{value}"
+ # end
+ # end
+ # end
+ #
+ #--
+ # This is a re-implementation of search that replaces the
+ # original one (now renamed searchx and possibly destined to go away).
+ # The difference is that we return a dataset (or nil) from the
+ # call, and pass _each entry_ as it is received from the server
+ # to the caller-supplied block. This will probably make things
+ # far faster as we can do useful work during the network latency
+ # of the search. The downside is that we have no access to the
+ # whole set while processing the blocks, so we can't do stuff
+ # like sort the DNs until after the call completes.
+ # It's also possible that this interacts badly with server timeouts.
+ # We'll have to ensure that something reasonable happens if
+ # the caller has processed half a result set when we throw a timeout
+ # error.
+ # Another important difference is that we return a result set from
+ # this method rather than a T/F indication.
+ # Since this can be very heavy-weight, we define an argument flag
+ # that the caller can set to suppress the return of a result set,
+ # if he's planning to process every entry as it comes from the server.
+ #
+ def search args
+ result_set = (args and args[:return_result] == false) ? nil : {}
+
+ if @open_connection
+ @result = @open_connection.search( args ) {|entry|
+ result_set[entry.dn] = entry if result_set
+ yield( entry ) if block_given?
+ }
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.search( args ) {|entry|
+ (result_set[entry.dn] = entry) if result_set
+ yield( entry ) if block_given?
+ }
+ end
+ conn.close
+ end
+
+ @result == 0 and result_set
+ end
+
+ # #bind connects to the LDAP server and requests authentication
+ # based on the <tt>:auth</tt> parameter passed to #open or #new.
+ # It takes no parameters.
+ # User code generally will not call #bind. It will be called
+ # implicitly by the library whenever an LDAP operation is
+ # requested. #bind can be useful to test authentication.
+ #--
+ # If there is an @open_connection, then perform the bind
+ # on it. Otherwise, connect, bind, and disconnect.
+ # The latter operation is obviously useful only as an auth check.
+ #
+ def bind
+ if @open_connection
+ @result = @open_connection.bind @auth
+ else
+ conn = Connection.new( :host => @host, :port => @port )
+ @result = conn.bind @auth
+ conn.close
+ end
+
+ @result == 0
+ end
+
+ #
+ # #bind_as is for testing authentication credentials.
+ # Most likely a "standard" name (like a CN or an email
+ # address) will be presented along with a password.
+ # We'll bind with the main credential given in the
+ # constructor, query the full DN of the user given
+ # to us as a parameter, then unbind and rebind as the
+ # new user.
+ #
+ # <i>This method is currently an unimplemented stub.</i>
+ #
+ def bind_as
+ end
+
+ # Adds a new entry to the remote LDAP server.
+ # Supported arguments:
+ # :dn :: Full DN of the new entry
+ # :attributes :: Attributes of the new entry.
+ #
+ # The attributes argument is supplied as a Hash keyed by Strings or Symbols
+ # giving the attribute name, and mapping to Strings or Arrays of Strings
+ # giving the actual attribute values. Observe that most LDAP directories
+ # enforce schema constraints on the attributes contained in entries.
+ # #add will fail with a server-generated error if your attributes violate
+ # the server-specific constraints.
+ # Here's an example:
+ #
+ # dn = "cn=George Smith,ou=people,dc=example,dc=com"
+ # attr = {
+ # :cn => "George Smith",
+ # :objectclass => ["top", "inetorgperson"],
+ # :sn => "Smith",
+ # :mail => "gsmith@example.com"
+ # }
+ # Net::LDAP.open (:host => host) do |ldap|
+ # ldap.add( :dn => dn, :attributes => attr )
+ # end
+ #
+ def add args
+ if @open_connection
+ @result = @open_connection.add( args )
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.add( args )
+ end
+ conn.close
+ end
+ @result == 0
+ end
+
+
+ # _DEPRECATED_ - Please use #add_attribute, #replace_attribute, or #delete_attribute.
+ #
+ # Modifies the attribute values of a particular entry on the LDAP directory.
+ # Takes a hash with arguments. Supported arguments are:
+ # :dn :: (the full DN of the entry whose attributes are to be modified)
+ # :operations :: (the modifications to be performed, detailed next)
+ #
+ # This method returns True or False to indicate whether the operation
+ # succeeded or failed, with extended information available by calling
+ # #get_operation_result.
+ #
+ # The LDAP protocol provides a full and well thought-out set of operations
+ # for changing the values of attributes, but they are necessarily somewhat complex
+ # and not always intuitive. If these instructions are confusing or incomplete,
+ # please send us email or create a bug report on rubyforge.
+ #
+ # The :operations parameter to #modify takes an array of operation-descriptors.
+ # Each individual operation is specified in one element of the array, and
+ # most LDAP servers will attempt to perform the operations in order.
+ #
+ # Each of the operations appearing in the Array must itself be an Array
+ # with exactly three elements:
+ # an operator:: must be :add, :replace, or :delete
+ # an attribute name:: the attribute name (string or symbol) to modify
+ # a value:: either a string or an array of strings.
+ #
+ # The :add operator will, unsurprisingly, add the specified values to
+ # the specified attribute. If the attribute does not already exist,
+ # :add will create it. Most LDAP servers will generate an error if you
+ # to add a value that already exists.
+ #
+ # :replace will erase the current value(s) for the specified attribute,
+ # if there are any, and replace them with the specified value(s).
+ #
+ # :delete will remove the specified value(s) from the specified attribute.
+ # If you pass nil, an empty string, or an empty array as the value parameter
+ # to a :delete operation, the _entire_ _attribute_ will be deleted.
+ #
+ # For example:
+ #
+ # dn = "mail=modifyme@example.com,ou=people,dc=example,dc=com"
+ # ops = [
+ # [:add, :mail, "aliasaddress@example.com"],
+ # [:replace, :mail, ["newaddress@example.com", "newalias@example.com"]],
+ # [:delete, :sn, nil]
+ # ]
+ # ldap.modify :dn => dn, :operations => ops
+ #
+ # <i>(This example is contrived since you probably wouldn't add a mail
+ # value right before replacing the whole attribute, but it shows that order
+ # of execution matters. Also, many LDAP servers won't let you delete SN
+ # because it would be a schema violation.)</i>
+ #
+ # It's essential to keep in mind that if you specify more than one operation in
+ # a call to #modify, most LDAP servers will attempt to perform all of the operations
+ # in the order you gave them.
+ # This matters because you may specify operations on the
+ # same attribute which must be performed in a certain order.
+ # Most LDAP servers will _stop_ processing your modifications if one of them
+ # causes an error on the server (such as a schema-constraint violation).
+ # If this happens, you will probably get a result code from the server that
+ # reflects only the operation that failed, and you may or may not get extended
+ # information that will tell you which one failed. #modify has no notion
+ # of an atomic transaction. If you specify a chain of modifications in one
+ # call to #modify, and one of them fails, the preceding ones will usually
+ # not be "rolled back," resulting in a partial update. This is a limitation
+ # of the LDAP protocol, not of Net::LDAP.
+ #
+ #
+ def modify args
+ if @open_connection
+ @result = @open_connection.modify( args )
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.modify( args )
+ end
+ conn.close
+ end
+ @result == 0
+ end
+
+
+ # Add a value to an attribute.
+ # Takes the full DN of the entry to modify,
+ # the name (Symbol or String) of the attribute, and the value (String or
+ # Array). If the attribute does not exist (and there are no schema violations),
+ # #add_attribute will create it with the caller-specified values.
+ # If the attribute already exists (and there are no schema violations), the
+ # caller-specified values will be _added_ to the values already present.
+ #
+ # Returns True or False to indicate whether the operation
+ # succeeded or failed, with extended information available by calling
+ # #get_operation_result. See also #replace_attribute and #delete_attribute.
+ #
+ # dn = "cn=modifyme,dc=example,dc=com"
+ # ldap.add_attribute dn, :mail, "newmailaddress@example.com"
+ #
+ def add_attribute dn, attribute, value
+ modify :dn => dn, :operations => [[:add, attribute, value]]
+ end
+
+ # Replace the value of an attribute.
+ # #replace_attribute can be thought of as equivalent to calling #delete_attribute
+ # followed by #add_attribute. It takes the full DN of the entry to modify,
+ # the name (Symbol or String) of the attribute, and the value (String or
+ # Array). If the attribute does not exist, it will be created with the
+ # caller-specified value(s). If the attribute does exist, its values will be
+ # _discarded_ and replaced with the caller-specified values.
+ #
+ # Returns True or False to indicate whether the operation
+ # succeeded or failed, with extended information available by calling
+ # #get_operation_result. See also #add_attribute and #delete_attribute.
+ #
+ # dn = "cn=modifyme,dc=example,dc=com"
+ # ldap.replace_attribute dn, :mail, "newmailaddress@example.com"
+ #
+ def replace_attribute dn, attribute, value
+ modify :dn => dn, :operations => [[:replace, attribute, value]]
+ end
+
+ # Delete an attribute and all its values.
+ # Takes the full DN of the entry to modify, and the
+ # name (Symbol or String) of the attribute to delete.
+ #
+ # Returns True or False to indicate whether the operation
+ # succeeded or failed, with extended information available by calling
+ # #get_operation_result. See also #add_attribute and #replace_attribute.
+ #
+ # dn = "cn=modifyme,dc=example,dc=com"
+ # ldap.delete_attribute dn, :mail
+ #
+ def delete_attribute dn, attribute
+ modify :dn => dn, :operations => [[:delete, attribute, nil]]
+ end
+
+
+ # Rename an entry on the remote DIS by changing the last RDN of its DN.
+ # _Documentation_ _stub_
+ #
+ def rename args
+ if @open_connection
+ @result = @open_connection.rename( args )
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.rename( args )
+ end
+ conn.close
+ end
+ @result == 0
+ end
+
+ # modify_rdn is an alias for #rename.
+ def modify_rdn args
+ rename args
+ end
+
+ # Delete an entry from the LDAP directory.
+ # Takes a hash of arguments.
+ # The only supported argument is :dn, which must
+ # give the complete DN of the entry to be deleted.
+ # Returns True or False to indicate whether the delete
+ # succeeded. Extended status information is available by
+ # calling #get_operation_result.
+ #
+ # dn = "mail=deleteme@example.com,ou=people,dc=example,dc=com"
+ # ldap.delete :dn => dn
+ #
+ def delete args
+ if @open_connection
+ @result = @open_connection.delete( args )
+ else
+ @result = 0
+ conn = Connection.new( :host => @host, :port => @port )
+ if (@result = conn.bind( args[:auth] || @auth )) == 0
+ @result = conn.delete( args )
+ end
+ conn.close
+ end
+ @result == 0
+ end
+
+ end # class LDAP
+
+
+
+ class LDAP
+ # This is a private class used internally by the library. It should not be called by user code.
+ class Connection # :nodoc:
+
+ LdapVersion = 3
+
+
+ #--
+ # initialize
+ #
+ def initialize server
+ begin
+ @conn = TCPsocket.new( server[:host], server[:port] )
+ rescue
+ raise LdapError.new( "no connection to server" )
+ end
+
+ yield self if block_given?
+ end
+
+
+ #--
+ # close
+ # This is provided as a convenience method to make
+ # sure a connection object gets closed without waiting
+ # for a GC to happen. Clients shouldn't have to call it,
+ # but perhaps it will come in handy someday.
+ def close
+ @conn.close
+ @conn = nil
+ end
+
+ #--
+ # next_msgid
+ #
+ def next_msgid
+ @msgid ||= 0
+ @msgid += 1
+ end
+
+
+ #--
+ # bind
+ #
+ def bind auth
+ user,psw = case auth[:method]
+ when :anonymous
+ ["",""]
+ when :simple
+ [auth[:username] || auth[:dn], auth[:password]]
+ end
+ raise LdapError.new( "invalid binding information" ) unless (user && psw)
+
+ msgid = next_msgid.to_ber
+ request = [LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0)].to_ber_appsequence(0)
+ request_pkt = [msgid, request].to_ber_sequence
+ @conn.write request_pkt
+
+ (be = @conn.read_ber(AsnSyntax) and pdu = Net::LdapPdu.new( be )) or raise LdapError.new( "no bind result" )
+ pdu.result_code
+ end
+
+ #--
+ # search
+ # Alternate implementation, this yields each search entry to the caller
+ # as it are received.
+ # TODO, certain search parameters are hardcoded.
+ # TODO, if we mis-parse the server results or the results are wrong, we can block
+ # forever. That's because we keep reading results until we get a type-5 packet,
+ # which might never come. We need to support the time-limit in the protocol.
+ #--
+ # WARNING: this code substantially recapitulates the searchx method.
+ #
+ def search args = {}
+ search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
+ search_base = (args && args[:base]) || "dc=example,dc=com"
+ search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
+
+ attributes_only = (args and args[:attributes_only] == true)
+ scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
+ raise LdapError.new( "invalid search scope" ) unless SearchScopes.include?(scope)
+
+ request = [
+ search_base.to_ber,
+ scope.to_ber_enumerated,
+ 0.to_ber_enumerated,
+ 0.to_ber,
+ 0.to_ber,
+ attributes_only.to_ber,
+ search_filter.to_ber,
+ search_attributes.to_ber_sequence
+ ].to_ber_appsequence(3)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ result_code = 0
+
+ while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
+ case pdu.app_tag
+ when 4 # search-data
+ yield( pdu.search_entry ) if block_given?
+ when 5 # search-result
+ result_code = pdu.result_code
+ break
+ else
+ raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
+ end
+ end
+
+ result_code
+ end
+
+
+ #--
+ # searchx
+ # Original implementation, this doesn't return until all data have been
+ # received from the server.
+ # TODO, certain search parameters are hardcoded.
+ # TODO, if we mis-parse the server results or the results are wrong, we can block
+ # forever. That's because we keep reading results until we get a type-5 packet,
+ # which might never come. We need to support the time-limit in the protocol.
+ #--
+ # WARNING: this code substantially recapitulates the search method.
+ #
+ def searchx args
+ search_filter = (args && args[:filter]) || Filter.eq( "objectclass", "*" )
+ search_base = (args && args[:base]) || "dc=example,dc=com"
+ search_attributes = ((args && args[:attributes]) || []).map {|attr| attr.to_s.to_ber}
+ request = [
+ search_base.to_ber,
+ 2.to_ber_enumerated,
+ 0.to_ber_enumerated,
+ 0.to_ber,
+ 0.to_ber,
+ false.to_ber,
+ search_filter.to_ber,
+ search_attributes.to_ber_sequence
+ ].to_ber_appsequence(3)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ search_results = {}
+ result_code = 0
+
+ while (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be ))
+ case pdu.app_tag
+ when 4 # search-data
+ search_results [pdu.search_dn] = pdu.search_attributes
+ when 5 # search-result
+ result_code = pdu.result_code
+ block_given? and yield( search_results )
+ break
+ else
+ raise LdapError.new( "invalid response-type in search: #{pdu.app_tag}" )
+ end
+ end
+
+ result_code
+ end
+
+ #--
+ # modify
+ # TODO, need to support a time limit, in case the server fails to respond.
+ # TODO!!! We're throwing an exception here on empty DN.
+ # Should return a proper error instead, probaby from farther up the chain.
+ # TODO!!! If the user specifies a bogus opcode, we'll throw a
+ # confusing error here ("to_ber_enumerated is not defined on nil").
+ #
+ def modify args
+ modify_dn = args[:dn] or raise "Unable to modify empty DN"
+ modify_ops = []
+ a = args[:operations] and a.each {|op, attr, values|
+ # TODO, fix the following line, which gives a bogus error
+ # if the opcode is invalid.
+ op_1 = {:add => 0, :delete => 1, :replace => 2} [op.to_sym].to_ber_enumerated
+ modify_ops << [op_1, [attr.to_s.to_ber, values.to_a.map {|v| v.to_ber}.to_ber_set].to_ber_sequence].to_ber_sequence
+ }
+
+ request = [modify_dn.to_ber, modify_ops.to_ber_sequence].to_ber_appsequence(6)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 7) or raise LdapError.new( "response missing or invalid" )
+ pdu.result_code
+ end
+
+
+ #--
+ # add
+ # TODO, need to support a time limit, in case the server fails to respond.
+ #
+ def add args
+ add_dn = args[:dn] or raise LdapError.new("Unable to add empty DN")
+ add_attrs = []
+ a = args[:attributes] and a.each {|k,v|
+ add_attrs << [ k.to_s.to_ber, v.to_a.map {|m| m.to_ber}.to_ber_set ].to_ber_sequence
+ }
+
+ request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(8)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 9) or raise LdapError.new( "response missing or invalid" )
+ pdu.result_code
+ end
+
+
+ #--
+ # rename
+ # TODO, need to support a time limit, in case the server fails to respond.
+ #
+ def rename args
+ old_dn = args[:olddn] or raise "Unable to rename empty DN"
+ new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN"
+ delete_attrs = args[:delete_attributes] ? true : false
+
+ request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber].to_ber_appsequence(12)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 13) or raise LdapError.new( "response missing or invalid" )
+ pdu.result_code
+ end
+
+
+ #--
+ # delete
+ # TODO, need to support a time limit, in case the server fails to respond.
+ #
+ def delete args
+ dn = args[:dn] or raise "Unable to delete empty DN"
+
+ request = dn.to_s.to_ber_application_string(10)
+ pkt = [next_msgid.to_ber, request].to_ber_sequence
+ @conn.write pkt
+
+ (be = @conn.read_ber(AsnSyntax)) && (pdu = LdapPdu.new( be )) && (pdu.app_tag == 11) or raise LdapError.new( "response missing or invalid" )
+ pdu.result_code
+ end
+
+
+ end # class Connection
+ end # class LDAP
+
+
+end # module Net
+
+
View
108 lib/net/ldap/dataset.rb
@@ -0,0 +1,108 @@
+# $Id$
+#
+#
+#----------------------------------------------------------------------------
+#
+# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
+#
+# Gmail: garbagecat10
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#---------------------------------------------------------------------------
+#
+#
+
+
+
+
+module Net
+class LDAP
+
+class Dataset < Hash
+
+ attr_reader :comments
+
+
+ def Dataset::read_ldif io
+ ds = Dataset.new
+
+ line = io.gets && chomp
+ dn = nil
+
+ while line
+ io.gets and chomp
+ if $_ =~ /^[\s]+/
+ line << " " << $'
+ else
+ nextline = $_
+
+ if line =~ /^\#/
+ ds.comments << line
+ elsif line =~ /^dn:[\s]*/i
+ dn = $'
+ ds[dn] = Hash.new {|k,v| k[v] = []}
+ elsif line.length == 0
+ dn = nil
+ elsif line =~ /^([^:]+):([\:]?)[\s]*/
+ # $1 is the attribute name
+ # $2 is a colon iff the attr-value is base-64 encoded
+ # $' is the attr-value
+ # Avoid the Base64 class because not all Ruby versions have it.
+ attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
+ ds[dn][$1.downcase.intern] << attrvalue
+ end
+
+ line = nextline
+ end
+ end
+
+ ds
+ end