diff --git a/ldap/ldap_attr.py b/ldap/ldap_attr.py
old mode 100644
new mode 100755
diff --git a/ldap/ldap_entry.py b/ldap/ldap_entry.py
new file mode 100755
index 0000000000000000000000000000000000000000..6ec4f6b94482a8ee49efaadfc841d84fc2b8522d
--- /dev/null
+++ b/ldap/ldap_entry.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+
+# ldap_entry Ansible module
+# Copyright (C) 2014 Peter Sagerson <psagers@ignorare.net>
+# Homepage: https://bitbucket.org/psagers/ansible-ldap
+
+
+# Copyright (c) 2014, Peter Sagerson
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# - Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+#
+# - Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from traceback import format_exc
+
+import ldap
+import ldap.modlist
+import ldap.sasl
+
+
+DOCUMENTATION = """
+---
+module: ldap_entry
+short_description: Add or remove LDAP entries.
+description:
+    - Add or remove LDAP entries. This module only asserts the existence or
+      non-existence of an LDAP entry, not its attributes. To assert the
+      attribute values of an entry, see M(ldap_attr).
+notes: []
+version_added: null
+author: Peter Sagerson
+requirements:
+    - python-ldap
+options:
+    dn:
+        required: true
+        description:
+            - The DN of the entry to add or remove.
+    state:
+        required: false
+        choices: [present, absent]
+        default: present
+        description:
+            - The target state of the entry.
+    objectClass:
+        required: false
+        description:
+            - If C(state=present), this must be a list of objectClass values to
+              use when creating the entry. It can either be a string containing
+              a comma-separated list of values, or an actual list of strings.
+    '...':
+        required: false
+        description:
+            - If C(state=present), all additional arguments are taken to be
+              LDAP attribute names like C(objectClass), with similar
+              lists of values. These should only be used to
+              provide the minimum attributes necessary for creating an entry;
+              existing entries are never modified. To assert specific attribute
+              values on an existing entry, see M(ldap_attr).
+    server_uri:
+        required: false
+        default: ldapi:///
+        description:
+            - A URI to the LDAP server. The default value lets the underlying
+              LDAP client library look for a UNIX domain socket in its default
+              location.
+    start_tls:
+        required: false
+        default: false
+        description:
+            - If true, we'll use the START_TLS LDAP extension.
+    bind_dn:
+        required: false
+        description:
+            - A DN to bind with. If this is omitted, we'll try a SASL bind with
+              the EXTERNAL mechanism. If this is blank, we'll use an anonymous
+              bind.
+    bind_pw:
+        required: false
+        description:
+            - The password to use with C(bind_dn).
+"""
+
+
+EXAMPLES = """
+# Make sure we have a parent entry for users.
+- ldap_entry: dn='ou=users,dc=example,dc=com' objectClass=organizationalUnit
+  sudo: true
+
+# Make sure we have an admin user.
+- ldap_entry:
+    dn: 'cn=admin,dc=example,dc=com'
+    objectClass: simpleSecurityObject,organizationalRole
+    description: An LDAP administrator
+    userPassword: '{SSHA}pedsA5Y9wHbZ5R90pRdxTEZmn6qvPdzm'
+  sudo: true
+
+# Get rid of an old entry.
+- ldap_entry: dn='ou=stuff,dc=example,dc=com' state=absent server_uri='ldap://localhost/' bind_dn='cn=admin,dc=example,dc=com' bind_pw=password
+"""
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec={
+            'dn': dict(required=True),
+            'state': dict(default='present', choices=['present', 'absent']),
+            'server_uri': dict(default='ldapi:///'),
+            'start_tls': dict(default='false', choices=BOOLEANS),
+            'bind_dn': dict(default=None),
+            'bind_pw': dict(default=''),
+        },
+        check_invalid_arguments=False,
+        supports_check_mode=True,
+    )
+
+    try:
+        LdapEntry(module).main()
+    except ldap.LDAPError, e:
+        module.fail_json(msg=str(e), exc=format_exc())
+
+
+class LdapEntry(object):
+    _connection = None
+
+    def __init__(self, module):
+        self.module = module
+
+        # python-ldap doesn't understand unicode strings. Parameters that are
+        # just going to get passed to python-ldap APIs are stored as utf-8.
+        self.dn = self._utf8_param('dn')
+        self.state = self.module.params['state']
+        self.server_uri = self.module.params['server_uri']
+        self.start_tls = self.module.boolean(self.module.params['start_tls'])
+        self.bind_dn = self._utf8_param('bind_dn')
+        self.bind_pw = self._utf8_param('bind_pw')
+        self.attrs = {}
+
+        self._load_attrs()
+
+        if (self.state == 'present') and ('objectClass' not in self.attrs):
+            self.module.fail_json(msg="When state=present, at least one objectClass must be provided")
+
+    def _utf8_param(self, name):
+        return self._force_utf8(self.module.params[name])
+
+    def _load_attrs(self):
+        for name, raw in self.module.params.iteritems():
+            if name not in self.module.argument_spec and name not in ['NO_LOG']:
+                self.attrs[name] = self._load_attr_values(name, raw)
+
+    def _load_attr_values(self, name, raw):
+        if isinstance(raw, basestring):
+            values = raw.split(',')
+        else:
+            values = raw
+
+        if not (isinstance(values, list) and all(isinstance(value, basestring) for value in values)):
+            self.module.fail_json(msg="{} must be a string or list of strings.".format(name))
+
+        return map(self._force_utf8, values)
+
+    def _force_utf8(self, value):
+        """ If value is unicode, encode to utf-8. """
+        if isinstance(value, unicode):
+            value = value.encode('utf-8')
+
+        return value
+
+    def main(self):
+        if self.state == 'present':
+            action = self.handle_present()
+        elif self.state == 'absent':
+            action = self.handle_absent()
+        else:
+            action = None
+
+        if (action is not None) and (not self.module.check_mode):
+            action()
+
+        self.module.exit_json(changed=(action is not None))
+
+    #
+    # State Implementations
+    #
+
+    def handle_present(self):
+        """ If self.dn does not exist, returns a callable that will add it. """
+        if not self.is_entry_present():
+            modlist = ldap.modlist.addModlist(self.attrs)
+            action = lambda: self.connection.add_s(self.dn, modlist)
+        else:
+            action = None
+
+        return action
+
+    def handle_absent(self):
+        """ If self.dn exists, returns a callable that will delete it. """
+        if self.is_entry_present():
+            action = lambda: self.connection.delete_s(self.dn)
+        else:
+            action = None
+
+        return action
+
+    #
+    # Util
+    #
+
+    def is_entry_present(self):
+        try:
+            self.connection.search_s(self.dn, ldap.SCOPE_BASE)
+        except ldap.NO_SUCH_OBJECT:
+            is_present = False
+        else:
+            is_present = True
+
+        return is_present
+
+    #
+    # LDAP Connection
+    #
+
+    @property
+    def connection(self):
+        """ An authenticated connection to the LDAP server (cached). """
+        if self._connection is None:
+            self._connection = self._connect_to_ldap()
+
+        return self._connection
+
+    def _connect_to_ldap(self):
+        connection = ldap.initialize(self.server_uri)
+
+        if self.start_tls:
+            connection.start_tls_s()
+
+        if self.bind_dn is not None:
+            connection.simple_bind_s(self.bind_dn, self.bind_pw)
+        else:
+            connection.sasl_interactive_bind_s('', ldap.sasl.external())
+
+        return connection
+
+
+from ansible.module_utils.basic import *  # noqa
+main()