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 5import logging 6import os 7import shutil 8 9from autotest_lib.client.bin import test, utils 10from autotest_lib.client.common_lib import error 11 12class 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