Exemplo n.º 1
0
def WriteComputerMSULog(uuid, details):
  """Write log details from MSU GUI into ComputerMSULog model.

  Args:
    uuid: str, computer uuid to update
    details: dict like = {
      'event': str, 'something_happened',
      'source': str, 'MSU' or 'user',
      'user': str, 'username',
      'time': int, epoch seconds,
      'desc': str, 'additional descriptive text',
    }
  """
  uuid = common.SanitizeUUID(uuid)
  key = '%s_%s_%s' % (uuid, details['source'], details['event'])
  c = models.ComputerMSULog(key_name=key)
  c.uuid = uuid
  c.event = details['event']
  c.source = details['source']
  c.user = details['user']
  c.desc = details['desc']
  try:
    mtime = util.Datetime.utcfromtimestamp(details.get('time', None))
  except ValueError, e:
    logging.warning('Ignoring msu_log time; %s' % str(e))
    mtime = datetime.datetime.utcnow()
Exemplo n.º 2
0
def WriteBrokenClient(uuid, reason, details):
  """Saves a BrokenClient entity to Datastore for the given UUID.

  Args:
    uuid: str, uuid of client.
    reason: str, short description of broken state the client is reporting.
    details: str, details or debugging output of the broken report.
  """
  # If the details string contains facter output, parse it.
  facts = {}
  lines = details.splitlines()
  for line in lines:
    try:
      (key, unused_sep, value) = line.split(' ', 2)
    except ValueError:
      continue  # current line was not facter, continue.
    value = value.strip()
    facts[key] = value

  # Update the existing, or create a new ComputerClientBroken entity.
  uuid = common.SanitizeUUID(uuid)
  bc = models.ComputerClientBroken.get_or_insert(uuid)
  bc.broken_datetimes.append(datetime.datetime.utcnow())
  bc.reason = reason
  bc.details = details
  bc.fixed = False  # Previously fixed computers will show up again.
  bc.hostname = facts.get('hostname', '')
  bc.owner = facts.get('primary_user', '')
  bc.serial = facts.get('sp_serial_number', '')
  bc.uuid = uuid
  bc.put()
Exemplo n.º 3
0
def WriteClientLog(model, uuid, **kwargs):
  """Writes a ClientLog entry.

  Args:
    model: db.Model to write to.
    uuid: str uuid of client.
    kwargs: property/value pairs to write to the model; uuid not allowed.
  Returns:
    models.Computer instance which is this client
  """
  if 'uuid' in kwargs:
    #logging.debug('WriteClientLog: Deleting uuid from kwargs')
    del(kwargs['uuid'])

  uuid = common.SanitizeUUID(uuid)

  if 'computer' not in kwargs:
    kwargs['computer'] =  models.Computer.get_by_key_name(uuid)

  l = model(uuid=uuid, **kwargs)
  try:
    l.put()
  except (db.Error, apiproxy_errors.Error, runtime.DeadlineExceededError):
    logging.warning('WriteClientLog put() failure; deferring...')
    now_str = datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S')
    deferred_name = 'write-client-log-%s-%s' % (uuid, now_str)
    deferred.defer(
        WriteClientLog, model, uuid,
        _name=deferred_name, _countdown=5, **kwargs)

  return kwargs['computer']
