Home | History | Annotate | Download | only in security_AccountsBaseline
      1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import os
      7 import shutil
      8 
      9 from autotest_lib.client.bin import test, utils
     10 from autotest_lib.client.common_lib import error
     11 
     12 class security_AccountsBaseline(test.test):
     13     version = 1
     14 
     15 
     16     @staticmethod
     17     def match_passwd(expected, actual):
     18         """Match login shell (2nd field), uid (3rd field),
     19            and gid (4th field)."""
     20         if expected[1:4] != actual[1:4]:
     21             logging.error(
     22                 "Expected shell/uid/gid %s for user '%s', got %s.",
     23                 tuple(expected[1:4]), expected[0], tuple(actual[1:4]))
     24             return False
     25         return True
     26 
     27 
     28     @staticmethod
     29     def match_group(expected, actual):
     30         """Match login shell (2nd field), gid (3rd field),
     31            and members (4th field, comma-separated)."""
     32         matched = True
     33         if expected[1:3] != actual[1:3]:
     34             matched = False
     35             logging.error(
     36                 "Expected shell/id %s for group '%s', got %s.",
     37                 tuple(expected[1:3]), expected[0], tuple(actual[1:3]))
     38         if set(expected[3].split(',')) != set(actual[3].split(',')):
     39             matched = False
     40             logging.error(
     41                 "Expected members '%s' for group '%s', got '%s'.",
     42                 expected[3], expected[0], actual[3])
     43         return matched
     44 
     45 
     46     def load_path(self, path):
     47         """Load the given passwd/group file."""
     48         return [x.strip().split(':') for x in open(path).readlines()]
     49 
     50 
     51     def capture_files(self):
     52         for f in ['passwd','group']:
     53             shutil.copyfile(os.path.join('/etc', f),
     54                             os.path.join(self.resultsdir, f))
     55 
     56 
     57     def check_file(self, basename):
     58         match_func = getattr(self, 'match_%s' % basename)
     59         success = True
     60 
     61         expected_entries = self.load_path(
     62             os.path.join(self.bindir, 'baseline.%s' % basename))
     63 
     64         # TODO(spang): Remove this once per-board baselines are supported
     65         # (crbug.com/406013).
     66         if utils.is_freon():
     67             extra_baseline = 'baseline.%s.freon' % basename
     68         else:
     69             extra_baseline = 'baseline.%s.x11' % basename
     70 
     71         expected_entries += self.load_path(
     72             os.path.join(self.bindir, extra_baseline))
     73 
     74         actual_entries = self.load_path('/etc/%s' % basename)
     75 
     76         if len(actual_entries) > len(expected_entries):
     77             success = False
     78             logging.error(
     79                 '%s baseline mismatch: expected %d entries, got %d.',
     80                 basename, len(expected_entries), len(actual_entries))
     81 
     82         for actual in actual_entries:
     83             expected = [x for x in expected_entries if x[0] == actual[0]]
     84             if not expected:
     85                 success = False
     86                 logging.error("Unexpected %s entry for '%s'.",
     87                               basename, actual[0])
     88                 continue
     89             expected = expected[0]
     90             match_res = match_func(expected, actual)
     91             success = success and match_res
     92 
     93         for expected in expected_entries:
     94             actual = [x for x in actual_entries if x[0] == expected[0]]
     95             if not actual:
     96                 logging.info("Ignoring missing %s entry for '%s'.",
     97                              basename, expected[0])
     98 
     99         return success
    100 
    101 
    102     def run_once(self):
    103         self.capture_files()
    104 
    105         passwd_ok = self.check_file('passwd')
    106         group_ok = self.check_file('group')
    107 
    108         # Fail after all mismatches have been reported.
    109         if not (passwd_ok and group_ok):
    110             raise error.TestFail('Baseline mismatch.')
    111