| 1 | """Provide a more Pythonic and object-oriented interface to ldb."""
|
|---|
| 2 |
|
|---|
| 3 | #
|
|---|
| 4 | # Swig interface to Samba
|
|---|
| 5 | #
|
|---|
| 6 | # Copyright (C) Tim Potter 2006
|
|---|
| 7 | #
|
|---|
| 8 | # This program is free software; you can redistribute it and/or modify
|
|---|
| 9 | # it under the terms of the GNU General Public License as published by
|
|---|
| 10 | # the Free Software Foundation; either version 3 of the License, or
|
|---|
| 11 | # (at your option) any later version.
|
|---|
| 12 | #
|
|---|
| 13 | # This program is distributed in the hope that it will be useful,
|
|---|
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|---|
| 16 | # GNU General Public License for more details.
|
|---|
| 17 | #
|
|---|
| 18 | # You should have received a copy of the GNU General Public License
|
|---|
| 19 | # along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|---|
| 20 | #
|
|---|
| 21 |
|
|---|
| 22 | #
|
|---|
| 23 | # Interface notes:
|
|---|
| 24 | #
|
|---|
| 25 | # - should an empty dn be represented as None, or an empty string?
|
|---|
| 26 | #
|
|---|
| 27 | # - should single-valued attributes be a string, or a list with one
|
|---|
| 28 | # element?
|
|---|
| 29 | #
|
|---|
| 30 |
|
|---|
| 31 | from ldb import *
|
|---|
| 32 |
|
|---|
| 33 | # Global initialisation
|
|---|
| 34 |
|
|---|
| 35 | result = ldb_global_init()
|
|---|
| 36 |
|
|---|
| 37 | if result != 0:
|
|---|
| 38 | raise LdbError, (result, 'ldb_global_init failed')
|
|---|
| 39 |
|
|---|
| 40 | # Ldb exceptions
|
|---|
| 41 |
|
|---|
| 42 | class LdbError(Exception):
|
|---|
| 43 | """An exception raised when a ldb error occurs.
|
|---|
| 44 | The exception data is a tuple consisting of the ldb number and a
|
|---|
| 45 | string description of the error."""
|
|---|
| 46 | pass
|
|---|
| 47 |
|
|---|
| 48 | # Ldb classes
|
|---|
| 49 |
|
|---|
| 50 | class LdbMessage:
|
|---|
| 51 | """A class representing a ldb message as a Python dictionary."""
|
|---|
| 52 |
|
|---|
| 53 | def __init__(self):
|
|---|
| 54 | self.mem_ctx = talloc_init(None)
|
|---|
| 55 | self.msg = ldb_msg_new(self.mem_ctx)
|
|---|
| 56 |
|
|---|
| 57 | def __del__(self):
|
|---|
| 58 | if self.mem_ctx is not None:
|
|---|
| 59 | talloc_free(self.mem_ctx)
|
|---|
| 60 | self.mem_ctx = None
|
|---|
| 61 | self.msg = None
|
|---|
| 62 |
|
|---|
| 63 | # Make the dn attribute of the object dynamic
|
|---|
| 64 |
|
|---|
| 65 | def __getattr__(self, attr):
|
|---|
| 66 | if attr == 'dn':
|
|---|
| 67 | return ldb_dn_linearize(None, self.msg.dn)
|
|---|
| 68 | return self.__dict__[attr]
|
|---|
| 69 |
|
|---|
| 70 | def __setattr__(self, attr, value):
|
|---|
| 71 | if attr == 'dn':
|
|---|
| 72 | self.msg.dn = ldb_dn_explode(self.msg, value)
|
|---|
| 73 | if self.msg.dn == None:
|
|---|
| 74 | err = ldb.ERR_INVALID_DN_SYNTAX
|
|---|
| 75 | raise LdbError(err, ldb_strerror(err))
|
|---|
| 76 | return
|
|---|
| 77 | self.__dict__[attr] = value
|
|---|
| 78 |
|
|---|
| 79 | # Get and set individual elements
|
|---|
| 80 |
|
|---|
| 81 | def __getitem__(self, key):
|
|---|
| 82 |
|
|---|
| 83 | elt = ldb_msg_find_element(self.msg, key)
|
|---|
| 84 |
|
|---|
| 85 | if elt is None:
|
|---|
| 86 | raise KeyError, "No such attribute '%s'" % key
|
|---|
| 87 |
|
|---|
| 88 | return [ldb_val_array_getitem(elt.values, i)
|
|---|
| 89 | for i in range(elt.num_values)]
|
|---|
| 90 |
|
|---|
| 91 | def __setitem__(self, key, value):
|
|---|
| 92 | ldb_msg_remove_attr(self.msg, key)
|
|---|
| 93 | if type(value) in (list, tuple):
|
|---|
| 94 | [ldb_msg_add_value(self.msg, key, v) for v in value]
|
|---|
| 95 | else:
|
|---|
| 96 | ldb_msg_add_value(self.msg, key, value)
|
|---|
| 97 |
|
|---|
| 98 | # Dictionary interface
|
|---|
| 99 | # TODO: move to iterator based interface
|
|---|
| 100 |
|
|---|
| 101 | def len(self):
|
|---|
| 102 | return self.msg.num_elements
|
|---|
| 103 |
|
|---|
| 104 | def keys(self):
|
|---|
| 105 | return [ldb_message_element_array_getitem(self.msg.elements, i).name
|
|---|
| 106 | for i in range(self.msg.num_elements)]
|
|---|
| 107 |
|
|---|
| 108 | def values(self):
|
|---|
| 109 | return [self[k] for k in self.keys()]
|
|---|
| 110 |
|
|---|
| 111 | def items(self):
|
|---|
| 112 | return [(k, self[k]) for k in self.keys()]
|
|---|
| 113 |
|
|---|
| 114 | # Misc stuff
|
|---|
| 115 |
|
|---|
| 116 | def sanity_check(self):
|
|---|
| 117 | return ldb_msg_sanity_check(self.msg)
|
|---|
| 118 |
|
|---|
| 119 | class Ldb:
|
|---|
| 120 | """A class representing a binding to a ldb file."""
|
|---|
| 121 |
|
|---|
| 122 | def __init__(self, url, flags = 0):
|
|---|
| 123 | """Initialise underlying ldb."""
|
|---|
| 124 |
|
|---|
| 125 | self.mem_ctx = talloc_init('mem_ctx for ldb 0x%x' % id(self))
|
|---|
| 126 | self.ldb_ctx = ldb_init(self.mem_ctx)
|
|---|
| 127 |
|
|---|
| 128 | result = ldb_connect(self.ldb_ctx, url, flags, None)
|
|---|
| 129 |
|
|---|
| 130 | if result != LDB_SUCCESS:
|
|---|
| 131 | raise LdbError, (result, ldb_strerror(result))
|
|---|
| 132 |
|
|---|
| 133 | def __del__(self):
|
|---|
| 134 | """Called when the object is to be garbage collected."""
|
|---|
| 135 | self.close()
|
|---|
| 136 |
|
|---|
| 137 | def close(self):
|
|---|
| 138 | """Close down a ldb."""
|
|---|
| 139 | if self.mem_ctx is not None:
|
|---|
| 140 | talloc_free(self.mem_ctx)
|
|---|
| 141 | self.mem_ctx = None
|
|---|
| 142 | self.ldb_ctx = None
|
|---|
| 143 |
|
|---|
| 144 | def _ldb_call(self, fn, *args):
|
|---|
| 145 | """Call a ldb function with args. Raise a LdbError exception
|
|---|
| 146 | if the function returns a non-zero return value."""
|
|---|
| 147 |
|
|---|
| 148 | result = fn(*args)
|
|---|
| 149 |
|
|---|
| 150 | if result != LDB_SUCCESS:
|
|---|
| 151 | raise LdbError, (result, ldb_strerror(result))
|
|---|
| 152 |
|
|---|
| 153 | def search(self, expression):
|
|---|
| 154 | """Search a ldb for a given expression."""
|
|---|
| 155 |
|
|---|
| 156 | self._ldb_call(ldb_search, self.ldb_ctx, None, LDB_SCOPE_DEFAULT,
|
|---|
| 157 | expression, None);
|
|---|
| 158 |
|
|---|
| 159 | return [LdbMessage(ldb_message_ptr_array_getitem(result.msgs, ndx))
|
|---|
| 160 | for ndx in range(result.count)]
|
|---|
| 161 |
|
|---|
| 162 | def delete(self, dn):
|
|---|
| 163 | """Delete a dn."""
|
|---|
| 164 |
|
|---|
| 165 | _dn = ldb_dn_explode(self.ldb_ctx, dn)
|
|---|
| 166 |
|
|---|
| 167 | self._ldb_call(ldb_delete, self.ldb_ctx, _dn)
|
|---|
| 168 |
|
|---|
| 169 | def rename(self, olddn, newdn):
|
|---|
| 170 | """Rename a dn."""
|
|---|
| 171 |
|
|---|
| 172 | _olddn = ldb_dn_explode(self.ldb_ctx, olddn)
|
|---|
| 173 | _newdn = ldb_dn_explode(self.ldb_ctx, newdn)
|
|---|
| 174 |
|
|---|
| 175 | self._ldb_call(ldb_rename, self.ldb_ctx, _olddn, _newdn)
|
|---|
| 176 |
|
|---|
| 177 | def add(self, m):
|
|---|
| 178 | self._ldb_call(ldb_add, self.ldb_ctx, m.msg)
|
|---|