1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import sys
20 import string
21 import pwd
22 import grp
23 import os
24 import libxml2
25 import config
26 import util
27 import cache
28 import random
29 import socket
30 import debuglog
31
32
33
34
35
36 try:
37 import ldap
38 has_ldap = True;
39 except ImportError:
40 has_ldap = False;
41
42
43
44
45
46 try:
47 import selinux
48 has_selinux = True;
49 except ImportError:
50 has_selinux = False;
51
52
53
54
55 defaultConf="""<profiles>
56 <default profile=""/>
57 </profiles>"""
58
59
60
61
62 cache.initialize()
63
66
67 -def get_setting (node, setting, default = None, convert_to = str):
68 a = node.hasProp(setting)
69 if a:
70 try:
71 return convert_to (a.content)
72 except:
73 np = node.nodePath()
74
75
76
77 raise SystemDatabaseException(_("invalid type for setting %(setting)s in %(np)s") % { "setting": setting,
78 "np": np })
79 return default
80
82 res = ""
83 i = 0
84 while i < len(string):
85 c = string[i]
86 i = i + 1
87 if c == "%":
88 if i < len(string):
89 c = string[i]
90 if c != "%":
91 if c in attrs:
92 c = attrs[c]
93 i = i + 1
94 res = res + c
95 return res
96
97
99 pass
100
102 """An encapsulation of the database which maintains an
103 association between users and profiles.
104
105 This database is stored by default in
106 $(sysconfdir)/sabayon/profiles/users.xml and contains a
107 list of users and the profile associated with each of
108 those users.
109
110 A profile can be reference by either its name (in which case
111 the profile is stored at /etc/sabayon/profiles/$(name).zip),
112 an absolute path or a http/file URL.
113 """
115 """Create a SystemDatabase object.
116
117 @db_file: a mandatory path which specifes the location
118 of the database file. If only a file is specified, the
119 directory /etc/sabayon is used.
120 """
121 if db_file is None:
122 raise SystemDatabaseException(_("No database file provided"))
123 elif db_file[0] != '/':
124 file = os.path.join (config.CONFIGDIR, db_file)
125 else:
126 file = db_file
127 self.file = file
128 self.xmlquery = None
129 self.nodes = None
130 self.modified = 0
131 dprint("New UserDatabase(%s) object\n" % self.file)
132
133 try:
134 self.doc = libxml2.readFile(file, None, libxml2.XML_PARSE_NOBLANKS)
135
136 self.doc.xincludeProcess()
137 except:
138
139 dprint("failed to parse %s falling back to default conf\n" %
140 self.file)
141 self.doc = None
142 if self.doc == None:
143 self.doc = libxml2.readMemory(defaultConf, len(defaultConf),
144 None, None,
145 libxml2.XML_PARSE_NOBLANKS)
146
148 if self.doc != None:
149 self.doc.freeDoc()
150
152 if not profile:
153 return None
154
155 uri = self.__ldap_query ("locationmap", {"p":profile, "h":socket.getfqdn()})
156 if uri:
157 return uri
158
159
160 orig_profile = profile
161 try:
162 tmp = parseURI(profile)
163 except:
164 profile = libxml2.URIEscapeStr(profile, "/:")
165
166
167 if node != None:
168 try:
169 base = node.getBase(None)
170 if base != None and base != "" and \
171 base != self.file:
172
173 ret = libxml2.buildURI(profile, base)
174 if ret[0] == '/':
175 ret = libxml2.URIUnescapeString(ret, len(ret), None)
176 dprint("Converted profile name '%s' to location '%s'\n",
177 orig_profile, ret)
178 return ret
179 except:
180 pass
181 try:
182 uri = libxml2.parseURI(profile);
183 if uri.scheme() is None:
184
185 if profile[0] != '/':
186 profile = os.path.join (config.PROFILESDIR, profile)
187 if profile[-4:] != ".zip":
188 profile = profile + ".zip"
189 else:
190
191 profile = profile
192 except:
193
194 profile = None
195
196 if profile[0] == '/':
197 profile = libxml2.URIUnescapeString(profile, len(profile), None)
198 dprint("Converted profile name '%s' to location '%s'\n",
199 orig_profile, profile)
200 return profile
201
203 ldap_node = self.nodes[0]
204
205
206
207
208 uris = get_setting (ldap_node, "uri", "ldap://localhost")
209
210
211
212
213 urilist = uris.split(',')
214
215 for uri in urilist:
216 try:
217 l = ldap.initialize (uri)
218
219 l.protocol_version = get_setting (ldap_node, "version", ldap.VERSION3, int)
220 l.timeout = get_setting (ldap_node, "timeout", 10, int)
221
222 bind_dn = get_setting (ldap_node, "bind_dn", "")
223 bind_pw = get_setting (ldap_node, "bind_pw", "")
224
225 l.simple_bind (bind_dn, bind_pw)
226
227 return l
228 except ldap.LDAPError, error_message:
229 dprint("Couldn't bind to %s: %s\n", uri, error_message)
230
232 global has_ldap
233 if not has_ldap:
234 return None
235 if not self.nodes:
236 self.nodes = self.doc.xpathEval ("/profiles/ldap/" + map)
237 if len (self.nodes) == 0:
238 has_ldap = False
239 return None
240 map_node = self.nodes[0]
241
242 l = self.__open_ldap ()
243 if not l:
244 return None
245
246 search_base = get_setting (map_node, "search_base")
247 query_filter = get_setting (map_node, "query_filter")
248 result_attribute = get_setting (map_node, "result_attribute")
249 scope = get_setting (map_node, "scope", "sub")
250 multiple_result = get_setting (map_node, "multiple_result", "first")
251
252 if search_base == None:
253 raise SystemDatabaseException(_("No LDAP search base specified for %s" % map))
254
255 if query_filter == None:
256 raise SystemDatabaseException(_("No LDAP query filter specified for %s" % map))
257
258 if result_attribute == None:
259 raise SystemDatabaseException(_("No LDAP result attribute specified for %s" % map))
260
261 if scope == "sub":
262 scope = ldap.SCOPE_SUBTREE
263 elif scope == "base":
264 scope = ldap.SCOPE_BASE
265 elif scope == "one":
266 scope = ldap.SCOPE_ONELEVEL
267 else:
268 raise SystemDatabaseException(_("LDAP Scope must be one of: ") + "sub, base, one")
269
270 query_filter = expand_string (query_filter, replace)
271 search_base = expand_string (search_base, replace)
272
273 results = l.search_s (search_base, scope, query_filter, [result_attribute])
274
275 if len (results) == 0:
276 return None
277
278 (dn, attrs) = results[0]
279 if not result_attribute in attrs:
280 return None
281 vals = attrs[result_attribute]
282
283 if multiple_result == "first":
284 val = vals[0]
285 elif multiple_result == "random":
286 val = vals[random.randint(0, len(vals)-1)]
287 else:
288 raise SystemDatabaseException(_("multiple_result must be one of: ") + "first, random")
289
290 l.unbind ()
291
292 return val
293
294
296 """Look up the default profile.
297
298 @profile_location: whether the profile location should
299 be returned
300
301 Return value: the location of the default profile, which
302 should be in a suitable form for constructing a ProfileStorage
303 object, or the default profile name if @profile_location is
304 False.
305 """
306 default = None
307 try:
308 default = self.doc.xpathEval("/profiles/default")[0]
309 profile = default.prop("profile")
310 except:
311 profile = None
312
313 if not profile_location:
314 return profile
315
316 return self.__profile_name_to_location (profile, default)
317
318 - def gen_get_profile (self, searchterm, replace, profile_location = True, ignore_default = False):
319 """Look up the profile for a given searchterm.
320
321 @username: the user whose profile location should be
322 returned.
323 @profile_location: whether the profile location should
324 be returned
325 @ignore_default: don't use the default profile if
326 no profile is explicitly set.
327
328 Return value: the location of the profile, which
329 should be in a suitable form for constructing a
330 ProfileStorage object, or the profile name if
331 @profile_location is False.
332 """
333 user = None
334 profile = self.__ldap_query ("profilemap", replace)
335 if not profile:
336 try:
337 query = self.xmlquery % searchterm
338 user = self.doc.xpathEval(query)[0]
339 profile = user.prop("profile")
340 except:
341 profile = None
342 if not profile and not ignore_default:
343 try:
344 query = "/profiles/default[1][@profile]"
345 user = self.doc.xpathEval(query)[0]
346 profile = user.prop("profile")
347 except:
348 profile = None
349
350 if not profile_location:
351 return profile
352
353
354 return self.__profile_name_to_location (profile, user)
355
357 global has_selinux
358 """Save the current version to the given filename"""
359 if filename == None:
360 filename = self.file
361
362 dprint("Saving UserDatabase to %s\n", filename)
363 try:
364 os.rename(filename, filename + ".bak")
365 backup = 1
366 except:
367 backup = 0
368 pass
369
370 try:
371 f = open(filename, 'w')
372 except:
373 if backup == 1:
374 try:
375 os.rename(filename + ".bak", filename)
376 dprint("Restore from %s.bak\n", filename)
377 except:
378 dprint("Failed to restore from %s.bak\n", filename)
379
380 raise SystemDatabaseException(
381 _("Could not open %s for writing") % filename)
382 try:
383 f.write(self.doc.serialize("UTF-8", format=1))
384 f.close()
385 except:
386 if backup == 1:
387 try:
388 os.rename(filename + ".bak", filename)
389 dprint("Restore from %s.bak\n", filename)
390 except:
391 dprint("Failed to restore from %s.bak\n", filename)
392
393 raise SystemDatabaseException(
394 _("Failed to save UserDatabase to %s") % filename)
395
396 if has_selinux:
397 if selinux.is_selinux_enabled() > 0:
398 rc, con = selinux.matchpathcon(filename, 0)
399 if rc == 0:
400 selinux.setfilecon(filename, con)
401
402 self.modified = 0
403
405 """Set the default profile to be used in this database.
406
407 @profile: the location of the profile.
408 """
409 if profile is None:
410 profile = ""
411 self.modified = 0
412 try:
413 default = self.doc.xpathEval("/profiles/default")[0]
414 oldprofile = default.prop("profile")
415 if oldprofile != profile:
416 default.setProp("profile", profile)
417 self.modified = 1
418 except:
419 try:
420 profiles = self.doc.xpathEval("/profiles")[0]
421 except:
422 raise SystemDatabaseException(
423 _("File %s is not a profile configuration") %
424 (self.file))
425 try:
426 default = profiles.newChild(None, "default", None)
427 default.setProp("profile", profile)
428 except:
429 raise SystemDatabaseException(
430 _("Failed to add default profile %s to configuration") %
431 (profile))
432 self.modified = 1
433 if self.modified == 1:
434 self.__save_as()
435
437 """Set the profile for a given searchterm.
438
439 @searchterm: the term whose profile location should be set.
440 @profile: the location of the profile.
441 """
442 if profile is None:
443 profile = ""
444 self.modified = 0
445 try:
446 query = self.xmlquery % searchterm
447 user = self.doc.xpathEval(query)[0]
448 oldprofile = user.prop("profile")
449 if oldprofile != profile:
450 user.setProp("profile", profile)
451 self.modified = 1
452 except:
453 try:
454 profiles = self.doc.xpathEval("/profiles")[0]
455 except:
456 raise SystemDatabaseException(
457 _("File %s is not a profile configuration") % (self.file))
458 try:
459 user = profiles.newChild(None, child, None)
460 user.setProp("name", searchterm)
461 user.setProp("profile", profile)
462 except:
463 raise SystemDatabaseException(
464 _("Failed to add user %s to profile configuration") %
465 (username))
466 self.modified = 1
467 if self.modified == 1:
468 self.__save_as()
469
471 """Return True if user's configuration was ever under Sabayon's
472 control.
473 """
474 profile = self.__ldap_query ("profilemap", replace)
475
476 if profile:
477 return True
478
479 try:
480 query = self.xmlquery % searchterm
481 user = self.doc.xpathEval(query)[0]
482 except:
483 return False
484
485 if user:
486 return True
487
488 return False
489
491 """Return the list of currently available profiles.
492 This is basically just list of zip files in
493 /etc/sabayon/profiles, each without the .zip extension.
494 """
495 list = []
496 try:
497 for file in os.listdir(config.PROFILESDIR):
498 if file[-4:] != ".zip":
499 continue
500 list.append(file[0:-4])
501 except:
502 dprint("Failed to read directory(%s)\n" % (config.PROFILESDIR))
503
504 return list
505
507 """Encapsulate a user mapping
508 """
516
517 - def get_profile (self, username, profile_location = True, ignore_default = False):
518 return self.gen_get_profile(username, {"u":username,
519 "h":socket.getfqdn()}, profile_location, ignore_default)
520
523
526
528 """Return the list of users on the system. These should
529 be real users - i.e. should not include system users
530 like nobody, gdm, nfsnobody etc.
531 """
532 list = []
533 try:
534 users = pwd.getpwall()
535 except:
536 raise SystemDatabaseException(_("Failed to get the user list"))
537
538 for user in pwd.getpwall():
539 try:
540
541 if user[2] < 500:
542 continue
543 if user[0] == "nobody":
544 continue
545 if user[0] in list:
546 continue
547 if user[6] == "" or string.find(user[6], "nologin") != -1:
548 continue
549 if user[0][len (user[0]) - 1] == "$":
550 continue
551 list.append(user[0])
552 except:
553 pass
554 return list
555
557 """Encapsulate a group mapping
558 """
566
567 - def get_profile (self, groupname, profile_location = True, ignore_default = False):
568 return self.gen_get_profile(groupname, {"g":groupname,
569 "h":socket.getfqdn()}, profile_location, ignore_default)
570
573
576
578 """Return the list of groups on the system. These should
579 be real groups - i.e. should not include system groups
580 like lp, udev, etc.
581 """
582 list = []
583 try:
584 groups = grp.getgrall()
585 except:
586 raise GroupDatabaseException(_("Failed to get the group list"))
587
588 for group in groups:
589
590 if group[2] < 500:
591 continue
592 if group[0] == "nogroup":
593 continue
594 if group[0] in list:
595 continue
596
597 try:
598 user = pwd.getpwnam(group[0])
599 except:
600 user = None
601 if user is not None and user[2] == group[2]:
602 continue
603 list.append(group[0])
604 return list
605
606 user_database = None
607 group_database = None
608
615
622
623
624
625
627 testuserfile = "/tmp/test_users.xml"
628 testgroupfile = "/tmp/test_groups.xml"
629 try:
630 os.unlink(testuserfile)
631 os.unlink(testgroupfile)
632 except:
633 pass
634 db = UserDatabase(testuserfile)
635 db.set_default_profile("default")
636 res = db.get_profile("localuser", False)
637 assert not res is None
638 assert res == "default"
639 db.set_profile("localuser", "groupA")
640 res = db.get_profile("localuser")
641 assert not res is None
642 assert res[-20:] == "/profiles/groupA.zip"
643 db.set_profile("localuser", "groupB")
644 res = db.get_profile("localuser")
645 assert not res is None
646 assert res[-20:] == "/profiles/groupB.zip"
647 res = db.get_users()
648 print res
649 db = GroupDatabase(testgroupfile)
650 db.set_default_profile("default")
651 res = db.get_profile("localuser", False)
652 assert not res is None
653 assert res == "default"
654 db.set_profile("localgroup", "groupA")
655 res = db.get_profile("localgroup")
656 assert not res is None
657 assert res[-20:] == "/profiles/groupA.zip"
658 db.set_profile("localgroup", "groupB")
659 res = db.get_profile("localgroup")
660 assert not res is None
661 assert res[-20:] == "/profiles/groupB.zip"
662 res = db.get_groups()
663 print res
664
665 if __name__ == "__main__":
666 util.init_gettext ()
667 run_unit_tests()
668