1 """
2 Manage LDAP data as a tree of LDIF files.
3 """
4 import os, errno, sets
5 from zope.interface import implements
6 from twisted.internet import defer, error
7 from twisted.python import failure
8 from ldaptor import entry, interfaces, attributeset, entryhelpers
9 from ldaptor.protocols.ldap import ldifprotocol, distinguishedname, ldaperrors
10 from twisted.mail.maildir import _generateMaildirName as tempName
11
13 """LDIFTree entry contains multiple LDIF entries."""
14
16 """LDIFTree entry does not contain a valid LDIF entry."""
17
19
20 """LDIFTree does not contain such entry."""
21
23 """Cannot remove root of LDAP tree"""
24
25
26
29 self.done = False
30 self.seen = []
31
32 - def gotEntry(self, obj):
34
37
39 return defer.maybeDeferred(_get, path, dn)
67
68 -def _putEntry(fileName, entry):
69 """fileName is without extension."""
70 tmp = fileName + '.' + tempName() + '.tmp'
71 f = file(tmp, 'w')
72 f.write(str(entry))
73 f.close()
74 os.rename(tmp, fileName+'.ldif')
75
76
77 -def _put(path, entry):
78 l = list(entry.dn.split())
79 assert len(l) >= 1
80 l.reverse()
81
82 entryRDN = l.pop()
83 if l:
84 grandParent = os.path.join(path,
85 *['%s.dir'%rdn for rdn in l[:-1]])
86 parentEntry = os.path.join(grandParent, '%s.ldif' % l[-1])
87 parentDir = os.path.join(grandParent, '%s.dir' % l[-1])
88 if not os.path.exists(parentDir):
89 if not os.path.exists(parentEntry):
90 raise LDIFTreeNoSuchObject, entry.dn.up()
91 try:
92 os.mkdir(parentDir)
93 except OSError, e:
94 if e.errno == errno.EEXIST:
95
96 pass
97 else:
98 raise
99 else:
100 parentDir = path
101 return _putEntry(os.path.join(parentDir, '%s'%entryRDN), entry)
102
103 -def put(path, entry):
105
106 -class LDIFTreeEntry(entry.EditableLDAPEntry,
107 entryhelpers.DiffTreeMixin,
108 entryhelpers.SubtreeFromChildrenMixin,
109 entryhelpers.MatchMixin,
110 entryhelpers.SearchByTreeWalkingMixin,
111 ):
112 implements(interfaces.IConnectedLDAPEntry)
113
114 - def __init__(self, path, dn=None, *a, **kw):
115 if dn is None:
116 dn = ''
117 entry.BaseLDAPEntry.__init__(self, dn, *a, **kw)
118 self.path = path
119 if dn != '':
120 self._load()
121
123 assert self.path.endswith('.dir')
124 entryPath = '%s.ldif' % self.path[:-len('.dir')]
125
126 parser = StoreParsedLDIF()
127
128 try:
129 f = file(entryPath)
130 except IOError, e:
131 if e.errno == errno.ENOENT:
132 return
133 else:
134 raise
135 while 1:
136 data = f.read(8192)
137 if not data:
138 break
139 parser.dataReceived(data)
140 parser.connectionLost(failure.Failure(error.ConnectionDone))
141 assert parser.done
142
143 entries = parser.seen
144 if len(entries) == 0:
145 raise LDIFTreeEntryContainsNoEntries
146 elif len(entries) > 1:
147 raise LDIFTreeEntryContainsMultipleEntries, entries
148 else:
149
150 for k,v in entries[0].items():
151 self._attributes[k] = attributeset.LDAPAttributeSet(k, v)
152
154
155 if self.dn == '':
156
157 return None
158 else:
159 parentPath, _ = os.path.split(self.path)
160 return self.__class__(parentPath, self.dn.up())
161
162 - def _sync_children(self):
163 children = []
164 try:
165 filenames = os.listdir(self.path)
166 except OSError, e:
167 if e.errno == errno.ENOENT:
168 pass
169 else:
170 raise
171 else:
172 seen = sets.Set()
173 for fn in filenames:
174 base, ext = os.path.splitext(fn)
175 if ext not in ['.dir', '.ldif']:
176 continue
177 if base in seen:
178 continue
179 seen.add(base)
180
181 dn = distinguishedname.DistinguishedName(
182 listOfRDNs=((distinguishedname.RelativeDistinguishedName(base),)
183 + self.dn.split()))
184 e = self.__class__(os.path.join(self.path, base + '.dir'), dn)
185 children.append(e)
186 return children
187
188 - def _children(self, callback=None):
189 children = self._sync_children()
190 if callback is None:
191 return children
192 else:
193 for c in children:
194 callback(c)
195 return None
196
197 - def children(self, callback=None):
198 return defer.maybeDeferred(self._children, callback=callback)
199
200 - def lookup(self, dn):
201 dn = distinguishedname.DistinguishedName(dn)
202 if not self.dn.contains(dn):
203 return defer.fail(ldaperrors.LDAPNoSuchObject(dn))
204 if dn == self.dn:
205 return defer.succeed(self)
206
207 it = dn.split()
208 me = self.dn.split()
209 assert len(it) > len(me)
210 assert ((len(me)==0) or (it[-len(me):] == me))
211 rdn = it[-len(me)-1]
212 path = os.path.join(self.path, '%s.dir' % rdn)
213 entry = os.path.join(self.path, '%s.ldif' % rdn)
214 if not os.path.isdir(path) and not os.path.isfile(entry):
215 return defer.fail(ldaperrors.LDAPNoSuchObject(dn))
216 else:
217 childDN = distinguishedname.DistinguishedName(listOfRDNs=(rdn,)+me)
218 c = self.__class__(path, childDN)
219 return c.lookup(dn)
220
221 - def _addChild(self, rdn, attributes):
222 rdn = distinguishedname.RelativeDistinguishedName(rdn)
223 for c in self._sync_children():
224 if c.dn.split()[0] == rdn:
225 raise ldaperrors.LDAPEntryAlreadyExists, c.dn
226
227 dn = distinguishedname.DistinguishedName(listOfRDNs=
228 (rdn,)
229 +self.dn.split())
230 e = entry.BaseLDAPEntry(dn, attributes)
231 if not os.path.exists(self.path):
232 os.mkdir(self.path)
233 fileName = os.path.join(self.path, '%s' % rdn)
234 tmp = fileName + '.' + tempName() + '.tmp'
235 f = file(tmp, 'w')
236 f.write(str(e))
237 f.close()
238 os.rename(tmp, fileName+'.ldif')
239
240
241 dirName = os.path.join(self.path, '%s.dir' % rdn)
242
243 e = self.__class__(dirName, dn)
244 return e
245
246 - def addChild(self, rdn, attributes):
247 d = self._addChild(rdn, attributes)
248 return d
249
251 if self.dn == '':
252 raise LDAPCannotRemoveRootError
253 if self._sync_children():
254 raise ldaperrors.LDAPNotAllowedOnNonLeaf(
255 'Cannot remove entry with children: %s' % self.dn)
256 assert self.path.endswith('.dir')
257 entryPath = '%s.ldif' % self.path[:-len('.dir')]
258 os.remove(entryPath)
259 return self
260
262 return defer.maybeDeferred(self._delete)
263
264 - def _deleteChild(self, rdn):
271
272 - def deleteChild(self, rdn):
273 return defer.maybeDeferred(self._deleteChild, rdn)
274
275 - def __repr__(self):
276 return '%s(%r, %r)' % (self.__class__.__name__,
277 self.path,
278 str(self.dn))
279
280 - def __cmp__(self, other):
281 if not isinstance(other, LDIFTreeEntry):
282 return NotImplemented
283 return cmp(self.dn, other.dn)
284
286 assert self.path.endswith('.dir')
287 entryPath = self.path[:-len('.dir')]
288 return defer.maybeDeferred(_putEntry, entryPath, self)
289
290 - def move(self, newDN):
291 return defer.maybeDeferred(self._move, newDN)
292
293 - def _move(self, newDN):
294 if not isinstance(newDN, distinguishedname.DistinguishedName):
295 newDN = distinguishedname.DistinguishedName(stringValue=newDN)
296 if newDN.up() != self.dn.up():
297
298 rootDN = self.dn
299 rootPath = self.path
300 while rootDN != '':
301 rootDN = rootDN.up()
302 rootPath = os.path.dirname(rootPath)
303 root = self.__class__(path=rootPath, dn=rootDN)
304 d = defer.maybeDeferred(root.lookup, newDN.up())
305 else:
306 d = defer.succeed(None)
307 d.addCallback(self._move2, newDN)
308 return d
309
310 - def _move2(self, newParent, newDN):
311
312 for attr in self.dn.split()[0].split():
313 self[attr.attributeType].remove(attr.value)
314
315 for attr in newDN.split()[0].split():
316
317 self[attr.attributeType].add(attr.value)
318 newRDN = newDN.split()[0]
319 srcdir = os.path.dirname(self.path)
320 if newParent is None:
321 dstdir = srcdir
322 else:
323 dstdir = newParent.path
324
325 newpath = os.path.join(dstdir, '%s.dir' % newRDN)
326 try:
327 os.rename(self.path, newpath)
328 except OSError, e:
329 if e.errno == errno.ENOENT:
330 pass
331 else:
332 raise
333 basename, ext = os.path.splitext(self.path)
334 assert ext == '.dir'
335 os.rename('%s.ldif' % basename,
336 os.path.join(dstdir, '%s.ldif' % newRDN))
337 self.dn = newDN
338 self.path = newpath
339 return self.commit()
340
341 if __name__ == '__main__':
342 """
343 Demonstration LDAP server; serves an LDIFTree from given directory
344 over LDAP on port 10389.
345 """
346
347 from twisted.internet import reactor, protocol
348 from twisted.python import log
349 import sys
350 log.startLogging(sys.stderr)
351
352 from twisted.python import components
353 from ldaptor.protocols.ldap import ldapserver
354
355 path = sys.argv[1]
356 db = LDIFTreeEntry(path)
357
361
364
365 components.registerAdapter(lambda x: x.root,
366 LDAPServerFactory,
367 interfaces.IConnectedLDAPEntry)
368
369 factory = LDAPServerFactory(db)
370 factory.protocol = MyLDAPServer
371 reactor.listenTCP(10389, factory)
372 reactor.run()
373