Exemplo n.º 4
0
def ParseClientId(client_id, uuid=None):
    """Splits a client id string and converts all key/value pairs to a dict.

  Also, this truncates string values to 500 characters.

  Args:
    client_id: string client id with "|" as delimiter.
    uuid: optional string uuid to override the uuid in client_id.
  Returns:
    Dict. Client id string "foo=bar|key=|one=1" yields
        {'foo': 'bar', 'key': None, 'one': '1'}.
  """
    if client_id and client_id.find('\n') > -1:
        logging.warning('ParseClientId: client_id has newline: %s',
                        base64.b64encode(client_id))
        client_id = client_id.replace('\n', '_')

    # Convert str input to unicode.
    if type(client_id) is str:
        try:
            client_id = client_id.decode('utf-8')
        except UnicodeDecodeError:
            client_id = client_id.decode('utf-8', 'replace')
            logging.warning('UnicodeDecodeError on client_id: %s', client_id)

    out = KeyValueStringToDict(client_id)

    # If any required fields were not present in the client id string, add them.
    # Also cast all values to their defined output types.
    for field, value_type in CLIENT_ID_FIELDS.iteritems():
        if field not in out or out[field] is None:
            out[field] = None
        elif value_type is bool:
            out[field] = GetBoolValueFromString(out[field])
        elif value_type is str:
            # truncate str fields to 500 characters, the StringProperty limit.
            out[field] = out[field][:500]
        else:
            try:
                out[field] = value_type(out[field])
            except ValueError:
                logging.warning(
                    'Error casting client id %s to defined type: %s', field,
                    out[field])
                out[field] = None

    if out['track'] not in common.TRACKS:
        if out['track'] is not None:
            logging.warning('Invalid track requested: %s', out['track'])
        out['track'] = common.DEFAULT_TRACK

    if uuid:
        out['uuid'] = common.SanitizeUUID(uuid)

    return out
Exemplo n.º 5
0
    def put(self, file_type=None, file_name=None):
        """UploadFile PUT handler.

    Returns:
      A webapp.Response() response.
    """
        session = gaeserver.DoMunkiAuth()
        uuid = main_common.SanitizeUUID(session.uuid)

        if not file_type or not file_name:
            logging.warning('file_type=%s , file_name=%s', file_type,
                            file_name)
            self.error(httplib.NOT_FOUND)
            return

        if file_type == 'log':
            key = '%s_%s' % (uuid, file_name)
            l = models.ClientLogFile(key_name=key)
            l.log_file = self.request.body
            l.uuid = uuid
            l.name = file_name
            try:
                l.put()
            except (apiproxy_errors.RequestTooLargeError,
                    datastore_errors.BadRequestError):
                # detastore raises BadRequestError now, if file too large.
                logging.warning(
                    'UploadFile may be log too large; truncating...')
                # Datastore has a 1MB entity limit and models.ClientLogFile.log_file
                # uses zlib compression. Anecdotal evidence of a handlful of log files
                # over 8MB in size compress down to well under 1MB. Therefore, slice
                # the top of the log data off at a conversative max, before retrying the
                # Datastore put.
                max_log_size_bytes = 5 * 1024 * 1024
                l.log_file = (
                    '*** Log truncated by Simian due to size ***\n\n' +
                    self.request.body[-1 * max_log_size_bytes:])
                l.put()

            c = models.Computer.get_by_key_name(uuid)
            recipients = c.upload_logs_and_notify
            c.upload_logs_and_notify = None
            c.put()

            # c.upload_logs_and_notify may be None from a previous upload, as multiple
            # files may be uploaded in different requests per execution.
            if recipients:
                recipients = recipients.split(',')
                deferred.defer(SendNotificationEmail, recipients, c,
                               settings.SERVER_HOSTNAME)
        else:
            self.error(httplib.NOT_FOUND)
