/[soft]/msec/trunk/src/msec/libmsec.py
ViewVC logotype

Contents of /msec/trunk/src/msec/libmsec.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6050 - (show annotations) (download) (as text)
Tue Sep 25 07:41:20 2012 UTC (11 years, 6 months ago) by kharec
File MIME type: text/x-python
File size: 36530 byte(s)
put the attribut at none is normally sufficient
1 #!/usr/bin/python -O
2 """This is the main msec module, responsible for all msec operations.
3
4 The following classes are defined here:
5
6 ConfigFile: an individual config file. This class is responsible for
7 configuration modification, variable searching and replacing,
8 and so on.
9
10 ConfigFiles: this file contains the entire set of modifications performed
11 by msec, stored in list of ConfigFile instances. When required, all
12 changes are commited back to physical files. This way, no real
13 change occurs on the system until the msec app explicitly tells
14 to do so.
15
16 Log: logging class, that supports logging to terminal, a fixed log file,
17 and syslog. A single log instance can be shared by all other
18 classes.
19
20 MSEC: main msec class. It contains the callback functions for all msec
21 operations.
22
23 All configuration variables, and config file names are defined here as well.
24 """
25
26 #---------------------------------------------------------------
27 # Project : Mandriva Linux
28 # Module : mseclib
29 # File : libmsec.py
30 # Version : $Id$
31 # Author : Eugeni Dodonov
32 # Original Author : Frederic Lepied
33 # Created On : Mon Dec 10 22:52:17 2001
34 # Purpose : low-level msec functions
35 #---------------------------------------------------------------
36
37 import os
38 import grp
39 import gettext
40 import pwd
41 import re
42 import string
43 import commands
44 import time
45 import stat
46 import traceback
47 import sys
48 import glob
49
50 # logging
51 import logging
52 from logging.handlers import SysLogHandler
53
54 # configuration
55 import config
56
57 # localization
58 try:
59 gettext.install('msec')
60 except IOError:
61 _ = str
62
63 # ConfigFile constants
64 STRING_TYPE = type('')
65
66 BEFORE=0
67 INSIDE=1
68 AFTER=2
69
70 # regexps
71 space = re.compile('\s')
72
73 # {{{ helper functions
74 def move(old, new):
75 """Renames files, deleting existent ones when necessary."""
76 try:
77 os.unlink(new)
78 except OSError:
79 pass
80 try:
81 os.rename(old, new)
82 except:
83 error('rename %s %s: %s' % (old, new, str(sys.exc_value)))
84
85 def substitute_re_result(res, s):
86 for idx in range(0, (res.lastindex or 0) + 1):
87 subst = res.group(idx) or ''
88 s = string.replace(s, '@' + str(idx), subst)
89 return s
90
91 # }}}
92
93 # {{{ Log
94 class Log:
95 """Logging class. Logs to both syslog and log file"""
96 def __init__(self,
97 app_name="msec",
98 log_syslog=True,
99 log_file=True,
100 log_level = logging.INFO,
101 log_facility=SysLogHandler.LOG_AUTHPRIV,
102 syslog_address="/dev/log",
103 log_path="/var/log/msec.log",
104 interactive=True,
105 quiet=False):
106 self.log_facility = log_facility
107 self.log_path = log_path
108
109 # buffer
110 self.buffer = None
111
112 # common logging stuff
113 self.logger = logging.getLogger(app_name)
114
115 self.quiet = quiet
116
117 # syslog
118 if log_syslog:
119 try:
120 self.syslog_h = SysLogHandler(facility=log_facility, address=syslog_address)
121 formatter = logging.Formatter('%(name)s: %(levelname)s: %(message)s')
122 self.syslog_h.setFormatter(formatter)
123 self.logger.addHandler(self.syslog_h)
124 except:
125 print >>sys.stderr, "Logging to syslog not available: %s" % (sys.exc_value[1])
126 interactive = True
127
128 # log to file
129 if log_file:
130 try:
131 self.file_h = logging.FileHandler(self.log_path)
132 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
133 self.file_h.setFormatter(formatter)
134 self.logger.addHandler(self.file_h)
135 except:
136 print >>sys.stderr, "Logging to '%s' not available: %s" % (self.log_path, sys.exc_value[1])
137 interactive = True
138
139 # interactive logging
140 if interactive:
141 self.interactive_h = logging.StreamHandler(sys.stderr)
142 formatter = logging.Formatter('%(levelname)s: %(message)s')
143 self.interactive_h.setFormatter(formatter)
144 self.logger.addHandler(self.interactive_h)
145
146 self.logger.setLevel(log_level)
147
148 def trydecode(self, message):
149 """Attempts to decode a unicode message"""
150 try:
151 msg = message.decode('UTF-*')
152 except:
153 msg = message
154 return msg
155
156 def info(self, message):
157 """Informative message (normal msec operation)"""
158 if self.quiet:
159 # skip informative messages in quiet mode
160 return
161 message = self.trydecode(message)
162 if self.buffer:
163 self.buffer["info"].append(message)
164 else:
165 self.logger.info(message)
166
167 def error(self, message):
168 """Error message (security has changed: authentication, passwords, etc)"""
169 message = self.trydecode(message)
170 if self.buffer:
171 self.buffer["error"].append(message)
172 else:
173 self.logger.error(message)
174
175 def debug(self, message):
176 """Debugging message"""
177 message = self.trydecode(message)
178 if self.buffer:
179 self.buffer["debug"].append(message)
180 else:
181 self.logger.debug(message)
182
183 def critical(self, message):
184 """Critical message (big security risk, e.g., rootkit, etc)"""
185 message = self.trydecode(message)
186 if self.buffer:
187 self.buffer["critical"].append(message)
188 else:
189 self.logger.critical(message)
190
191 def warn(self, message):
192 """Warning message (slight security change, permissions change, etc)"""
193 if self.quiet:
194 # skip warning messages in quiet mode
195 return
196 message = self.trydecode(message)
197 if self.buffer:
198 self.buffer["warn"].append(message)
199 else:
200 self.logger.warn(message)
201
202 def start_buffer(self):
203 """Beginns message buffering"""
204 self.buffer = {"info": [], "error": [], "debug": [], "critical": [], "warn": []}
205
206 def get_buffer(self):
207 """Returns buffered messages"""
208 messages = self.buffer.copy()
209 self.buffer = None
210 return messages
211
212 # }}}
213
214 # {{{ ConfigFiles - stores references to all configuration files
215 class ConfigFiles:
216 """This class is responsible to store references to all configuration files,
217 mark them as changed, and update on disk when necessary"""
218 def __init__(self, log, root=''):
219 """Initializes list of ConfigFiles"""
220 self.files = {}
221 self.modified_files = []
222 self.action_assoc = []
223 self.log = log
224 self.root = root
225
226 def add(self, file, path):
227 """Appends a path to list of files"""
228 self.files[path] = file
229
230 def modified(self, path):
231 """Marks a file as modified"""
232 if not path in self.modified_files:
233 self.modified_files.append(path)
234
235 def get_config_file(self, path, suffix=None):
236 """Retreives corresponding config file"""
237 try:
238 return self.files[path]
239 except KeyError:
240 return ConfigFile(path, self, self.log, suffix=suffix, root=self.root)
241
242 def add_config_assoc(self, regex, action):
243 """Adds association between a file and an action"""
244 self.log.debug("Adding custom command '%s' for '%s'" % (action, regex))
245 self.action_assoc.append((re.compile(regex), action))
246
247 def write_files(self, commit=True):
248 """Writes all files back to disk"""
249 for f in self.files.values():
250 self.log.debug("Attempting to write %s" % f.path)
251 if commit:
252 f.write()
253
254 if len(self.modified_files) > 0:
255 self.log.info("%s: %s" % (config.MODIFICATIONS_FOUND, " ".join(self.modified_files)))
256 else:
257 self.log.info(config.MODIFICATIONS_NOT_FOUND)
258
259 for f in self.modified_files:
260 for a in self.action_assoc:
261 res = a[0].search(f)
262 if res:
263 s = substitute_re_result(res, a[1])
264 if commit:
265 self.log.info(_('%s modified so launched command: %s') % (f, s))
266 cmd = commands.getstatusoutput(s)
267 cmd = [0, '']
268 if cmd[0] == 0:
269 if cmd[1]:
270 self.log.info(cmd[1])
271 else:
272 self.log.error(cmd[1])
273 else:
274 self.log.info(_('%s modified so should have run command: %s') % (f, s))
275
276 # }}}
277
278 # {{{ ConfigFile - an individual config file
279 class ConfigFile:
280 """This class represents an individual config file.
281 All config files are stored in meta (which is ConfigFiles).
282 All operations are performed in memory, and written when required"""
283 def __init__(self, path, meta, log, root='', suffix=None):
284 """Initializes a config file, and put reference to meta (ConfigFiles)"""
285 self.meta=meta
286 self.path = root + path
287 self.is_modified = 0
288 self.is_touched = 0
289 self.is_deleted = 0
290 self.is_moved = 0
291 self.suffix = suffix
292 self.lines = None
293 self.sym_link = None
294 self.log = log
295 self.meta.add(self, path)
296
297 def get_lines(self):
298 if self.lines == None:
299 file=None
300 try:
301 file = open(self.path, 'r')
302 except IOError:
303 if self.suffix:
304 try:
305 moved = self.path + self.suffix
306 file = open(moved, 'r')
307 move(moved, self.path)
308 self.meta.modified(self.path)
309 except IOError:
310 self.lines = []
311 else:
312 self.lines = []
313 if file:
314 self.lines = string.split(file.read(), "\n")
315 file.close()
316 return self.lines
317
318 def append(self, value):
319 lines = self.lines
320 l = len(lines)
321 if l > 0 and lines[l - 1] == '':
322 lines.insert(l - 1, value)
323 else:
324 lines.append(value)
325 lines.append('')
326
327 def modified(self):
328 self.is_modified = 1
329 self.meta.modified(self.path)
330 return self
331
332 def touch(self):
333 self.is_touched = 1
334 self.modified()
335 return self
336
337 def symlink(self, link):
338 self.sym_link = link
339 self.modified()
340 return self
341
342 def exists(self):
343 return os.path.lexists(self.path)
344 #return os.path.exists(self.path) or (self.suffix and os.path.exists(self.path + self.suffix))
345
346 def realpath(self):
347 return os.path.realpath(self.path)
348
349 def move(self, suffix):
350 self.suffix = suffix
351 self.is_moved = 1
352 self.modified()
353
354 def unlink(self):
355 self.is_deleted = 1
356 self.lines=[]
357 self.modified()
358 return self
359
360 def is_link(self):
361 '''Checks if file is a symlink and, if yes, returns the real path'''
362 full = os.stat(self.path)
363 if stat.S_ISLNK(full[stat.ST_MODE]):
364 link = os.readlink(self.path)
365 else:
366 link = None
367 return link
368
369 def write(self):
370 if self.is_deleted:
371 if self.exists():
372 try:
373 os.unlink(self.path)
374 except:
375 error('unlink %s: %s' % (self.path, str(sys.exc_value)))
376 self.log.info(_('deleted %s') % (self.path,))
377 elif self.is_touched:
378 if os.path.exists(self.path):
379 try:
380 os.utime(self.path, None)
381 except:
382 self.log.error('utime %s: %s' % (self.path, str(sys.exc_value)))
383 elif self.suffix and os.path.exists(self.path + self.suffix):
384 move(self.path + self.suffix, self.path)
385 try:
386 os.utime(self.path, None)
387 except:
388 self.log.error('utime %s: %s' % (self.path, str(sys.exc_value)))
389 else:
390 self.lines = []
391 self.is_modified = 1
392 file = open(self.path, 'w')
393 file.close()
394 self.log.info(_('touched file %s') % (self.path,))
395 elif self.sym_link:
396 done = 0
397 if self.exists():
398 full = os.lstat(self.path)
399 if stat.S_ISLNK(full[stat.ST_MODE]):
400 link = os.readlink(self.path)
401 # to be fixed: resolv relative symlink
402 done = (link == self.sym_link)
403 if not done:
404 try:
405 os.unlink(self.path)
406 except:
407 self.log.error('unlink %s: %s' % (self.path, str(sys.exc_value)))
408 self.log.info(_('deleted %s') % (self.path,))
409 if not done:
410 try:
411 os.symlink(self.sym_link, self.path)
412 except:
413 self.log.error('symlink %s %s: %s' % (self.sym_link, self.path, str(sys.exc_value)))
414 self.log.info(_('made symbolic link from %s to %s') % (self.sym_link, self.path))
415 elif self.is_moved:
416 move(self.path, self.path + self.suffix)
417 self.log.info(_('moved file %s to %s') % (self.path, self.path + self.suffix))
418 self.meta.modified(self.path)
419 elif self.is_modified:
420 content = string.join(self.lines, "\n")
421 dirname = os.path.dirname(self.path)
422 if not os.path.exists(dirname):
423 os.makedirs(dirname)
424 file = open(self.path, 'w')
425 file.write(content)
426 file.close()
427 self.meta.modified(self.path)
428 self.is_touched = 0
429 self.is_modified = 0
430 self.is_deleted = 0
431 self.is_moved = 0
432
433 def set_shell_variable(self, var, value, start=None, end=None):
434 regex = re.compile('^' + var + '="?([^#"]+)"?(.*)')
435 lines = self.get_lines()
436 idx=0
437 value=str(value)
438 start_regexp = start
439
440 if start:
441 status = BEFORE
442 start = re.compile(start)
443 else:
444 status = INSIDE
445
446 if end:
447 end = re.compile(end)
448
449 idx = None
450 for idx in range(0, len(lines)):
451 line = lines[idx]
452 if status == BEFORE:
453 if start.search(line):
454 status = INSIDE
455 else:
456 continue
457 elif end and end.search(line):
458 break
459 res = regex.search(line)
460 if res:
461 if res.group(1) != value:
462 if space.search(value):
463 lines[idx] = var + '="' + value + '"' + res.group(2)
464 else:
465 lines[idx] = var + '=' + value + res.group(2)
466 self.modified()
467 self.log.debug(_('set variable %s to %s in %s') % (var, value, self.path,))
468 return self
469 if status == BEFORE:
470 # never found the start delimiter
471 self.log.debug('WARNING: never found regexp %s in %s, not writing changes' % (start_regexp, self.path))
472 return self
473 if space.search(value):
474 s = var + '="' + value + '"'
475 else:
476 s = var + '=' + value
477 if idx == None or idx == len(lines):
478 self.append(s)
479 else:
480 lines.insert(idx, s)
481
482 self.modified()
483 self.log.debug(_('set variable %s to %s in %s') % (var, value, self.path,))
484 return self
485
486 def get_shell_variable(self, var, start=None, end=None):
487 # if file does not exists, fail quickly
488 if not self.exists():
489 return None
490 if end:
491 end=re.compile(end)
492 if start:
493 start=re.compile(start)
494 regex = re.compile('^' + var + '="?([^#"]+)"?(.*)')
495 lines = self.get_lines()
496 llen = len(lines)
497 start_idx = 0
498 end_idx = llen
499 if start:
500 found = 0
501 for idx in range(0, llen):
502 if start.search(lines[idx]):
503 start_idx = idx
504 found = 1
505 break
506 if found:
507 for idx in range(start_idx, llen):
508 if end.search(lines[idx]):
509 end_idx = idx
510 break
511 else:
512 start_idx = 0
513 for idx in range(end_idx - 1, start_idx - 1, -1):
514 res = regex.search(lines[idx])
515 if res:
516 return res.group(1)
517 return None
518
519 def get_match(self, regex, replace=None):
520 # if file does not exists, fail quickly
521 if not self.exists():
522 return None
523 r=re.compile(regex)
524 lines = self.get_lines()
525 for idx in range(0, len(lines)):
526 res = r.search(lines[idx])
527 if res:
528 if replace:
529 s = substitute_re_result(res, replace)
530 return s
531 else:
532 return lines[idx]
533 return None
534
535 def replace_line_matching(self, regex, value, at_end_if_not_found=0, all=0, start=None, end=None):
536 # if at_end_if_not_found is a string its value will be used as the string to inster
537 r=re.compile(regex)
538 lines = self.get_lines()
539 matches = 0
540
541 if start:
542 status = BEFORE
543 start = re.compile(start)
544 else:
545 status = INSIDE
546
547 if end:
548 end = re.compile(end)
549
550 idx = None
551 for idx in range(0, len(lines)):
552 line = lines[idx]
553 if status == BEFORE:
554 if start.search(line):
555 status = INSIDE
556 else:
557 continue
558 elif end and end.search(line):
559 break
560 res = r.search(line)
561 if res:
562 s = substitute_re_result(res, value)
563 matches = matches + 1
564 if s != line:
565 self.log.debug("replaced in %s the line %d:\n%s\nwith the line:\n%s" % (self.path, idx, line, s))
566 lines[idx] = s
567 self.modified()
568 if not all:
569 return matches
570 if matches == 0 and at_end_if_not_found:
571 if type(at_end_if_not_found) == STRING_TYPE:
572 value = at_end_if_not_found
573 self.log.debug("appended in %s the line:\n%s" % (self.path, value))
574 if idx == None or idx == len(lines):
575 self.append(value)
576 else:
577 lines.insert(idx, value)
578 self.modified()
579 matches = matches + 1
580 return matches
581
582 def insert_after(self, regex, value, at_end_if_not_found=0, all=0):
583 matches = 0
584 r=re.compile(regex)
585 lines = self.get_lines()
586 for idx in range(0, len(lines)):
587 res = r.search(lines[idx])
588 if res:
589 s = substitute_re_result(res, value)
590 self.log.debug("inserted in %s after the line %d:\n%s\nthe line:\n%s" % (self.path, idx, lines[idx], s))
591 lines.insert(idx+1, s)
592 self.modified()
593 matches = matches + 1
594 if not all:
595 return matches
596 if matches == 0 and at_end_if_not_found:
597 self.log.debug("appended in %s the line:\n%s" % (self.path, value))
598 self.append(value)
599 self.modified()
600 matches = matches + 1
601 return matches
602
603 def insert_before(self, regex, value, at_top_if_not_found=0, all=0):
604 matches = 0
605 r=re.compile(regex)
606 lines = self.get_lines()
607 for idx in range(0, len(lines)):
608 res = r.search(lines[idx])
609 if res:
610 s = substitute_re_result(res, value)
611 self.log.debug("inserted in %s before the line %d:\n%s\nthe line:\n%s" % (self.path, idx, lines[idx], s))
612 lines.insert(idx, s)
613 self.modified()
614 matches = matches + 1
615 if not all:
616 return matches
617 if matches == 0 and at_top_if_not_found:
618 self.log.debug("inserted at the top of %s the line:\n%s" % (self.path, value))
619 lines.insert(0, value)
620 self.modified()
621 matches = matches + 1
622 return matches
623
624 def insert_at(self, idx, value):
625 lines = self.get_lines()
626 try:
627 lines.insert(idx, value)
628 self.log.debug("inserted in %s at the line %d:\n%s" % (self.path, idx, value))
629 self.modified()
630 return 1
631 except KeyError:
632 return 0
633
634 def remove_line_matching(self, regex, all=0):
635 matches = 0
636 r=re.compile(regex)
637 lines = self.get_lines()
638 for idx in range(len(lines) - 1, -1, -1):
639 res = r.search(lines[idx])
640 if res:
641 self.log.debug("removing in %s the line %d:\n%s" % (self.path, idx, lines[idx]))
642 lines.pop(idx)
643 self.modified()
644 matches = matches + 1
645 if not all:
646 return matches
647 return matches
648 # }}}
649
650 # {{{ MSEC - main class
651 class MSEC:
652 """Main msec class. Contains all functions and performs the actions"""
653 def __init__(self, log, root='', plugins=config.PLUGINS_DIR):
654 """Initializes config files and associations"""
655 # all config files
656 self.log = log
657 self.root = root
658 self.configfiles = ConfigFiles(log, root=root)
659
660 # plugins
661 self.init_plugins(plugins)
662
663 def init_plugins(self, path=config.PLUGINS_DIR):
664 """Loads msec plugins from path"""
665 self.plugins = {}
666 plugin_files = glob.glob("%s/*.py" % path)
667 plugin_r = re.compile("plugins/(.*).py")
668 sys.path.insert(0, path)
669 for file in plugin_files:
670 f = plugin_r.findall(file)
671 if f:
672 plugin_f = f[0]
673 try:
674 plugin = __import__(plugin_f, fromlist=[path])
675 if not hasattr(plugin, "PLUGIN"):
676 # not a valid plugin
677 continue
678 self.log.debug("Loading plugin %s" % file)
679 plugin_name = getattr(plugin, "PLUGIN")
680 plugin_class = getattr(plugin, plugin_name)
681 plugin = plugin_class(log=self.log, configfiles=self.configfiles, root=self.root)
682 self.plugins[plugin_name] = plugin
683 self.log.debug("Loaded plugin '%s'" % plugin_f)
684 except:
685 self.log.error(_("Error loading plugin '%s' from %s: %s") % (plugin_f, file, sys.exc_value))
686
687 def reset(self):
688 """Resets the configuration"""
689 self.log.debug("Resetting msec data.")
690 self.configfiles = ConfigFiles(self.log, root=self.root)
691 # updating plugins
692 for plugin in self.plugins:
693 self.plugins[plugin].configfiles = self.configfiles
694
695 def get_action(self, name):
696 """Determines correspondent function for requested action."""
697 # finding out what function to call
698 try:
699 plugin_, callback = name.split(".", 1)
700 except:
701 # bad format?
702 self.log.error(_("Invalid callback: %s") % (name))
703 return None
704 # is it a main function or a plugin?
705 if plugin_ == config.MAIN_LIB:
706 plugin = self
707 else:
708 if plugin_ in self.plugins:
709 plugin = self.plugins[plugin_]
710 else:
711 self.log.info(_("Plugin %s not found") % plugin_)
712 return self.log.info
713 return None
714 try:
715 func = getattr(plugin, callback)
716 return func
717 except:
718 self.log.info(_("Not supported function '%s' in '%s'") % (callback, plugin))
719 traceback.print_exc()
720 return None
721
722 def commit(self, really_commit=True):
723 """Commits changes"""
724 if not really_commit:
725 self.log.info(_("In check-only mode, nothing is written back to disk."))
726 self.configfiles.write_files(really_commit)
727
728 def apply(self, curconfig):
729 '''Applies configuration from a MsecConfig instance'''
730 # first, reset previous msec data
731 self.reset()
732 # process all options
733 for opt in curconfig.list_options():
734 # Determines correspondent function
735 action = None
736 callback = config.find_callback(opt)
737 valid_params = config.find_valid_params(opt)
738 if callback:
739 action = self.get_action(callback)
740 if not action:
741 # The required functionality is not supported
742 self.log.debug("'%s' is not available in this version" % opt)
743 continue
744 self.log.debug("Processing action %s: %s(%s)" % (opt, callback, curconfig.get(opt)))
745 # validating parameters
746 param = curconfig.get(opt)
747 # if param is None, this option is to be skipped
748 if param == None or len(param) == 0:
749 self.log.debug("Skipping %s" % opt)
750 continue
751 if param not in valid_params and '*' not in valid_params:
752 self.log.error(_("Invalid parameter for %s: '%s'. Valid parameters: '%s'.") % (opt,
753 param, valid_params))
754 continue
755 action(curconfig.get(opt))
756
757 def base_level(self, param):
758 """Defines the base security level, on top of which the current configuration is based."""
759 pass
760
761 # }}}
762
763 # {{{ PERMS - permissions handling
764 class PERMS:
765 """Permission checking/enforcing."""
766 def __init__(self, log, root=''):
767 """Initializes internal variables"""
768 self.log = log
769 self.root = root
770 self.USER = {}
771 self.GROUP = {}
772 self.USERID = {}
773 self.GROUPID = {}
774 self.files = {}
775 self.fs_regexp = self.build_non_localfs_regexp()
776
777 def get_user_id(self, name):
778 '''Caches and retreives user id correspondent to name'''
779 try:
780 return self.USER[name]
781 except KeyError:
782 try:
783 self.USER[name] = pwd.getpwnam(name)[2]
784 except KeyError:
785 self.log.error(_('user name %s not found') % name)
786 self.USER[name] = -1
787 return self.USER[name]
788
789 def get_user_name(self, id):
790 '''Caches and retreives user name correspondent to id'''
791 try:
792 return self.USERID[id]
793 except KeyError:
794 try:
795 self.USERID[id] = pwd.getpwuid(id)[0]
796 except KeyError:
797 self.log.error(_('user name not found for id %d') % id)
798 self.USERID[id] = str(id)
799 return self.USERID[id]
800
801 def get_group_id(self, name):
802 '''Caches and retreives group id correspondent to name'''
803 try:
804 return self.GROUP[name]
805 except KeyError:
806 try:
807 self.GROUP[name] = grp.getgrnam(name)[2]
808 except KeyError:
809 self.log.error(_('group name %s not found') % name)
810 self.GROUP[name] = -1
811 return self.GROUP[name]
812
813 def get_group_name(self, id):
814 '''Caches and retreives group name correspondent to id'''
815 try:
816 return self.GROUPID[id]
817 except KeyError:
818 try:
819 self.GROUPID[id] = grp.getgrgid(id)[0]
820 except KeyError:
821 self.log.error(_('group name not found for id %d') % id)
822 self.GROUPID[id] = str(id)
823 return self.GROUPID[id]
824
825 def build_non_localfs_regexp(self,
826 non_localfs = ['nfs', 'codafs', 'smbfs', 'cifs', 'autofs']):
827 """Build a regexp that matches all the non local filesystems"""
828 try:
829 file = open('/proc/mounts', 'r')
830 except IOError:
831 self.log.error(_('Unable to check /proc/mounts. Assuming all file systems are local.'))
832 return None
833
834 regexp = None
835
836 for line in file.readlines():
837 fields = string.split(line)
838 if fields[2] in non_localfs:
839 if regexp:
840 regexp = regexp + '|' + fields[1]
841 else:
842 regexp = '^(' + fields[1]
843
844 file.close()
845
846 if not regexp:
847 return None
848 else:
849 return re.compile(regexp + ')')
850
851 def commit(self, really_commit=True, enforce=False):
852 """Commits changes.
853 If enforce is True, the permissions on all files are enforced."""
854 if not really_commit:
855 self.log.info(_("In check-only mode, nothing is written back to disk."))
856
857 if len(self.files) > 0:
858 self.log.info("%s: %s" % (config.MODIFICATIONS_FOUND, " ".join(self.files)))
859 else:
860 self.log.info(config.MODIFICATIONS_NOT_FOUND)
861
862 for file in self.files:
863 newperm, newuser, newgroup, force, newacl = self.files[file]
864 # are we in enforcing mode?
865 if enforce:
866 force = True
867
868 if newuser != None:
869 if force and really_commit:
870 self.log.warn(_("Forcing ownership of %s to %s") % (file, self.get_user_name(newuser)))
871 try:
872 os.chown(file, newuser, -1)
873 except:
874 self.log.error(_("Error changing user on %s: %s") % (file, sys.exc_value))
875 else:
876 self.log.warn(_("Wrong owner of %s: should be %s") % (file, self.get_user_name(newuser)))
877 if newgroup != None:
878 if force and really_commit:
879 self.log.warn(_("Enforcing group on %s to %s") % (file, self.get_group_name(newgroup)))
880 try:
881 os.chown(file, -1, newgroup)
882 except:
883 self.log.error(_("Error changing group on %s: %s") % (file, sys.exc_value))
884 else:
885 self.log.warn(_("Wrong group of %s: should be %s") % (file, self.get_group_name(newgroup)))
886 # permissions should be last, as chown resets them
887 # on suid files
888 if newperm != None:
889 if force and really_commit:
890 self.log.warn(_("Enforcing permissions on %s to %o") % (file, newperm))
891 try:
892 os.chmod(file, newperm)
893 except:
894 self.log.error(_("Error changing permissions on %s: %s") % (file, sys.exc_value))
895 else:
896 self.log.warn(_("Wrong permissions of %s: should be %o") % (file, newperm))
897
898 if newacl != None:
899 if force and really_commit:
900 self.log.warn(_("Enforcing acl on %s") % (file))
901 try:
902 # TODO: only change ACL if it differs from actual
903 # TODO: and use python code instead of os.system
904 os.system('setfacl -b %s' % (file))
905 users = newacl.split(",")
906 for acluser in users :
907 if acluser.split(":")[0] == "": # clean root from list
908 print acluser
909 continue
910 # make the acl rule stick
911 ret = os.system('setfacl -m u:%s %s' % (acluser, file))
912 if ret != 0:
913 # problem setting setfacl
914 self.log.error(_("Unable to add filesystem-specific ACL %s to %s") % (acluser, file))
915 except:
916 self.log.error(_("Error changing acl on %s: %s") % (file, sys.exc_value))
917 else:
918 self.log.warn(_("Wrong acl of %s") % (file))
919
920
921 def check_perms(self, perms, files_to_check=[]):
922 '''Checks permissions for all entries in perms (PermConfig).
923 If files_to_check is specified, only the specified files are checked.'''
924
925 for file in perms.list_options():
926 user_s, group_s, perm_s, force, acl = perms.get(file)
927
928 # permission
929 if perm_s == 'current':
930 perm = -1
931 else:
932 try:
933 perm = int(perm_s, 8)
934 except ValueError:
935 self.log.error(_("bad permissions for '%s': '%s'") % (file, perm_s))
936 continue
937
938 # user
939 if user_s == 'current' or not user_s:
940 user = -1
941 else:
942 user = self.get_user_id(user_s)
943
944 # group
945 if group_s == 'current' or not group_s:
946 group = -1
947 else:
948 group = self.get_group_id(group_s)
949
950 # now check the permissions
951 for f in glob.glob('%s%s' % (self.root, file)):
952 # get file properties
953 f = os.path.realpath(f)
954 try:
955 full = os.lstat(f)
956 except OSError:
957 continue
958
959 if self.fs_regexp and self.fs_regexp.search(f):
960 self.log.info(_('Non local file: "%s". Nothing changed.') % f)
961 continue
962
963 curperm = perm
964 mode = stat.S_IMODE(full[stat.ST_MODE])
965
966 if perm != -1 and stat.S_ISDIR(full[stat.ST_MODE]):
967 if curperm & 0400:
968 curperm = curperm | 0100
969 if curperm & 0040:
970 curperm = curperm | 0010
971 if curperm & 0004:
972 curperm = curperm | 0001
973
974 curuser = full[stat.ST_UID]
975 curgroup = full[stat.ST_GID]
976 curperm = mode
977 # checking for subdirectory permissions
978 if f != '/' and f[-1] == '/':
979 f = f[:-1]
980 if f[-2:] == '/.':
981 f = f[:-2]
982 # check for changes
983 newperm = None
984 newuser = None
985 newgroup = None
986 newacl = None
987 if perm != -1 and perm != curperm:
988 newperm = perm
989 if user != -1 and user != curuser:
990 newuser = user
991 if group != -1 and group != curgroup:
992 newgroup = group
993 if acl != "":
994 newacl = acl
995 if newperm != None or newuser != None or newgroup != None or newacl != None:
996 self.files[f] = (newperm, newuser, newgroup, force, newacl)
997 self.log.debug("Updating %s (matched by '%s')" % (f, file))
998 else:
999 # see if any other rule put this file into the list
1000 if f in self.files:
1001 self.log.debug("Removing previously selected %s (matched by '%s')" % (f, file))
1002 del self.files[f]
1003 # do we have to check for any specific paths?
1004 if files_to_check:
1005 self.log.info(_("Checking paths: %s") % ", ".join(files_to_check))
1006 paths_to_check = []
1007 for f in files_to_check:
1008 paths_to_check.extend(glob.glob(f))
1009 paths_to_check = set(paths_to_check)
1010 # remove unneeded entries from self.files
1011 for f in self.files.keys():
1012 if f not in paths_to_check:
1013 del self.files[f]
1014 return self.files
1015 # }}}
1016
1017 if __name__ == "__main__":
1018 # this should never ever be run directly
1019 print >>sys.stderr, """This file should not be run directly."""
1020

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.30