1# Copyright 2017 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""" 5Wrapper for D-Bus calls ot the AuthPolicy daemon. 6""" 7 8import logging 9import os 10import sys 11 12import dbus 13 14from autotest_lib.client.common_lib import error 15from autotest_lib.client.common_lib import utils 16from autotest_lib.client.cros import upstart 17 18 19class AuthPolicy(object): 20 """ 21 Wrapper for D-Bus calls ot the AuthPolicy daemon. 22 23 The AuthPolicy daemon handles Active Directory domain join, user 24 authentication and policy fetch. This class is a wrapper around the D-Bus 25 interface to the daemon. 26 27 """ 28 29 # Log file written by authpolicyd. 30 _LOG_FILE = '/var/log/authpolicy.log' 31 32 # Number of log lines to include in error logs. 33 _LOG_LINE_LIMIT = 50 34 35 # The usual system log file (minijail logs there!). 36 _SYSLOG_FILE = '/var/log/messages' 37 38 # Authpolicy daemon D-Bus parameters. 39 _DBUS_SERVICE_NAME = 'org.chromium.AuthPolicy' 40 _DBUS_SERVICE_PATH = '/org/chromium/AuthPolicy' 41 _DBUS_INTERFACE_NAME = 'org.chromium.AuthPolicy' 42 _DBUS_ERROR_SERVICE_UNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown' 43 44 # Default timeout in seconds for D-Bus calls. 45 _DEFAULT_TIMEOUT = 120 46 47 def __init__(self, bus_loop, proto_binding_location): 48 """ 49 Constructor 50 51 Creates and returns a D-Bus connection to authpolicyd. The daemon must 52 be running. 53 54 @param bus_loop: glib main loop object. 55 @param proto_binding_location: the location of generated python bindings 56 for authpolicy protobufs. 57 """ 58 59 # Pull in protobuf bindings. 60 sys.path.append(proto_binding_location) 61 62 self._bus_loop = bus_loop 63 self.restart() 64 65 def restart(self): 66 """ 67 Restarts authpolicyd and rebinds to D-Bus interface. 68 """ 69 logging.info('restarting authpolicyd') 70 upstart.restart_job('authpolicyd') 71 bus = dbus.SystemBus(self._bus_loop) 72 proxy = bus.get_object(self._DBUS_SERVICE_NAME, 73 self._DBUS_SERVICE_PATH) 74 self._authpolicyd = dbus.Interface(proxy, self._DBUS_INTERFACE_NAME) 75 76 def stop(self): 77 """ 78 Turns debug logs off. 79 80 Stops authpolicyd. 81 """ 82 logging.info('stopping authpolicyd') 83 84 # Reset log level and stop. Ignore errors that occur when authpolicy is 85 # already down. 86 try: 87 self.set_default_log_level(0) 88 except dbus.exceptions.DBusException as ex: 89 if ex.get_dbus_name() != self._DBUS_ERROR_SERVICE_UNKNOWN: 90 raise 91 try: 92 upstart.stop_job('authpolicyd') 93 except error.CmdError as ex: 94 if (ex.result_obj.exit_status == 0): 95 raise 96 97 self._authpolicyd = None 98 99 def join_ad_domain(self, 100 user_principal_name, 101 password, 102 machine_name, 103 machine_domain=None, 104 machine_ou=None): 105 """ 106 Joins a machine (=device) to an Active Directory domain. 107 108 @param user_principal_name: Logon name of the user (with @realm) who 109 joins the machine to the domain. 110 @param password: Password corresponding to user_principal_name. 111 @param machine_name: Netbios computer (aka machine) name for the joining 112 device. 113 @param machine_domain: Domain (realm) the machine should be joined to. 114 If not specified, the machine is joined to the user's realm. 115 @param machine_ou: Array of organizational units (OUs) from leaf to 116 root. The machine is put into the leaf OU. If not specified, the 117 machine account is created in the default 'Computers' OU. 118 119 @return A tuple with the ErrorType and the joined domain returned by the 120 D-Bus call. 121 122 """ 123 124 from active_directory_info_pb2 import JoinDomainRequest 125 126 request = JoinDomainRequest() 127 request.user_principal_name = user_principal_name 128 request.machine_name = machine_name 129 if machine_ou: 130 request.machine_ou.extend(machine_ou) 131 if machine_domain: 132 request.machine_domain = machine_domain 133 134 with self.PasswordFd(password) as password_fd: 135 return self._authpolicyd.JoinADDomain( 136 dbus.ByteArray(request.SerializeToString()), 137 dbus.types.UnixFd(password_fd), 138 timeout=self._DEFAULT_TIMEOUT, 139 byte_arrays=True) 140 141 def authenticate_user(self, user_principal_name, account_id, password): 142 """ 143 Authenticates a user with an Active Directory domain. 144 145 @param user_principal_name: User logon name (user@example.com) for the 146 Active Directory domain. 147 #param account_id: User account id (aka objectGUID). May be empty. 148 @param password: Password corresponding to user_principal_name. 149 150 @return A tuple with the ErrorType and an ActiveDirectoryAccountInfo 151 blob string returned by the D-Bus call. 152 153 """ 154 155 from active_directory_info_pb2 import ActiveDirectoryAccountInfo 156 from active_directory_info_pb2 import AuthenticateUserRequest 157 from active_directory_info_pb2 import ERROR_NONE 158 159 request = AuthenticateUserRequest() 160 request.user_principal_name = user_principal_name 161 if account_id: 162 request.account_id = account_id 163 164 with self.PasswordFd(password) as password_fd: 165 error_value, account_info_blob = self._authpolicyd.AuthenticateUser( 166 dbus.ByteArray(request.SerializeToString()), 167 dbus.types.UnixFd(password_fd), 168 timeout=self._DEFAULT_TIMEOUT, 169 byte_arrays=True) 170 account_info = ActiveDirectoryAccountInfo() 171 if error_value == ERROR_NONE: 172 account_info.ParseFromString(account_info_blob) 173 return error_value, account_info 174 175 def refresh_user_policy(self, account_id): 176 """ 177 Fetches user policy and sends it to Session Manager. 178 179 @param account_id: User account ID (aka objectGUID). 180 181 @return ErrorType from the D-Bus call. 182 183 """ 184 185 return self._authpolicyd.RefreshUserPolicy( 186 dbus.String(account_id), 187 timeout=self._DEFAULT_TIMEOUT, 188 byte_arrays=True) 189 190 def refresh_device_policy(self): 191 """ 192 Fetches device policy and sends it to Session Manager. 193 194 @return ErrorType from the D-Bus call. 195 196 """ 197 198 return self._authpolicyd.RefreshDevicePolicy( 199 timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) 200 201 def change_machine_password(self): 202 """ 203 Changes machine password. 204 205 @return ErrorType from the D-Bus call. 206 207 """ 208 return self._authpolicyd.ChangeMachinePasswordForTesting( 209 timeout=self._DEFAULT_TIMEOUT, byte_arrays=True) 210 211 def set_default_log_level(self, level): 212 """ 213 Fetches device policy and sends it to Session Manager. 214 215 @param level: Log level, 0=quiet, 1=taciturn, 2=chatty, 3=verbose. 216 217 @return error_message: Error message, empty if no error occurred. 218 219 """ 220 221 return self._authpolicyd.SetDefaultLogLevel(level, byte_arrays=True) 222 223 def print_log_tail(self): 224 """ 225 Prints out authpolicyd log tail. Catches and prints out errors. 226 227 """ 228 229 try: 230 cmd = 'tail -n %s %s' % (self._LOG_LINE_LIMIT, self._LOG_FILE) 231 log_tail = utils.run(cmd).stdout 232 logging.info('Tail of %s:\n%s', self._LOG_FILE, log_tail) 233 except error.CmdError as ex: 234 logging.error('Failed to print authpolicyd log tail: %s', ex) 235 236 def print_seccomp_failure_info(self): 237 """ 238 Detects seccomp failures and prints out the failing syscall. 239 240 """ 241 242 # Exit code 253 is minijail's marker for seccomp failures. 243 cmd = 'grep -q "exit code 253" %s' % self._LOG_FILE 244 if utils.run(cmd, ignore_status=True).exit_status == 0: 245 logging.error('Seccomp failure detected!') 246 cmd = 'grep -oE "blocked syscall: \\w+" %s | tail -1' % \ 247 self._SYSLOG_FILE 248 try: 249 logging.error(utils.run(cmd).stdout) 250 logging.error( 251 'This can happen if you changed a dependency of ' 252 'authpolicyd. Consider whitelisting this syscall in ' 253 'the appropriate -seccomp.policy file in authpolicyd.' 254 '\n') 255 except error.CmdError as ex: 256 logging.error( 257 'Failed to determine reason for seccomp issue: %s', ex) 258 259 def clear_log(self): 260 """ 261 Clears the authpolicy daemon's log file. 262 263 """ 264 265 try: 266 utils.run('echo "" > %s' % self._LOG_FILE) 267 except error.CmdError as ex: 268 logging.error('Failed to clear authpolicyd log file: %s', ex) 269 270 class PasswordFd(object): 271 """ 272 Writes password into a file descriptor. 273 274 Use in a 'with' statement to automatically close the returned file 275 descriptor. 276 277 @param password: Plaintext password string. 278 279 @return A file descriptor (pipe) containing the password. 280 281 """ 282 283 def __init__(self, password): 284 self._password = password 285 self._read_fd = None 286 287 def __enter__(self): 288 """Creates the password file descriptor.""" 289 self._read_fd, write_fd = os.pipe() 290 os.write(write_fd, self._password) 291 os.close(write_fd) 292 return self._read_fd 293 294 def __exit__(self, mytype, value, traceback): 295 """Closes the password file descriptor again.""" 296 if self._read_fd: 297 os.close(self._read_fd) 298