Exemplo n.º 6
0
    def post(self):
        """Reports get handler.

    Returns:
      A webapp.Response() response.
    """
        session = gaeserver.DoMunkiAuth()
        uuid = main_common.SanitizeUUID(session.uuid)
        report_type = self.request.get('_report_type')
        feedback_requested = self.request.get('_feedback')
        message = None
        details = None
        client_id = None
        computer = None

        if report_type == 'preflight' or report_type == 'postflight':
            client_id_str = urllib.unquote(self.request.get('client_id'))
            client_id = common.ParseClientId(client_id_str, uuid=uuid)
            user_settings_str = self.request.get('user_settings')
            user_settings = None
            try:
                if user_settings_str:
                    user_settings = util.Deserialize(
                        urllib.unquote(str(user_settings_str)))
            except util.DeserializeError:
                logging.warning('Client %s sent broken user_settings: %s',
                                client_id_str, user_settings_str)

            pkgs_to_install = self.request.get_all('pkgs_to_install')
            apple_updates_to_install = self.request.get_all(
                'apple_updates_to_install')

            computer = models.Computer.get_by_key_name(uuid)
            ip_address = os.environ.get('REMOTE_ADDR', '')
            report_feedback = None
            if report_type == 'preflight':
                # if the UUID is known to be lost/stolen, log this connection.
                if models.ComputerLostStolen.IsLostStolen(uuid):
                    logging.warning('Connection from lost/stolen machine: %s',
                                    uuid)
                    models.ComputerLostStolen.LogLostStolenConnection(
                        computer=computer, ip_address=ip_address)

                # we want to get feedback now, before preflight_datetime changes.
                if feedback_requested:
                    client_exit = self.request.get('client_exit', None)
                    report_feedback = self.GetReportFeedback(
                        uuid,
                        report_type,
                        computer=computer,
                        ip_address=ip_address,
                        client_exit=client_exit)
                    self.response.out.write(report_feedback)

                    # if report feedback calls for a client exit, log it.
                    if report_feedback == common.ReportFeedback.EXIT:
                        if not client_exit:
                            # client didn't ask for an exit, which means server decided.
                            client_exit = 'Connection from defined exit IP address'
                        common.WriteClientLog(models.PreflightExitLog,
                                              uuid,
                                              computer=computer,
                                              exit_reason=client_exit)

            common.LogClientConnection(report_type,
                                       client_id,
                                       user_settings,
                                       pkgs_to_install,
                                       apple_updates_to_install,
                                       computer=computer,
                                       ip_address=ip_address,
                                       report_feedback=report_feedback)

        elif report_type == 'install_report':
            computer = models.Computer.get_by_key_name(uuid)

            self._LogInstalls(self.request.get_all('installs'), computer)

            for removal in self.request.get_all('removals'):
                common.WriteClientLog(models.ClientLog,
                                      uuid,
                                      computer=computer,
                                      action='removal',
                                      details=removal)

            for problem in self.request.get_all('problem_installs'):
                common.WriteClientLog(models.ClientLog,
                                      uuid,
                                      computer=computer,
                                      action='install_problem',
                                      details=problem)
        elif report_type == 'preflight_exit':
            # NOTE(user): only remains for older clients.
            message = self.request.get('message')
            computer = common.WriteClientLog(models.PreflightExitLog,
                                             uuid,
                                             exit_reason=message)
        elif report_type == 'broken_client':
            # Default reason of "objc" to support legacy clients, existing when objc
            # was the only broken state ever reported.
            reason = self.request.get('reason', 'objc')
            details = self.request.get('details')
            logging.warning('Broken Munki client (%s): %s', reason, details)
            common.WriteBrokenClient(uuid, reason, details)
        elif report_type == 'msu_log':
            details = {}
            for k in ['time', 'user', 'source', 'event', 'desc']:
                details[k] = self.request.get(k, None)
            common.WriteComputerMSULog(uuid, details)
        else:
            # unknown report type; log all post params.
            params = []
            for param in self.request.arguments():
                params.append('%s=%s' % (param, self.request.get_all(param)))
            common.WriteClientLog(models.ClientLog,
                                  uuid,
                                  action='unknown',
                                  details=str(params))

        # If the client asked for feedback, get feedback and respond.
        # Skip this if the report_type is preflight, as report feedback was
        # retrieved before LogComputerConnection changed preflight_datetime.
        if feedback_requested and report_type != 'preflight':
            self.response.out.write(
                self.GetReportFeedback(
                    uuid,
                    report_type,
                    message=message,
                    details=details,
                    computer=computer,
                ))
Exemplo n.º 7
0
    def post(self):
        """Reports get handler.

    Returns:
      A webapp.Response() response.
    """
        session = gaeserver.DoMunkiAuth()
        uuid = main_common.SanitizeUUID(session.uuid)
        report_type = self.request.get('_report_type')
        report_feedback = {}
        message = None
        details = None
        client_id = None
        computer = None

        if report_type == 'preflight' or report_type == 'postflight':
            client_id_str = urllib.unquote(self.request.get('client_id'))
            client_id = common.ParseClientId(client_id_str, uuid=uuid)
            user_settings_str = self.request.get('user_settings')
            user_settings = None
            try:
                if user_settings_str:
                    user_settings = util.Deserialize(
                        urllib.unquote(str(user_settings_str)))
            except util.DeserializeError:
                logging.warning('Client %s sent broken user_settings: %s',
                                client_id_str, user_settings_str)

            pkgs_to_install = self.request.get_all('pkgs_to_install')
            apple_updates_to_install = self.request.get_all(
                'apple_updates_to_install')

            computer = models.Computer.get_by_key_name(uuid)
            ip_address = os.environ.get('REMOTE_ADDR', '')
            if report_type == 'preflight':
                # we want to get feedback now, before preflight_datetime changes.
                client_exit = self.request.get('client_exit', None)
                report_feedback = self.GetReportFeedback(
                    uuid,
                    report_type,
                    computer=computer,
                    ip_address=ip_address,
                    client_exit=client_exit)

                if self.request.get('json') == '1':
                    self.response.out.write(JSON_PREFIX +
                                            json.dumps(report_feedback))
                else:
                    # For legacy clients that accept a single string, not JSON.
                    feedback_to_send = 'OK'
                    for feedback in LEGACY_FEEDBACK_LIST:
                        if report_feedback.get(feedback.lower()):
                            feedback_to_send = feedback
                    self.response.out.write(feedback_to_send)

                # if report feedback calls for a client exit, log it.
                if report_feedback.get('exit'):
                    if not client_exit:
                        # client didn't ask for an exit, which means server decided.
                        client_exit = 'Connection from defined exit IP address'
                    common.WriteClientLog(models.PreflightExitLog,
                                          uuid,
                                          computer=computer,
                                          exit_reason=client_exit)

            common.LogClientConnection(report_type,
                                       client_id,
                                       user_settings,
                                       pkgs_to_install,
                                       apple_updates_to_install,
                                       computer=computer,
                                       ip_address=ip_address,
                                       report_feedback=report_feedback)

        elif report_type == 'install_report':
            computer = models.Computer.get_by_key_name(uuid)

            self._LogInstalls(self.request.get_all('installs'), computer)

            for removal in self.request.get_all('removals'):
                common.WriteClientLog(models.ClientLog,
                                      uuid,
                                      computer=computer,
                                      action='removal',
                                      details=removal)

            for problem in self.request.get_all('problem_installs'):
                common.WriteClientLog(models.ClientLog,
                                      uuid,
                                      computer=computer,
                                      action='install_problem',
                                      details=problem)
        elif report_type == 'broken_client':
            # Default reason of "objc" to support legacy clients, existing when objc
            # was the only broken state ever reported.
            reason = self.request.get('reason', 'objc')
            details = self.request.get('details')
            logging.warning('Broken Munki client (%s): %s', reason, details)
            common.WriteBrokenClient(uuid, reason, details)
        elif report_type == 'msu_log':
            details = {}
            for k in ['time', 'user', 'source', 'event', 'desc']:
                details[k] = self.request.get(k, None)
            common.WriteComputerMSULog(uuid, details)
        else:
            # unknown report type; log all post params.
            params = []
            for param in self.request.arguments():
                params.append('%s=%s' % (param, self.request.get_all(param)))
            common.WriteClientLog(models.ClientLog,
                                  uuid,
                                  action='unknown',
                                  details=str(params))