crash-reporter: Replace the crash_sender script by a service daemon.

BUG=chromium:391887
TEST=Integration tests
CQ-DEPEND=I02ce7593fcfae4ba1d7d3ebdf3912901e635f1c9
CQ-DEPEND=I00315ad47657cebd9e8a4a0121ecb54114a7e200

Change-Id: I2f4546c82fb3769b5f3da5d22949551412096b10
Reviewed-on: https://chromium-review.googlesource.com/208671
Tested-by: Benjamin Lerman <qsr@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
diff --git a/crash_reporter/crash-reporter.gyp b/crash_reporter/crash-reporter.gyp
index 79faf97..f36d1d0 100644
--- a/crash_reporter/crash-reporter.gyp
+++ b/crash_reporter/crash-reporter.gyp
@@ -63,17 +63,51 @@
       ],
     },
     {
-      'target_name': 'list_proxies',
+      'target_name': 'libproxies',
+      'type': 'static_library',
+      'variables': {
+        'exported_deps': [
+          'libchrome-<(libbase_ver)',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'libproxies.cc',
+        'libproxies.h',
+      ],
+    },
+    {
+      'target_name': 'crash_sender',
       'type': 'executable',
       'variables': {
         'deps': [
           'dbus-1',
-          'dbus-glib-1',
           'libchrome-<(libbase_ver)',
+          'libchromeos-<(libbase_ver)',
+          'libcurl',
+          'libmetrics-<(libbase_ver)',
         ],
       },
+      'dependencies': [
+        'libproxies',
+      ],
+      'libraries': [
+        '-lvboot_host',
+      ],
       'sources': [
-        'list_proxies.cc',
+        'crash_sender_daemon.cc',
+        'crash_sender_daemon.h',
+        'crash_sender_service.cc',
+        'crash_sender_service.h',
+        'proxy_resolver.cc',
+        'proxy_resolver.h',
       ],
     },
     {
diff --git a/crash_reporter/crash_sender b/crash_reporter/crash_sender
deleted file mode 100755
index c83a12a..0000000
--- a/crash_reporter/crash_sender
+++ /dev/null
@@ -1,685 +0,0 @@
-#!/bin/sh
-
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-set -e
-
-# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
-CHROMEOS_PRODUCT=ChromeOS
-
-# File whose existence implies crash reports may be sent, and whose
-# contents includes our machine's anonymized guid.
-CONSENT_ID="/home/chronos/Consent To Send Stats"
-
-# Crash sender lock in case the sender is already running.
-CRASH_SENDER_LOCK="/var/lock/crash_sender"
-
-# Path to file that indicates a crash test is currently running.
-CRASH_TEST_IN_PROGRESS_FILE="/tmp/crash-test-in-progress"
-
-# Path to find which is required for computing the crash rate.
-FIND="/usr/bin/find"
-
-# Set this to 1 in the environment to allow uploading crash reports
-# for unofficial versions.
-FORCE_OFFICIAL=${FORCE_OFFICIAL:-0}
-
-# Path to hardware class description.
-HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID"
-
-# Path to file that indicates this is a developer image.
-LEAVE_CORE_FILE="/root/.leave_core"
-
-# Path to list_proxies.
-LIST_PROXIES="/usr/bin/list_proxies"
-
-# Maximum crashes to send per day.
-MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
-
-# Path to metrics_client.
-METRICS_CLIENT="/usr/bin/metrics_client"
-
-# File whose existence mocks crash sending.  If empty we pretend the
-# crash sending was successful, otherwise unsuccessful.
-MOCK_CRASH_SENDING="/tmp/mock-crash-sending"
-
-# Set this to 1 in the environment to pretend to have booted in developer
-# mode.  This is used by autotests.
-MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0}
-
-# Ignore PAUSE_CRASH_SENDING file if set.
-OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0}
-
-# File whose existence causes crash sending to be delayed (for testing).
-# Must be stateful to enable testing kernel crashes.
-PAUSE_CRASH_SENDING="/var/lib/crash_sender_paused"
-
-# URL to send official build crash reports to.
-REPORT_UPLOAD_PROD_URL="https://clients2.google.com/cr/report"
-
-# Path to a directory of restricted certificates which includes
-# a certificate for ${REPORT_UPLOAD_PROD_URL}.
-RESTRICTED_CERTIFICATES_PATH="/usr/share/chromeos-ca-certificates"
-
-# File whose existence implies we're running and not to start again.
-RUN_FILE="/var/run/crash_sender.pid"
-
-# Maximum time to sleep between sends.
-SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600}
-
-# The syslog tag for all logging we emit.
-TAG="$(basename $0)[$$]"
-
-# Directory to store timestamp files indicating the uploads in the past 24
-# hours.
-TIMESTAMPS_DIR="/var/lib/crash_sender"
-
-# Temp directory for this process.
-TMP_DIR=""
-
-# Chrome's crash report log file.
-CHROME_CRASH_LOG="/var/log/chrome/Crash Reports/uploads.log"
-
-lecho() {
-  logger -t "${TAG}" "$@"
-}
-
-# Returns true if mock is enabled.
-is_mock() {
-  [ -f "${MOCK_CRASH_SENDING}" ] && return 0
-  return 1
-}
-
-is_mock_successful() {
-  local mock_in=$(cat "${MOCK_CRASH_SENDING}")
-  [ "${mock_in}" = "" ] && return 0  # empty file means success
-  return 1
-}
-
-cleanup() {
-  if [ -n "${TMP_DIR}" ]; then
-    rm -rf "${TMP_DIR}"
-  fi
-  rm -f "${RUN_FILE}"
-  crash_done
-}
-
-crash_done() {
-  if is_mock; then
-    # For testing purposes, emit a message to log so that we
-    # know when the test has received all the messages from this run.
-    lecho "crash_sender done."
-  fi
-}
-
-is_official_image() {
-  [ ${FORCE_OFFICIAL} -ne 0 ] && return 0
-  grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | grep -q Official
-}
-
-# Returns 0 if the a crash test is currently running.  NOTE: Mirrors
-# crash_collector.cc:CrashCollector::IsCrashTestInProgress().
-is_crash_test_in_progress() {
-  [ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0
-  return 1
-}
-
-# Returns 0 if we should consider ourselves to be running on a developer
-# image.  NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
-is_developer_image() {
-  # If we're testing crash reporter itself, we don't want to special-case
-  # for developer images.
-  is_crash_test_in_progress && return 1
-  [ -f "${LEAVE_CORE_FILE}" ] && return 0
-  return 1
-}
-
-# Returns 0 if we should consider ourselves to be running on a test image.
-is_test_image() {
-  # If we're testing crash reporter itself, we don't want to special-case
-  # for test images.
-  is_crash_test_in_progress && return 1
-  case $(get_channel) in
-  test*) return 0;;
-  esac
-  return 1
-}
-
-# Returns 0 if the machine booted up in developer mode.
-is_developer_mode() {
-  [ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0
-  # If we're testing crash reporter itself, we don't want to special-case
-  # for developer mode.
-  is_crash_test_in_progress && return 1
-  crossystem "devsw_boot?1"  # exit status will be accurate
-}
-
-# Generate a uniform random number in 0..max-1.
-generate_uniform_random() {
-  local max=$1
-  local random="$(od -An -N4 -tu /dev/urandom)"
-  echo $((random % max))
-}
-
-# Check if sending a crash now does not exceed the maximum 24hr rate and
-# commit to doing so, if not.
-check_rate() {
-  mkdir -p ${TIMESTAMPS_DIR}
-  # Only consider minidumps written in the past 24 hours by removing all older.
-  ${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) \
-      -exec rm -- '{}' ';'
-  local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w)
-  lecho "Current send rate: ${sends_in_24hrs}sends/24hrs"
-  if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then
-    lecho "Cannot send more crashes:"
-    lecho "  current ${sends_in_24hrs}send/24hrs >= " \
-          "max ${MAX_CRASH_RATE}send/24hrs"
-    return 1
-  fi
-  mktemp "${TIMESTAMPS_DIR}"/XXXX > /dev/null
-  return 0
-}
-
-# Gets the base part of a crash report file, such as name.01234.5678.9012 from
-# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz.  We make sure
-# "name" is sanitized in CrashCollector::Sanitize to not include any periods.
-get_base() {
-  echo "$1" | cut -d. -f-4
-}
-
-get_extension() {
-  local extension="${1##*.}"
-  local filename="${1%.*}"
-  # For gzipped file, we ignore .gz and get the real extension
-  if [ "${extension}" = "gz" ]; then
-    echo "${filename##*.}"
-  else
-    echo "${extension}"
-  fi
-}
-
-# Return which kind of report the given metadata file relates to
-get_kind() {
-  local payload="$(get_key_value "$1" "payload")"
-  if [ ! -r "${payload}" ]; then
-    lecho "Missing payload: ${payload}"
-    echo "undefined"
-    return
-  fi
-  local kind="$(get_extension "${payload}")"
-  if [ "${kind}" = "dmp" ]; then
-    echo "minidump"
-    return
-  fi
-  echo "${kind}"
-}
-
-get_key_value() {
-  local file="$1" key="$2" value
-
-  if [ -f "${file}" ]; then
-    # Return the first entry.  There shouldn't be more than one anyways.
-    # Substr at length($1) + 2 skips past the key and following = sign (awk
-    # uses 1-based indexes), but preserves embedded = characters.
-    value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}")
-  fi
-
-  echo "${value:-undefined}"
-}
-
-get_keys() {
-  local file="$1" regex="$2"
-
-  awk -F'[[:space:]=]' -vregex="${regex}" \
-      'match($1, regex) { print $1 }' "${file}"
-}
-
-# Return the board name.
-get_board() {
-  get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_BOARD"
-}
-
-# Return the channel name (sans "-channel" suffix).
-get_channel() {
-  get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_TRACK" |
-    sed 's:-channel$::'
-}
-
-# Return the hardware class or "undefined".
-get_hardware_class() {
-  if [ -r "${HWCLASS_PATH}" ]; then
-    cat "${HWCLASS_PATH}"
-  elif crossystem hwid > /dev/null 2>&1; then
-    echo "$(crossystem hwid)"
-  else
-    echo "undefined"
-  fi
-}
-
-send_crash() {
-  local meta_path="$1"
-  local report_payload="$(get_key_value "${meta_path}" "payload")"
-  local kind="$(get_kind "${meta_path}")"
-  local exec_name="$(get_key_value "${meta_path}" "exec_name")"
-  local url="${REPORT_UPLOAD_PROD_URL}"
-  local chromeos_version="$(get_key_value "${meta_path}" "ver")"
-  local board="$(get_board)"
-  local hwclass="$(get_hardware_class)"
-  local write_payload_size="$(get_key_value "${meta_path}" "payload_size")"
-  local log="$(get_key_value "${meta_path}" "log")"
-  local sig="$(get_key_value "${meta_path}" "sig")"
-  local send_payload_size="$(stat --printf=%s "${report_payload}" 2>/dev/null)"
-  local product="$(get_key_value "${meta_path}" "upload_var_prod")"
-  local version="$(get_key_value "${meta_path}" "upload_var_ver")"
-  local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")"
-  local guid
-
-  set -- \
-    -F "write_payload_size=${write_payload_size}" \
-    -F "send_payload_size=${send_payload_size}"
-  if [ "${sig}" != "undefined" ]; then
-    set -- "$@" \
-      -F "sig=${sig}" \
-      -F "sig2=${sig}"
-  fi
-  if [ -r "${report_payload}" ]; then
-    set -- "$@" \
-      -F "upload_file_${kind}=@${report_payload}"
-  fi
-  if [ "${log}" != "undefined" -a -r "${log}" ]; then
-    set -- "$@" \
-      -F "log=@${log}"
-  fi
-
-  if [ "${upload_prefix}" = "undefined" ]; then
-    upload_prefix=""
-  fi
-
-  # Grab any variable that begins with upload_.
-  local v
-  for k in $(get_keys "${meta_path}" "^upload_"); do
-    v="$(get_key_value "${meta_path}" "${k}")"
-    case ${k} in
-      # Product & version are handled separately.
-      upload_var_prod) ;;
-      upload_var_ver) ;;
-      upload_var_*)
-        set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}"
-        ;;
-      upload_file_*)
-        if [ -r "${v}" ]; then
-          set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}"
-        fi
-        ;;
-    esac
-  done
-
-  # When uploading Chrome reports we need to report the right product and
-  # version. If the meta file does not specify it, use GOOGLE_CRASH_ID
-  # as the product and GOOGLE_CRASH_VERSION_ID as the version.
-  if [ "${product}" = "undefined" ]; then
-    product="$(get_key_value /etc/os-release 'GOOGLE_CRASH_ID')"
-  fi
-  if [ "${version}" = "undefined" ]; then
-    version="$(get_key_value /etc/os-release 'GOOGLE_CRASH_VERSION_ID')"
-  fi
-
-  # If GOOGLE_CRASH_* is undefined, we look for ID and VERSION_ID in
-  # /etc/os-release.
-  if [ "${product}" = "undefined" ]; then
-    product="$(get_key_value /etc/os-release 'ID')"
-  fi
-  if [ "${version}" = "undefined" ]; then
-    version="$(get_key_value /etc/os-release 'VERSION_ID')"
-  fi
-
-  # If ID or VERSION_ID is undefined, we use the default product name
-  # and CHROMEOS_RELEASE_VERSION from /etc/lsb-release.
-  if [ "${product}" = "undefined" ]; then
-    product="${CHROMEOS_PRODUCT}"
-  fi
-  if [ "${version}" = "undefined" ]; then
-    version="${chromeos_version}"
-  fi
-
-  local image_type
-  if is_test_image; then
-    image_type="test"
-  elif is_developer_image; then
-    image_type="dev"
-  elif [ ${FORCE_OFFICIAL} -ne 0 ]; then
-    image_type="force-official"
-  elif is_mock && ! is_mock_successful; then
-    image_type="mock-fail"
-  fi
-
-  local boot_mode
-  if ! crossystem "cros_debug" > /dev/null 2>&1; then
-    # Sanity-check failed that makes sure crossystem exists.
-    lecho "Cannot determine boot mode due to error running crossystem command"
-    boot_mode="missing-crossystem"
-  elif is_developer_mode; then
-    boot_mode="dev"
-  fi
-
-  # Need to strip dashes ourselves as Chrome preserves it in the file
-  # nowadays.  This is also what the Chrome breakpad client does.
-  guid=$(tr -d '-' < "${CONSENT_ID}")
-
-  local error_type="$(get_key_value "${meta_path}" "error_type")"
-  [ "${error_type}" = "undefined" ] && error_type=
-
-  lecho "Sending crash:"
-  if [ "${product}" != "${CHROMEOS_PRODUCT}" ]; then
-    lecho "  Sending crash report on behalf of ${product}"
-  fi
-  lecho "  Metadata: ${meta_path} (${kind})"
-  lecho "  Payload: ${report_payload}"
-  lecho "  Version: ${version}"
-  [ -n "${image_type}" ] && lecho "  Image type: ${image_type}"
-  [ -n "${boot_mode}" ] && lecho "  Boot mode: ${boot_mode}"
-  if is_mock; then
-    lecho "  Product: ${product}"
-    lecho "  URL: ${url}"
-    lecho "  Board: ${board}"
-    lecho "  HWClass: ${hwclass}"
-    lecho "  write_payload_size: ${write_payload_size}"
-    lecho "  send_payload_size: ${send_payload_size}"
-    if [ "${log}" != "undefined" ]; then
-      lecho "  log: @${log}"
-    fi
-    if [ "${sig}" != "undefined" ]; then
-      lecho "  sig: ${sig}"
-    fi
-  fi
-  lecho "  Exec name: ${exec_name}"
-  [ -n "${error_type}" ] && lecho "  Error type: ${error_type}"
-  if is_mock; then
-    if ! is_mock_successful; then
-      lecho "Mocking unsuccessful send"
-      return 1
-    fi
-    lecho "Mocking successful send"
-    return 0
-  fi
-
-  # Read in the first proxy, if any, for a given URL.  NOTE: The
-  # double-quotes are necessary due to a bug in dash with the "local"
-  # builtin command and values that have spaces in them (see
-  # "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097").
-  local proxy="`${LIST_PROXIES} -quiet "${url}" | head -1`"
-  # if a direct connection should be used, unset the proxy variable.
-  [ "${proxy}" = "direct://" ] && proxy=
-  local report_id="${TMP_DIR}/report_id"
-  local curl_stderr="${TMP_DIR}/curl_stderr"
-
-  set +e
-  curl "${url}" -v ${proxy:+--proxy "$proxy"} \
-    --capath "${RESTRICTED_CERTIFICATES_PATH}" --ciphers HIGH \
-    -F "prod=${product}" \
-    -F "ver=${version}" \
-    -F "board=${board}" \
-    -F "hwclass=${hwclass}" \
-    -F "exec_name=${exec_name}" \
-    ${image_type:+-F "image_type=${image_type}"} \
-    ${boot_mode:+-F "boot_mode=${boot_mode}"} \
-    ${error_type:+-F "error_type=${error_type}"} \
-    -F "guid=${guid}" \
-    -o "${report_id}" \
-    "$@" \
-    2>"${curl_stderr}"
-  curl_result=$?
-  set -e
-
-  if [ ${curl_result} -eq 0 ]; then
-    local id="$(cat "${report_id}")"
-    local product_name
-    local timestamp="$(date +%s)"
-    case ${product} in
-    Chrome_ChromeOS)
-      if is_official_image; then
-        product_name="Chrome"
-      else
-        product_name="Chromium"
-      fi
-      ;;
-    *)
-      if is_official_image; then
-        product_name="ChromeOS"
-      else
-        product_name="ChromiumOS"
-      fi
-      ;;
-    esac
-    printf '%s,%s,%s\n' \
-      "${timestamp}" "${id}" "${product_name}" >> "${CHROME_CRASH_LOG}"
-    lecho "Crash report receipt ID ${id}"
-  else
-    lecho "Crash sending failed with exit code ${curl_result}: " \
-      "$(cat "${curl_stderr}")"
-  fi
-
-  rm -f "${report_id}"
-
-  return ${curl_result}
-}
-
-# *.meta files always end with done=1 so we can tell if they are complete.
-is_complete_metadata() {
-  grep -q "done=1" "$1"
-}
-
-# Remove the given report path.
-remove_report() {
-  local base="${1%.*}"
-  rm -f -- "${base}".*
-}
-
-# Send all crashes from the given directory.  This applies even when we're on a
-# 3G connection (see crosbug.com/3304 for discussion).
-send_crashes() {
-  local dir="$1"
-
-  if [ ! -d "${dir}" ]; then
-    return
-  fi
-
-  # Consider any old files which still have no corresponding meta file
-  # as orphaned, and remove them.
-  for old_file in $(${FIND} "${dir}" -mindepth 1 \
-                    -mmin +$((24 * 60)) -type f); do
-    if [ ! -e "$(get_base "${old_file}").meta" ]; then
-      lecho "Removing old orphaned file: ${old_file}."
-      rm -f -- "${old_file}"
-    fi
-  done
-
-  # Look through all metadata (*.meta) files, oldest first.  That way, the rate
-  # limit does not stall old crashes if there's a high amount of new crashes
-  # coming in.
-  # For each crash report, first evaluate conditions that might lead to its
-  # removal to honor user choice and to free disk space as soon as possible,
-  # then decide whether it should be sent right now or kept for later sending.
-  for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do
-    lecho "Considering metadata ${meta_path}."
-
-    local kind=$(get_kind "${meta_path}")
-    if [ "${kind}" != "minidump" ] && \
-       [ "${kind}" != "kcrash" ] && \
-       [ "${kind}" != "log" ]; then
-      lecho "Unknown report kind ${kind}.  Removing report."
-      remove_report "${meta_path}"
-      continue
-    fi
-
-    if ! is_complete_metadata "${meta_path}"; then
-      # This report is incomplete, so if it's old, just remove it.
-      local old_meta=$(${FIND} "${dir}" -mindepth 1 -name \
-        $(basename "${meta_path}") -mmin +$((24 * 60)) -type f)
-      if [ -n "${old_meta}" ]; then
-        lecho "Removing old incomplete metadata."
-        remove_report "${meta_path}"
-      else
-        lecho "Ignoring recent incomplete metadata."
-      fi
-      continue
-    fi
-
-    if ! is_mock && ! is_official_image; then
-      lecho "Not an official OS version.  Removing crash."
-      remove_report "${meta_path}"
-      continue
-    fi
-
-    # Don't send crash reports from previous sessions while we're in guest mode
-    # to avoid the impression that crash reporting was enabled, which it isn't.
-    # (Don't exit right now because subsequent reports may be candidates for
-    # deletion.)
-    if ${METRICS_CLIENT} -g; then
-      lecho "Guest mode has been entered.  Delaying crash sending until exited."
-      continue
-    fi
-
-    # Remove existing crashes in case user consent has not (yet) been given or
-    # has been revoked.  This must come after the guest mode check because
-    # ${METRICS_CLIENT} always returns "not consented" in guest mode.
-    if ! ${METRICS_CLIENT} -c; then
-      lecho "Crash reporting is disabled.  Removing crash."
-      remove_report "${meta_path}"
-      continue
-    fi
-
-    # Skip report if the upload rate is exceeded.  (Don't exit right now because
-    # subsequent reports may be candidates for deletion.)
-    if ! check_rate; then
-      lecho "Sending ${meta_path} would exceed rate.  Leaving for later."
-      continue
-    fi
-
-    # The .meta file should be written *after* all to-be-uploaded files that it
-    # references.  Nevertheless, as a safeguard, a hold-off time of thirty
-    # seconds after writing the .meta file is ensured.  Also, sending of crash
-    # reports is spread out randomly by up to SECONDS_SEND_SPREAD.  Thus, for
-    # the sleep call the greater of the two delays is used.
-    local now=$(date +%s)
-    local holdoff_time=$(($(stat --format=%Y "${meta_path}") + 30 - ${now}))
-    local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}")
-    local sleep_time
-    if [ ${spread_time} -gt ${holdoff_time} ]; then
-      sleep_time="${spread_time}"
-    else
-      sleep_time="${holdoff_time}"
-    fi
-    lecho "Scheduled to send in ${sleep_time}s."
-    if ! is_mock; then
-      if ! sleep "${sleep_time}"; then
-          lecho "Sleep failed"
-          return 1
-      fi
-    fi
-
-    # Try to upload.
-    if ! send_crash "${meta_path}"; then
-      lecho "Problem sending ${meta_path}, not removing."
-      continue
-    fi
-
-    # Send was successful, now remove.
-    lecho "Successfully sent crash ${meta_path} and removing."
-    remove_report "${meta_path}"
-  done
-}
-
-usage() {
-  cat <<EOF
-Usage: crash_sender [options]
-
-Options:
- -e <var>=<val>     Set env |var| to |val| (only some vars)
-EOF
-  exit ${1:-1}
-}
-
-parseargs() {
-  # Parse the command line arguments.
-  while [ $# -gt 0 ]; do
-    case $1 in
-    -e)
-      shift
-      case $1 in
-      FORCE_OFFICIAL=*|\
-      MAX_CRASH_RATE=*|\
-      MOCK_DEVELOPER_MODE=*|\
-      OVERRIDE_PAUSE_SENDING=*|\
-      SECONDS_SEND_SPREAD=*)
-        export "$1"
-        ;;
-      *)
-        lecho "Unknown var passed to -e: $1"
-        exit 1
-        ;;
-      esac
-      ;;
-    -h)
-      usage 0
-      ;;
-    *)
-      lecho "Unknown options: $*"
-      exit 1
-      ;;
-    esac
-    shift
-  done
-}
-
-main() {
-  trap cleanup EXIT INT TERM
-
-  parseargs "$@"
-
-  if [ -e "${PAUSE_CRASH_SENDING}" ] && \
-     [ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then
-    lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
-    exit 1
-  fi
-
-  if is_test_image; then
-    lecho "Exiting early due to test image."
-    exit 1
-  fi
-
-  # We don't perform checks on this because we have a master lock with the
-  # CRASH_SENDER_LOCK file.  This pid file is for the system to keep track
-  # (like with autotests) that we're still running.
-  echo $$ > "${RUN_FILE}"
-
-  for dependency in "${FIND}" "${METRICS_CLIENT}" \
-                    "${RESTRICTED_CERTIFICATES_PATH}"; do
-    if [ ! -x "${dependency}" ]; then
-      lecho "Fatal: Crash sending disabled: ${dependency} not found."
-      exit 1
-    fi
-  done
-
-  TMP_DIR="$(mktemp -d /tmp/crash_sender.XXXXXX)"
-
-  # Send system-wide crashes
-  send_crashes "/var/spool/crash"
-
-  # Send user-specific crashes
-  local d
-  for d in /home/chronos/crash /home/chronos/u-*/crash; do
-    send_crashes "${d}"
-  done
-}
-
-(
-if ! flock -n 9; then
-  lecho "Already running; quitting."
-  crash_done
-  exit 1
-fi
-main "$@"
-) 9>"${CRASH_SENDER_LOCK}"
diff --git a/crash_reporter/crash_sender.conf b/crash_reporter/crash_sender.conf
new file mode 100644
index 0000000..5611c10
--- /dev/null
+++ b/crash_reporter/crash_sender.conf
@@ -0,0 +1,28 @@
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can
+# be found in the LICENSE file.
+
+# This file has the format:
+# NAME=value
+
+# Product ID in crash report.
+CHROMEOS_PRODUCT=ChromeOS
+
+# Set this to 1 to allow uploading crash reports for unofficial versions.
+FORCE_OFFICIAL=0
+
+# Maximum crashes to send per day.
+MAX_CRASH_RATE=32
+
+# Set this to 1 to pretend to have booted in developer mode. This is used by
+# autotests.
+MOCK_DEVELOPER_MODE=0
+
+# Ignore PAUSE_CRASH_SENDING file if set.
+OVERRIDE_PAUSE_SENDING=0
+
+# URL to send official build crash reports to.
+REPORT_UPLOAD_PROD_URL=https://clients2.google.com/cr/report
+
+# Maximum time to sleep between sends.
+SECONDS_SEND_SPREAD=600
diff --git a/crash_reporter/crash_sender_daemon.cc b/crash_reporter/crash_sender_daemon.cc
new file mode 100644
index 0000000..f477bdb
--- /dev/null
+++ b/crash_reporter/crash_sender_daemon.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/crash_sender_daemon.h"
+
+#include <unistd.h>
+
+#include <base/at_exit.h>
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/run_loop.h>
+#include <chromeos/syslog_logging.h>
+#include <crash-reporter/crash_sender_service.h>
+#include <dbus/bus.h>
+
+namespace {
+// Parameter to specify a custom config file.
+const char kSwitchCustomConfigFile[] = "config";
+
+const char kDefaultConfigFile[] = "/etc/crash_sender.conf";
+
+const int kTerminationSignals[] = { SIGTERM, SIGINT };
+const int kNumTerminationSignals = arraysize(kTerminationSignals);
+}  // namespace
+
+
+namespace crash_reporter {
+CrashSenderDaemon::CrashSenderDaemon(const base::FilePath& config_file)
+    : config_file_(config_file) {}
+
+CrashSenderDaemon::~CrashSenderDaemon() {
+}
+
+void CrashSenderDaemon::Run() {
+  base::RunLoop loop;
+  dbus::Bus::Options options;
+  options.bus_type = dbus::Bus::SYSTEM;
+  options.disconnected_callback = loop.QuitClosure();
+
+  scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
+  CrashSenderConfiguration config =
+      CrashSenderService::ParseConfiguration(config_file_);
+  scoped_ptr<DbusCrashSenderServiceImpl> impl(
+      new DbusCrashSenderServiceImpl(config));
+
+  CHECK(impl->Start(bus)) << "Failed to start crash sender service";
+  crash_sender_service_ = impl.Pass();
+
+  for (size_t i = 0; i < kNumTerminationSignals; ++i) {
+    async_signal_handler_.RegisterHandler(
+        kTerminationSignals[i],
+        base::Bind(&CrashSenderDaemon::Shutdown, base::Unretained(this)));
+  }
+  async_signal_handler_.RegisterHandler(
+      SIGHUP, base::Bind(&CrashSenderDaemon::Restart, base::Unretained(this)));
+  async_signal_handler_.Init();
+
+  loop.Run();
+
+  bus->ShutdownAndBlock();
+}
+
+bool CrashSenderDaemon::Shutdown(const struct signalfd_siginfo& info) {
+  loop_.PostTask(FROM_HERE, loop_.QuitClosure());
+  // Unregister the signal handler.
+  return true;
+}
+
+bool CrashSenderDaemon::Restart(const struct signalfd_siginfo& info) {
+  CrashSenderConfiguration config =
+      CrashSenderService::ParseConfiguration(config_file_);
+  crash_sender_service_->Restart(config);
+  // Keep listening to the signal.
+  return false;
+}
+
+}  // namespace crash_reporter
+
+int main(int argc, char** argv) {
+  CommandLine::Init(argc, argv);
+  CommandLine* args = CommandLine::ForCurrentProcess();
+
+  // Some libchrome calls need this.
+  base::AtExitManager at_exit_manager;
+
+  chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogToStderr);
+
+  base::FilePath config_file =
+      args->GetSwitchValuePath(kSwitchCustomConfigFile);
+  if (config_file.empty()) {
+    config_file = base::FilePath(FILE_PATH_LITERAL(kDefaultConfigFile));
+  } else {
+    LOG(INFO) << "Using crash configuration at: " << config_file.value();
+  }
+
+  crash_reporter::CrashSenderDaemon daemon(config_file);
+  daemon.Run();
+  return 0;
+}
diff --git a/crash_reporter/crash_sender_daemon.h b/crash_reporter/crash_sender_daemon.h
new file mode 100644
index 0000000..462a53d
--- /dev/null
+++ b/crash_reporter/crash_sender_daemon.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_CRASH_SENDER_DAEMON_H_
+#define CRASH_REPORTER_CRASH_SENDER_DAEMON_H_
+
+#include <base/files/file_path.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/message_loop/message_loop.h>
+#include <chromeos/asynchronous_signal_handler.h>
+
+#include "crash-reporter/crash_sender_service.h"
+
+namespace crash_reporter {
+
+class CrashSenderDaemon {
+ public:
+  // |config_file| specifies the config file for the crash sender.
+  explicit CrashSenderDaemon(const base::FilePath& config_file);
+  ~CrashSenderDaemon();
+
+  // Does all the work. Blocks until the daemon is finished.
+  void Run();
+
+ private:
+  base::MessageLoopForIO loop_;
+  base::FilePath config_file_;
+  scoped_ptr<CrashSenderService> crash_sender_service_;
+  chromeos::AsynchronousSignalHandler async_signal_handler_;
+
+  // Shutdown the sender.
+  bool Shutdown(const signalfd_siginfo& info);
+
+  // Restart the service, reading the configuration file again.
+  bool Restart(const signalfd_siginfo& info);
+
+  DISALLOW_COPY_AND_ASSIGN(CrashSenderDaemon);
+};
+
+}  // namespace crash_reporter
+
+#endif  // CRASH_REPORTER_CRASH_SENDER_DAEMON_H_
diff --git a/crash_reporter/crash_sender_service.cc b/crash_reporter/crash_sender_service.cc
new file mode 100644
index 0000000..04fb23c
--- /dev/null
+++ b/crash_reporter/crash_sender_service.cc
@@ -0,0 +1,853 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/crash_sender_service.h"
+
+#include <curl/curl.h>
+
+#include <algorithm>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+#include <dbus/bus.h>
+
+#include "vboot/crossystem.h"
+
+namespace {
+
+// Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
+const char kDefaultProduct[] = "ChromeOS";
+
+// File whose existence implies crash reports may be sent, and whose
+// contents includes our machine's anonymized guid.
+const char kConsentIdPath[] = "/home/chronos/Consent To Send Stats";
+
+// Crash sender lock in case the sender is already running.
+const char kCrashSenderLockPath[] = "/var/lock/crash_sender";
+
+// Crash sender lock in case the sender is already running for tests.
+const char kCrashSenderLockForTestsPath[] = "/var/lock/crash_sender_test";
+
+// Path to file that indicates a crash test is currently running.
+const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
+
+// Path to hardware class description.
+const char kHWClassPath[] = "/sys/devices/platform/chromeos_acpi/HWID";
+
+// Path to file that indicates this is a developer image.
+const char kLeaveCorePath[] = "/root/.leave_core";
+
+// File whose existence causes crash sending to be delayed (for testing).
+// Must be stateful to enable testing kernel crashes.
+const char kPauseCrashSendingPath[] = "/var/lib/crash_sender_paused";
+
+// Path to a directory of restricted certificates which includes
+// a certificate for ${REPORT_UPLOAD_PROD_URL}.
+const char kRestrictedCertificatesPath[] =
+    "/usr/share/chromeos-ca-certificates";
+
+// File whose existence implies we're running and not to start again.
+const char kRunFilePath[] = "/var/run/crash_sender.pid";
+
+// Directory to store timestamp files indicating the uploads in the past 24
+// hours.
+const char kTimestampsDirPath[] = "/var/lib/crash_sender";
+
+// Chrome's crash report log file.
+const char kChromeCrashLogPath[] = "/var/log/chrome/Crash Reports/uploads.log";
+
+// File whose existence mocks crash sending. If empty we pretend the crash
+// sending was successful, otherwise unsuccessful.
+const char kMockCrashSendingPath[] = "/tmp/mock-crash-sending";
+
+// Configuration keys.
+const char kForceOfficial[] = "FORCE_OFFICIAL";
+const char kMaxCrashRate[] = "MAX_CRASH_RATE";
+const char kMockDeveloperMode[] = "MOCK_DEVELOPER_MODE";
+const char kOverridePauseSending[] = "OVERRIDE_PAUSE_SENDING";
+const char kReportUploadProdUrl[] = "REPORT_UPLOAD_PROD_URL";
+const char kSecondsSendSpread[] = "SECONDS_SEND_SPREAD";
+
+// Owns a curl handle.
+class ScopedCurl {
+ public:
+  explicit ScopedCurl(bool mock) : mock_(mock) {
+    if (!mock)
+      curl_ = curl_easy_init();
+  }
+
+  ~ScopedCurl() {
+    if (mock_)
+      return;
+
+    if (post_)
+      curl_formfree(post_);
+
+    curl_easy_cleanup(curl_);
+  }
+
+  CURL* curl() const { return curl_; }
+
+  void AddMultipartContent(std::string key, std::string value) {
+    LOG(INFO) << key << ": " << value;
+    if (mock_)
+      return;
+    curl_formadd(&post_, &last_,
+                 CURLFORM_COPYNAME, key.c_str(),
+                 CURLFORM_COPYCONTENTS, value.c_str(),
+                 CURLFORM_END);
+  }
+
+  void AddFile(std::string key, base::FilePath file) {
+    LOG(INFO) << key << ": " << file.value();
+    if (mock_)
+      return;
+    curl_formadd(&post_, &last_,
+                 CURLFORM_COPYNAME, key.c_str(),
+                 CURLFORM_FILE, file.value().c_str(),
+                 CURLFORM_END);
+  }
+
+  CURLcode perform() {
+    CHECK(!mock_);
+    curl_easy_setopt(curl_, CURLOPT_HTTPPOST, post_);
+    return curl_easy_perform(curl_);
+  }
+
+ private:
+  bool mock_;
+  CURL* curl_ = nullptr;
+  struct curl_httppost* post_ = nullptr;
+  struct curl_httppost* last_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedCurl);
+};
+
+// Comparison function.
+bool order_meta_files(const crash_reporter::MetaFile& f1,
+                      const crash_reporter::MetaFile& f2) {
+  return std::make_tuple(f1.modification_time, f1.path.value()) <
+         std::make_tuple(f2.modification_time, f2.path.value());
+}
+
+// Return the list of directories containing crashes.
+std::vector<base::FilePath> GetCrashDirectories() {
+  std::vector<base::FilePath> result;
+  base::FilePath system_wide("/var/spool/crash");
+  if (base::DirectoryExists(system_wide))
+    result.push_back(system_wide);
+
+  base::FilePath main_user("/home/chronos/crash");
+  if (base::DirectoryExists(main_user))
+    result.push_back(main_user);
+
+  base::FileEnumerator enumCrashDirectories(
+      base::FilePath("/home/chronos"), false, base::FileEnumerator::DIRECTORIES,
+      FILE_PATH_LITERAL("u-*"));
+  for (base::FilePath dir = enumCrashDirectories.Next(); !dir.empty();
+       dir = enumCrashDirectories.Next()) {
+    base::FilePath crash_dir = dir.Append("crash");
+    if (base::DirectoryExists(crash_dir))
+      result.push_back(crash_dir);
+  }
+  return result;
+}
+
+// Returns all the files in a given directory with the time of last
+// modification.
+std::vector<std::pair<base::Time, base::FilePath>> GetOrderedFiles(
+    const base::FilePath& dir) {
+  std::vector<std::pair<base::Time, base::FilePath>> files;
+  base::FileEnumerator enumFiles(dir, false, base::FileEnumerator::FILES);
+  for (base::FilePath file = enumFiles.Next(); !file.empty();
+       file = enumFiles.Next()) {
+    files.push_back(
+        std::make_pair(enumFiles.GetInfo().GetLastModifiedTime(), file));
+  }
+  return files;
+}
+
+// Parse a file containing key value of the form:
+// KEY=VALUE
+std::map<std::string, std::string> ParseKeyValueFile(
+    const base::FilePath& file) {
+  std::map<std::string, std::string> result;
+  std::string content;
+  if (!base::ReadFileToString(file, &content)) {
+    LOG(WARNING) << "Unable to read key values from: " << file.value();
+    return result;
+  }
+  base::StringPairs pairs;
+  base::SplitStringIntoKeyValuePairs(content, '=', '\n', &pairs);
+  for (base::StringPairs::const_iterator pair = pairs.begin();
+       pair != pairs.end(); ++pair) {
+    if (!pair->first.empty() && pair->first[0] != '#')
+      result[pair->first] = pair->second;
+  }
+
+  return result;
+}
+
+// Returns the value associated to |key| in |map|, or |default_value| if |map|
+// doesn't contain |key|.
+std::string GetValue(const std::map<std::string, std::string>& map,
+                     const std::string& key, const std::string& default_value) {
+  std::map<std::string, std::string>::const_iterator it = map.find(key);
+  if (it != map.end())
+    return it->second;
+
+  return default_value;
+}
+
+// As |GetValue| for values of type |base::FilePath|.
+base::FilePath GetPathValue(const std::map<std::string, std::string>& map,
+                            const std::string& key,
+                            const base::FilePath& default_value) {
+  std::map<std::string, std::string>::const_iterator it = map.find(key);
+  if (it != map.end())
+    return base::FilePath(it->second);
+
+  return default_value;
+}
+
+// As |GetValue| for values of type |int|.
+int GetIntValue(const std::map<std::string, std::string>& map,
+                const std::string& key, int default_value) {
+  std::map<std::string, std::string>::const_iterator it = map.find(key);
+  if (it != map.end()) {
+    int result;
+    if (base::StringToInt(it->second, &result)) {
+      return result;
+    }
+  }
+  return default_value;
+}
+
+// Remove the report for the given |meta_file|.
+void RemoveReport(const base::FilePath& meta_file) {
+  LOG(INFO) << "Removing report: " << meta_file.value();
+  base::FilePath directory = meta_file.DirName();
+  base::FilePath template_value = meta_file.ReplaceExtension(".*").BaseName();
+
+  base::FileEnumerator filesToDelete(
+      directory, false, base::FileEnumerator::FILES, template_value.value());
+  for (base::FilePath file = filesToDelete.Next(); !file.empty();
+       file = filesToDelete.Next()) {
+    if (!base::DeleteFile(file, false))
+      LOG(WARNING) << "Unable to delete " << file.value();
+  }
+}
+
+// Returns the extenstion of the given file, stripping .gz if the file is
+// compressed.
+std::string GetExtension(const base::FilePath& file) {
+  std::string extension = file.FinalExtension();
+  if (extension == ".gz")
+    extension = file.RemoveFinalExtension().FinalExtension();
+
+  if (!extension.empty()) {
+    DCHECK_EQ(extension[0], '.');
+    extension = extension.substr(1);
+  }
+  return extension;
+}
+
+// Returns the report kind.
+std::string GetKind(const crash_reporter::MetaFile& meta_file) {
+  base::FilePath payload =
+      GetPathValue(meta_file.meta_information, "payload", base::FilePath());
+  if (payload.value().empty() || !base::PathExists(payload)) {
+    LOG(WARNING) << "Missing payload on file: " << meta_file.path.value();
+    return "";
+  }
+  std::string kind = GetExtension(payload);
+  if (kind == "dmp") {
+    return "minidump";
+  }
+  return kind;
+}
+
+// Callback function for curl. It delegates to a callback passed as additional
+// data.
+size_t CurlWriteData(void* buffer, size_t size, size_t nmemb, void* data) {
+  base::Callback<size_t(void*, size_t)>* callback =
+      static_cast<base::Callback<size_t(void*, size_t)>*>(data);
+  return callback->Run(buffer, size * nmemb);
+}
+
+size_t AppendDataToString(std::string* data, const void* buffer, size_t size) {
+  data->append(reinterpret_cast<const char*>(buffer), size);
+  return size;
+}
+
+
+}  // namespace
+
+namespace crash_reporter {
+
+CrashSenderService::CrashSenderService(const CrashSenderConfiguration& config)
+    : config_(config) {
+  metrics_lib_.Init();
+}
+
+CrashSenderService::~CrashSenderService() {}
+
+bool CrashSenderService::Start(ProxyResolver* proxy_resolver) {
+  proxy_resolver_ = proxy_resolver;
+  std::map<std::string, std::string> lsb_release_values =
+      ParseKeyValueFile(base::FilePath("/etc/lsb-release"));
+
+  board_ = lsb_release_values["CHROMEOS_RELEASE_BOARD"];
+  if (board_.empty()) {
+    LOG(ERROR) << "Unable to retrieve board information.";
+    return false;
+  }
+
+  channel_ = lsb_release_values["CHROMEOS_RELEASE_TRACK"];
+  const char kChannelSuffix[] = "-channel";
+  if (EndsWith(channel_, kChannelSuffix, true))
+    channel_ =
+        channel_.substr(0, channel_.size() - arraysize(kChannelSuffix) + 1);
+
+  if (channel_.empty()) {
+    LOG(ERROR) << "Unable to retrieve channel information.";
+    return false;
+  }
+
+  official_ =
+      (lsb_release_values["CHROMEOS_RELEASE_DESCRIPTION"].find("Official") !=
+       std::string::npos);
+
+  std::map<std::string, std::string> os_release_values =
+      ParseKeyValueFile(base::FilePath("/etc/os-release"));
+
+  default_product_ = GetValue(os_release_values, "GOOGLE_CRASH_ID", "");
+  if (default_product_.empty())
+    default_product_ = GetValue(os_release_values, "ID", "");
+
+  default_version_ = GetValue(os_release_values, "GOOGLE_CRASH_VERSION_ID", "");
+  if (default_version_.empty())
+    default_version_ = GetValue(os_release_values, "VERSION_ID", "");
+
+  return ReapplyConfig(config_);
+}
+
+void CrashSenderService::Restart(const CrashSenderConfiguration& config) {
+  CrashSenderConfiguration old_config = config_;
+  config_ = config;
+  if (!ReapplyConfig(config_)) {
+    LOG(ERROR) << "Restarting failed. Reapplying old configuration.";
+    config_ = old_config;
+    CHECK(ReapplyConfig(config_));
+  }
+}
+
+CrashSenderConfiguration CrashSenderService::ParseConfiguration(
+    const base::FilePath& config_file) {
+  CrashSenderConfiguration result;
+  std::map<std::string, std::string> key_values =
+      ParseKeyValueFile(config_file);
+
+  result.force_official = GetIntValue(key_values, kForceOfficial, false);
+  result.max_crash_rate = GetIntValue(key_values, kMaxCrashRate, 32);
+  result.mock_developer_mode =
+      GetIntValue(key_values, kMockDeveloperMode, false);
+  result.override_pause_sending =
+      GetIntValue(key_values, kOverridePauseSending, false);
+  result.report_upload_prod_url =
+      GetValue(key_values, kReportUploadProdUrl,
+               "https://clients2.google.com/cr/report");
+  result.seconds_send_spread = GetIntValue(key_values, kSecondsSendSpread, 600);
+
+  return result;
+}
+
+bool CrashSenderService::ReapplyConfig(const CrashSenderConfiguration& config) {
+  bool test_run = IsMock();
+  const char* lock_path =
+      test_run ? kCrashSenderLockForTestsPath : kCrashSenderLockPath;
+  lock_file_.reset(
+      new base::File(base::FilePath(lock_path), base::File::FLAG_OPEN_ALWAYS |
+                                                    base::File::FLAG_READ |
+                                                    base::File::FLAG_WRITE));
+  if (lock_file_->Lock() != base::File::FILE_OK) {
+    LOG(ERROR) << "Already running; quitting.";
+    return false;
+  }
+  base::FilePath run_file(kRunFilePath);
+  run_file_deleter_.Reset(
+      base::Bind(base::IgnoreResult(&base::DeleteFile), run_file, false));
+  std::string pid = base::IntToString(getpid()) + "\n";
+  base::WriteFile(run_file, pid.data(), pid.length());
+
+  CollectAllCrashes();
+  if (test_run) {
+    base::MessageLoop::current()->PostTask(FROM_HERE,
+                                           base::MessageLoop::QuitClosure());
+    LOG(INFO) << "crash_sender done.";
+  }
+  return true;
+}
+
+bool CrashSenderService::IsCrashTestInProgress() const {
+  return base::PathExists(base::FilePath(kCrashTestInProgressPath));
+}
+
+bool CrashSenderService::IsTestImage() const {
+  if (IsCrashTestInProgress())
+    return false;
+
+  return StartsWithASCII(channel_, "test", true);
+}
+
+bool CrashSenderService::IsMock() const {
+  return base::PathExists(base::FilePath(kMockCrashSendingPath));
+}
+
+bool CrashSenderService::IsMockSuccessful() const {
+  std::string content;
+  if (base::ReadFileToString(base::FilePath(kMockCrashSendingPath), &content))
+    return content.empty();
+
+  return false;
+}
+
+bool CrashSenderService::IsOfficialImage() const {
+  return config_.force_official || official_;
+}
+
+bool CrashSenderService::IsDeveloperMode() const {
+  if (config_.mock_developer_mode)
+    return true;
+
+  if (IsCrashTestInProgress())
+    return false;
+
+  return VbGetSystemPropertyInt("devsw_boot");
+}
+
+bool CrashSenderService::IsDeveloperImage() const {
+  // Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
+  if (IsCrashTestInProgress())
+    return false;
+
+  return base::PathExists(base::FilePath(kLeaveCorePath));
+}
+
+std::string CrashSenderService::GetHardwareClass() const {
+  std::string content;
+  if (base::ReadFileToString(base::FilePath(kHWClassPath), &content))
+    return content;
+
+  char buffer[VB_MAX_STRING_PROPERTY];
+  const char* hwid =
+      VbGetSystemPropertyString("hwid", buffer, VB_MAX_STRING_PROPERTY);
+  if (hwid)
+    return hwid;
+
+  return "undefined";
+}
+
+std::string CrashSenderService::GetConsentId() const {
+  std::string content;
+  if (base::ReadFileToString(base::FilePath(kConsentIdPath), &content)) {
+    content.erase(std::remove(content.begin(), content.end(), '-'),
+                  content.end());
+    return content;
+  }
+
+  return "undefined";
+}
+
+void CrashSenderService::CollectCrashes(const base::FilePath& dir) {
+  std::vector<std::pair<base::Time, base::FilePath>> files =
+      GetOrderedFiles(dir);
+  base::Time now = base::Time::Now();
+  for (const std::pair<base::Time, base::FilePath>& file : files) {
+    if (file.second.FinalExtension() == ".meta") {
+      MetaFile info;
+      info.modification_time = file.first;
+      info.path = file.second;
+      info.meta_information = ParseKeyValueFile(info.path);
+      switch (FilterCrashes(info)) {
+        case CAN_UPLOAD:
+          current_crashes_.push_back(info);
+          break;
+        case DELETE:
+          RemoveReport(info.path);
+          break;
+        case WAIT:
+          // Nothing
+          break;
+      }
+    } else if ((now - file.first >= base::TimeDelta::FromDays(1)) &&
+               !base::PathExists(file.second.ReplaceExtension(".meta"))) {
+      if (base::DeleteFile(file.second, false)) {
+        LOG(INFO) << "Removing old orphaned file: " << file.second.value();
+      } else {
+        LOG(WARNING) << "Unable to delete: " << file.second.value();
+      }
+    }
+  }
+}
+
+void CrashSenderService::CollectAllCrashes() {
+  current_crashes_.clear();
+
+  std::vector<base::FilePath> crash_directories = GetCrashDirectories();
+  for (const base::FilePath& path : crash_directories) {
+    CrashSenderService::CollectCrashes(path);
+  }
+  std::sort(current_crashes_.begin(), current_crashes_.end(),
+            &order_meta_files);
+
+  if (current_crashes_.empty()) {
+    // If no crash is present, wait for an hour.
+    ScheduleNext();
+    return;
+  }
+
+  PrepareToSendNextCrash();
+}
+
+CrashSenderService::FileStatus CrashSenderService::FilterCrashes(
+    const MetaFile& file) {
+  if (!metrics_lib_.AreMetricsEnabled()) {
+    LOG(INFO) << "Crash reporting is disabled.  Removing crash.";
+    return DELETE;
+  }
+
+  if (!IsMock() && !IsOfficialImage()) {
+    LOG(INFO) << "Not an official OS version.  Removing crash.";
+    return DELETE;
+  }
+
+  if (GetValue(file.meta_information, "done", "") != "1") {
+    // This report is incomplete, so if it's old, just remove it
+    if (base::Time::Now() - file.modification_time >=
+        base::TimeDelta::FromDays(1)) {
+      LOG(INFO) << "Removing old incomplete metadata.";
+      return DELETE;
+    } else {
+      LOG(INFO) << "Ignoring recent incomplete metadata.";
+      return WAIT;
+    }
+  }
+
+  std::string report_kind = GetKind(file);
+  if (report_kind != "minidump" && report_kind != "kcrash" &&
+      report_kind != "log") {
+    LOG(INFO) << "Unknown report kind " << report_kind << ".  Removing report.";
+    return DELETE;
+  }
+
+  return CAN_UPLOAD;
+}
+
+bool CrashSenderService::MustThrottle() const {
+  base::FilePath timestamps_dir(kTimestampsDirPath);
+  if (!base::CreateDirectoryAndGetError(timestamps_dir, nullptr)) {
+    LOG(WARNING) << "Unable to create directory: " << timestamps_dir.value();
+    return true;
+  }
+
+  base::Time now = base::Time::Now();
+  base::FileEnumerator timestamps(timestamps_dir, false,
+                                  base::FileEnumerator::FILES);
+  int sends_in_24hrs = 0;
+  for (base::FilePath file = timestamps.Next(); !file.empty();
+       file = timestamps.Next()) {
+    if (now - timestamps.GetInfo().GetLastModifiedTime() >=
+        base::TimeDelta::FromDays(1)) {
+      base::DeleteFile(file, false);
+    } else {
+      ++sends_in_24hrs;
+    }
+  }
+  LOG(INFO) << "Current send rate: " << sends_in_24hrs << "sends/24hrs";
+  if (sends_in_24hrs >= config_.max_crash_rate) {
+    LOG(INFO) << "Cannot send more crashes: current " << sends_in_24hrs
+              << "send/24hrs >= max " << config_.max_crash_rate << "send/24hrs";
+    return true;
+  }
+  base::FilePath tmp_file;
+  if (!base::CreateTemporaryFileInDir(timestamps_dir, &tmp_file)) {
+    LOG(WARNING) << "Unable to create a file in " << timestamps_dir.value();
+    return true;
+  }
+  return false;
+}
+
+void CrashSenderService::PrepareToSendNextCrash() {
+  // If we cannot send any crashes, wait one hour.
+  if (!CanSendNextCrash()) {
+    ScheduleNext();
+    return;
+  }
+
+  // If there is no crash to send, collect crashes and return.
+  if (current_crashes_.empty()) {
+    CollectAllCrashes();
+    return;
+  }
+
+  const MetaFile& file = current_crashes_.front();
+  base::TimeDelta time_to_wait =
+      std::max(file.modification_time + base::TimeDelta::FromSeconds(30) -
+                   base::Time::Now(),
+               base::TimeDelta::FromSeconds(
+                   base::RandInt(1, config_.seconds_send_spread)));
+  LOG(INFO) << "Scheduled to send " << file.path.value() << " in "
+            << time_to_wait.InSeconds() << "s.";
+
+  if (IsMock()) {
+    SendNextCrash();
+  } else {
+    timer_.Start(FROM_HERE, time_to_wait, this,
+                 &CrashSenderService::SendNextCrash);
+  }
+}
+
+bool CrashSenderService::CanSendNextCrash() {
+  // Handle pause crash sending
+  base::FilePath pause_crash_sending(kPauseCrashSendingPath);
+  if (base::PathExists(pause_crash_sending) &&
+      !config_.override_pause_sending) {
+    LOG(INFO) << "Not sending crashes due to " << pause_crash_sending.value();
+    return false;
+  }
+
+  // Handle is test image
+  if (IsTestImage()) {
+    LOG(INFO) << "Not sending crashes due to test image.";
+    return false;
+  }
+
+  // Handle certificate path
+  base::FilePath restricted_certificates_path(kRestrictedCertificatesPath);
+  if (!base::DirectoryExists(restricted_certificates_path)) {
+    LOG(INFO) << "Not sending crashes due to "
+              << restricted_certificates_path.value() << " missing.";
+    return false;
+  }
+
+  // Guest mode.
+  if (metrics_lib_.IsGuestMode()) {
+    LOG(INFO)
+        << "Guest mode has been entered. Delaying crash sending until exited.";
+    return false;
+  }
+
+  return true;
+}
+
+void CrashSenderService::SendNextCrash() {
+  // Ensure the timer will be called again if this is exited early due to
+  // exceptional conditions.
+  ScheduleNext();
+
+  if (!CanSendNextCrash())
+    return;
+
+  // Check uploads rate
+  if (MustThrottle()) {
+    LOG(INFO) << "Sending a report would exceed rate. Leaving for later.";
+    return;
+  }
+
+  const MetaFile file = current_crashes_[0];
+  current_crashes_.erase(current_crashes_.begin());
+
+  // Trying to send a crash. Preparing next crash.
+  base::ScopedClosureRunner send_next_crash(base::Bind(
+      &CrashSenderService::PrepareToSendNextCrash, base::Unretained(this)));
+  // Delete the report whatever the result.
+  base::ScopedClosureRunner report_delete(base::Bind(&RemoveReport, file.path));
+
+  ScopedCurl curl(IsMock());
+
+  LOG(INFO) << "Sending crash:";
+  std::string product = GetValue(file.meta_information, "upload_var_prod", "");
+  if (product.empty())
+    product = default_product_;
+
+  if (product.empty())
+    product = kDefaultProduct;
+
+  curl.AddMultipartContent("prod", product);
+  if (product != kDefaultProduct)
+    LOG(INFO) << "Sending crash report on behalf of " << product;
+
+  LOG(INFO) << "Metadata: " << file.path.value() << " (" << GetKind(file)
+            << ")";
+
+  std::string version = GetValue(file.meta_information, "upload_var_ver", "");
+  if (version.empty())
+    version = default_version_;
+  if (version.empty())
+    version = GetValue(file.meta_information, "ver", "");
+
+  curl.AddMultipartContent("ver", version);
+  curl.AddMultipartContent("board", board_);
+  curl.AddMultipartContent("hwclass", GetHardwareClass());
+  curl.AddMultipartContent(
+      "exec_name", GetValue(file.meta_information, "exec_name", "undefined"));
+
+  std::string image_type;
+  if (IsTestImage()) {
+    image_type = "test";
+  } else if (IsDeveloperImage()) {
+    image_type = "dev";
+  } else if (config_.force_official) {
+    image_type = "force-official";
+  } else if (IsMock() && !IsMockSuccessful()) {
+    image_type = "mock-fail";
+  }
+  if (!image_type.empty())
+    curl.AddMultipartContent("image_type", image_type);
+
+  if (VbGetSystemPropertyInt("cros_debug") && IsDeveloperMode())
+    curl.AddMultipartContent("boot_mode", "dev");
+
+  std::string error_type = GetValue(file.meta_information, "error_type", "");
+  if (!error_type.empty())
+    curl.AddMultipartContent("error_type", error_type);
+
+  curl.AddMultipartContent("guid", GetConsentId());
+  curl.AddMultipartContent(
+      "write_payload_size",
+      GetValue(file.meta_information, "payload_size", "undefined"));
+
+  base::FilePath payload =
+      GetPathValue(file.meta_information, "payload", base::FilePath());
+  if (!payload.value().empty()) {
+    int64 file_size;
+    if (base::GetFileSize(payload, &file_size)) {
+      curl.AddMultipartContent("send_payload_size",
+                               base::Int64ToString(file_size));
+      curl.AddFile("upload_file_" + GetKind(file), payload);
+    }
+  }
+
+  std::string signature = GetValue(file.meta_information, "sig", "");
+  if (!signature.empty()) {
+    curl.AddMultipartContent("sig", signature);
+    curl.AddMultipartContent("sig2", signature);
+  }
+
+  base::FilePath log =
+      GetPathValue(file.meta_information, "log", base::FilePath());
+  if (base::PathExists(log))
+    curl.AddFile("log", log);
+
+  std::string upload_prefix =
+      GetValue(file.meta_information, "upload_prefix", "");
+
+  const char kUploadVarPrefix[] = "upload_var_";
+  const char kUploadFilePrefix[] = "upload_file_";
+  for (const auto& pair : file.meta_information) {
+    if (StartsWithASCII(pair.first, kUploadVarPrefix, true)) {
+      curl.AddMultipartContent(
+          upload_prefix + pair.first.substr(arraysize(kUploadVarPrefix) - 1),
+          pair.second);
+    }
+    if (StartsWithASCII(pair.first, kUploadFilePrefix, true)) {
+      curl.AddFile(
+          upload_prefix + pair.first.substr(arraysize(kUploadFilePrefix) - 1),
+          base::FilePath(pair.second));
+    }
+  }
+
+  if (IsMock()) {
+    if (IsMockSuccessful()) {
+      LOG(INFO) << "Mocking successful send";
+    } else {
+      LOG(INFO) << "Mocking unsuccessful send";
+    }
+    return;
+  }
+
+  curl_easy_setopt(curl.curl(), CURLOPT_URL,
+                   config_.report_upload_prod_url.c_str());
+  curl_easy_setopt(curl.curl(), CURLOPT_POST, 1L);
+  std::vector<std::string> proxies = proxy_resolver_->GetProxiesForUrl(
+      config_.report_upload_prod_url, base::TimeDelta::FromSeconds(5));
+  if (proxies.size() && proxies[0] != "direct://")
+    curl_easy_setopt(curl.curl(), CURLOPT_PROXY, proxies[0].c_str());
+
+  // TODO(qsr) Remove
+  curl_easy_setopt(curl.curl(), CURLOPT_PROXY, "http://192.168.45.1:8888");
+
+  std::string received_data = "";
+  base::Callback<size_t(const void*, size_t)> callback =
+      base::Bind(&AppendDataToString, &received_data);
+  curl_easy_setopt(curl.curl(), CURLOPT_WRITEFUNCTION, &CurlWriteData);
+  curl_easy_setopt(curl.curl(), CURLOPT_WRITEDATA, &callback);
+
+  CURLcode success = curl.perform();
+
+  if (success != 0 || received_data.size() > 20) {
+    LOG(ERROR) << "Unable to upload crash report. Error code: " << success;
+    return;
+  }
+
+  std::string product_name;
+  if (product == "Chrome_ChromeOS") {
+    if (IsOfficialImage()) {
+      product_name = "Chrome";
+    } else {
+      product_name = "Chromium";
+    }
+  } else {
+    if (IsOfficialImage()) {
+      product_name = "ChromeOS";
+    } else {
+      product_name = "ChromiumOS";
+    }
+  }
+  std::string log_string = base::StringPrintf(
+      "%" PRIu64 ",%s,%s\n", static_cast<uint64_t>(base::Time::Now().ToTimeT()),
+      received_data.c_str(), product_name.c_str());
+  if (base::AppendToFile(base::FilePath(kChromeCrashLogPath), log_string.data(),
+                         log_string.size()) == -1) {
+    LOG(ERROR) << "Unable to update crash log.";
+  }
+}
+
+void CrashSenderService::ScheduleNext() {
+  timer_.Start(FROM_HERE, base::TimeDelta::FromHours(1), this,
+               &CrashSenderService::PrepareToSendNextCrash);
+}
+
+DbusCrashSenderServiceImpl::DbusCrashSenderServiceImpl(
+    const CrashSenderConfiguration& config)
+    : CrashSenderService(config) {}
+
+DbusCrashSenderServiceImpl::~DbusCrashSenderServiceImpl() {}
+
+bool DbusCrashSenderServiceImpl::Start(dbus::Bus* bus) {
+  if (!bus || !bus->Connect()) {
+    LOG(ERROR) << "Failed to connect to DBus";
+    return false;
+  }
+
+  bus_ = bus;
+  proxy_resolver_.reset(new DBusProxyResolver(bus_));
+  proxy_resolver_->Init();
+  return CrashSenderService::Start(proxy_resolver_.get());
+}
+
+}  // namespace crash_reporter
diff --git a/crash_reporter/crash_sender_service.h b/crash_reporter/crash_sender_service.h
new file mode 100644
index 0000000..bc2b35c
--- /dev/null
+++ b/crash_reporter/crash_sender_service.h
@@ -0,0 +1,114 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_CRASH_SENDER_SERVICE_H_
+#define CRASH_REPORTER_CRASH_SENDER_SERVICE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/callback_helpers.h>
+#include <base/files/file.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/timer/timer.h>
+
+#include "crash-reporter/proxy_resolver.h"
+#include "metrics/metrics_library.h"
+
+namespace dbus {
+class Bus;
+}  // namespace dbus
+
+namespace crash_reporter {
+
+// The configuration for the crash sender. See |crash_sender.conf| for details.
+struct CrashSenderConfiguration {
+  bool force_official;
+  int max_crash_rate;
+  bool mock_developer_mode;
+  bool override_pause_sending;
+  std::string report_upload_prod_url;
+  int seconds_send_spread;
+};
+
+// The information about a crash report, which is obtained from the associated
+// meta file.
+struct MetaFile {
+  base::Time modification_time;
+  base::FilePath path;
+  std::map<std::string, std::string> meta_information;
+};
+
+class CrashSenderService {
+ public:
+  explicit CrashSenderService(const CrashSenderConfiguration& config);
+  virtual ~CrashSenderService();
+
+  bool Start(ProxyResolver* proxy_resolver);
+
+  void Restart(const CrashSenderConfiguration& config);
+
+  static CrashSenderConfiguration ParseConfiguration(
+      const base::FilePath& config_file);
+
+ private:
+  enum FileStatus {
+    CAN_UPLOAD,
+    WAIT,
+    DELETE,
+  };
+
+  bool ReapplyConfig(const CrashSenderConfiguration& config);
+  bool IsCrashTestInProgress() const;
+  bool IsTestImage() const;
+  bool IsMock() const;
+  bool IsMockSuccessful() const;
+  bool IsOfficialImage() const;
+  bool IsDeveloperMode() const;
+  bool IsDeveloperImage() const;
+  std::string GetHardwareClass() const;
+  std::string GetConsentId() const;
+  void CollectCrashes(const base::FilePath& dir);
+  void CollectAllCrashes();
+  FileStatus FilterCrashes(const MetaFile& file);
+  bool MustThrottle() const;
+  void PrepareToSendNextCrash();
+  bool CanSendNextCrash();
+  void SendNextCrash();
+  void ScheduleNext();
+
+  ProxyResolver* proxy_resolver_ = nullptr;
+  CrashSenderConfiguration config_;
+  MetricsLibrary metrics_lib_;
+  base::OneShotTimer<CrashSenderService> timer_;
+  base::ScopedClosureRunner run_file_deleter_;
+  scoped_ptr<base::File> lock_file_;
+  std::string channel_;
+  std::string board_;
+  std::string default_product_;
+  std::string default_version_;
+  bool official_ = false;
+  std::vector<MetaFile> current_crashes_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrashSenderService);
+};
+
+class DbusCrashSenderServiceImpl : public CrashSenderService {
+ public:
+  explicit DbusCrashSenderServiceImpl(const CrashSenderConfiguration& config);
+  virtual ~DbusCrashSenderServiceImpl();
+
+  bool Start(dbus::Bus* bus);
+
+ private:
+  dbus::Bus* bus_ = nullptr;
+  scoped_ptr<DBusProxyResolver> proxy_resolver_;
+
+  DISALLOW_COPY_AND_ASSIGN(DbusCrashSenderServiceImpl);
+};
+}  // namespace crash_reporter
+
+#endif  // CRASH_REPORTER_CRASH_SENDER_SERVICE_H_
diff --git a/crash_reporter/init/crash-sender.conf b/crash_reporter/init/crash-sender.conf
index 892186f..11b7e4f 100644
--- a/crash_reporter/init/crash-sender.conf
+++ b/crash_reporter/init/crash-sender.conf
@@ -1,11 +1,12 @@
-# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-description     "Run the crash sender periodically"
-author          "chromium-os-dev@chromium.org"
+description "Runs a daemon which send collected crash reports."
+author      "chromium-os-dev@chromium.org"
 
-start on starting system-services
+start on started system-services
 stop on stopping system-services
+respawn
 
-exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender
+exec crash_sender
diff --git a/crash_reporter/libproxies.cc b/crash_reporter/libproxies.cc
new file mode 100644
index 0000000..cb77295
--- /dev/null
+++ b/crash_reporter/libproxies.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/libproxies.h"
+
+#include <algorithm>
+
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <chromeos/strings/string_utils.h>
+
+namespace crash_reporter {
+
+const char kLibCrosProxyResolveSignalInterface[] =
+    "org.chromium.CrashReporterLibcrosProxyResolvedInterface";
+const char kLibCrosProxyResolveName[] = "ProxyResolved";
+const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface";
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kLibCrosServicePath[] = "/org/chromium/LibCrosService";
+const char kLibCrosServiceResolveNetworkProxyMethodName[] =
+    "ResolveNetworkProxy";
+const char kNoProxy[] = "direct://";
+
+std::vector<std::string> ParseProxyString(const std::string& input) {
+  std::vector<std::string> ret;
+  // Some of this code taken from
+  // https://chromium.googlesource.com/chromium/chromium/+/master/net/proxy
+  for (const std::string& token : chromeos::string_utils::Split(input, ';')) {
+    auto space =
+        std::find_if(token.begin(), token.end(), IsAsciiWhitespace<char>);
+    std::string scheme(token.begin(), space);
+    base::StringToLowerASCII(&scheme);
+    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+    if (scheme == "socks") {
+      scheme += "4";
+    } else if (scheme == "proxy") {
+      scheme = "http";
+    } else if (scheme != "https" && scheme != "socks4" && scheme != "socks5" &&
+               scheme != "direct") {
+      continue;  // Invalid proxy scheme
+    }
+
+    std::string host_and_port = std::string(space, token.end());
+    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+    if (scheme != "direct" && host_and_port.empty())
+      continue;  // Must supply host/port when non-direct proxy used.
+
+    ret.push_back(scheme + "://" + host_and_port);
+  }
+  if (ret.empty() || ret.back() != kNoProxy)
+    ret.push_back(kNoProxy);
+
+  return ret;
+}
+
+}  // namespace crash_reporter
diff --git a/crash_reporter/libproxies.h b/crash_reporter/libproxies.h
new file mode 100644
index 0000000..ed32f34
--- /dev/null
+++ b/crash_reporter/libproxies.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_LIBPROXIES_H_
+#define CRASH_REPORTER_LIBPROXIES_H_
+
+#include <string>
+#include <vector>
+
+namespace crash_reporter {
+
+extern const char kLibCrosProxyResolveSignalInterface[];
+extern const char kLibCrosProxyResolveName[];
+extern const char kLibCrosServiceInterface[];
+extern const char kLibCrosServiceName[];
+extern const char kLibCrosServicePath[];
+extern const char kLibCrosServiceResolveNetworkProxyMethodName[];
+extern const char kNoProxy[];
+
+// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
+// Parses the browser's answer for resolved proxies.  It returns a
+// list of strings, each of which is a resolved proxy.
+std::vector<std::string> ParseProxyString(const std::string& input);
+
+}  // namespace crash_reporter
+
+#endif  // CRASH_REPORTER_LIBPROXIES_H_
diff --git a/crash_reporter/list_proxies.cc b/crash_reporter/list_proxies.cc
deleted file mode 100644
index 282c6ae..0000000
--- a/crash_reporter/list_proxies.cc
+++ /dev/null
@@ -1,257 +0,0 @@
-// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <dbus/dbus-glib-lowlevel.h>
-#include <glib.h>
-#include <unistd.h>  // for isatty()
-
-#include <deque>
-#include <string>
-
-#include <base/command_line.h>
-#include <base/files/file_util.h>
-#include <base/strings/string_number_conversions.h>
-#include <base/strings/string_tokenizer.h>
-#include <base/strings/string_util.h>
-#include <base/values.h>
-#include <chromeos/dbus/dbus.h>
-#include <chromeos/syslog_logging.h>
-
-const char kLibCrosProxyResolveSignalInterface[] =
-    "org.chromium.CrashReporterLibcrosProxyResolvedInterface";
-const char kLibCrosProxyResolveName[] = "ProxyResolved";
-const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface";
-const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
-const char kLibCrosServicePath[] = "/org/chromium/LibCrosService";
-const char kLibCrosServiceResolveNetworkProxyMethodName[] =
-    "ResolveNetworkProxy";
-const char kNoProxy[] = "direct://";
-
-namespace switches {
-
-const unsigned kTimeoutDefault = 5;
-
-const char kHelp[] = "help";
-const char kQuiet[] = "quiet";
-const char kTimeout[] = "timeout";
-const char kVerbose[] = "verbose";
-// Help message to show when the --help command line switch is specified.
-const char kHelpMessage[] =
-    "Chromium OS Crash helper: proxy lister\n"
-    "\n"
-    "Available Switches:\n"
-    "  --quiet      Only print the proxies\n"
-    "  --verbose    Print additional messages even when not run from a TTY\n"
-    "  --timeout=N  Set timeout for browser resolving proxies (default is 5)\n"
-    "  --help       Show this help.\n";
-
-}  // namespace switches
-
-static const char *GetGErrorMessage(const GError *error) {
-  if (!error)
-    return "Unknown error.";
-  return error->message;
-}
-
-// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
-// Parses the browser's answer for resolved proxies.  It returns a
-// list of strings, each of which is a resolved proxy.
-std::deque<std::string> ParseProxyString(const std::string &input) {
-  std::deque<std::string> ret;
-  // Some of this code taken from
-  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
-  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
-  base::StringTokenizer entry_tok(input, ";");
-  while (entry_tok.GetNext()) {
-    std::string token = entry_tok.token();
-    base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
-
-    // Start by finding the first space (if any).
-    std::string::iterator space;
-    for (space = token.begin(); space != token.end(); ++space) {
-      if (IsAsciiWhitespace(*space)) {
-        break;
-      }
-    }
-
-    std::string scheme = std::string(token.begin(), space);
-    base::StringToLowerASCII(&scheme);
-    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
-    if (scheme == "socks")
-      scheme += "4";
-    else if (scheme == "proxy")
-      scheme = "http";
-    else if (scheme != "https" &&
-             scheme != "socks4" &&
-             scheme != "socks5" &&
-             scheme != "direct")
-      continue;  // Invalid proxy scheme
-
-    std::string host_and_port = std::string(space, token.end());
-    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
-    if (scheme != "direct" && host_and_port.empty())
-      continue;  // Must supply host/port when non-direct proxy used.
-    ret.push_back(scheme + "://" + host_and_port);
-  }
-  if (ret.empty() || *ret.rbegin() != kNoProxy)
-    ret.push_back(kNoProxy);
-  return ret;
-}
-
-// Define a signal-watcher class to handle the D-Bus signal sent to us when
-// the browser answers our request to resolve proxies.
-class BrowserProxyResolvedSignalWatcher : public chromeos::dbus::SignalWatcher {
- public:
-  explicit BrowserProxyResolvedSignalWatcher(GMainLoop *main_loop,
-                                             std::deque<std::string> *proxies)
-      : main_loop_(main_loop), proxies_(proxies) { }
-
-  void OnSignal(DBusMessage *message) override {
-    // Get args
-    char *source_url = NULL;
-    char *proxy_list = NULL;
-    char *error = NULL;
-    DBusError arg_error;
-    dbus_error_init(&arg_error);
-    if (!dbus_message_get_args(message, &arg_error,
-                               DBUS_TYPE_STRING, &source_url,
-                               DBUS_TYPE_STRING, &proxy_list,
-                               DBUS_TYPE_STRING, &error,
-                               DBUS_TYPE_INVALID)) {
-      LOG(ERROR) << "Error reading D-Bus signal";
-      return;
-    }
-    if (!source_url || !proxy_list) {
-      LOG(ERROR) << "Error getting url, proxy list from D-Bus signal";
-      return;
-    }
-
-    const std::deque<std::string> &proxies = ParseProxyString(proxy_list);
-    for (std::deque<std::string>::const_iterator it = proxies.begin();
-         it != proxies.end(); ++it) {
-      LOG(INFO) << "Found proxy via browser signal: " << (*it).c_str();
-      proxies_->push_back(*it);
-    }
-
-    g_main_loop_quit(main_loop_);
-  }
-
- private:
-  GMainLoop *main_loop_;
-  std::deque<std::string> *proxies_;
-};
-
-static gboolean HandleBrowserTimeout(void *data) {
-  GMainLoop *main_loop = reinterpret_cast<GMainLoop *>(data);
-  LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
-  g_main_loop_quit(main_loop);
-  return false;  // only call once
-}
-
-static bool ShowBrowserProxies(std::string url, unsigned timeout) {
-  GMainLoop *main_loop = g_main_loop_new(NULL, false);
-
-  chromeos::dbus::BusConnection dbus = chromeos::dbus::GetSystemBusConnection();
-  if (!dbus.HasConnection()) {
-    LOG(ERROR) << "Error connecting to system D-Bus";
-    return false;
-  }
-  chromeos::dbus::Proxy browser_proxy(dbus,
-                                      kLibCrosServiceName,
-                                      kLibCrosServicePath,
-                                      kLibCrosServiceInterface);
-  if (!browser_proxy) {
-    LOG(ERROR) << "Error creating D-Bus proxy to interface "
-               << "'" << kLibCrosServiceName << "'";
-    return false;
-  }
-
-  // Watch for a proxy-resolved signal sent to us
-  std::deque<std::string> proxies;
-  BrowserProxyResolvedSignalWatcher proxy_resolver(main_loop, &proxies);
-  proxy_resolver.StartMonitoring(kLibCrosProxyResolveSignalInterface,
-                                 kLibCrosProxyResolveName);
-
-  // Request the proxies for our URL.  The answer is sent to us via a
-  // proxy-resolved signal.
-  GError *gerror = NULL;
-  if (!dbus_g_proxy_call(browser_proxy.gproxy(),
-                         kLibCrosServiceResolveNetworkProxyMethodName,
-                         &gerror,
-                         G_TYPE_STRING, url.c_str(),
-                         G_TYPE_STRING, kLibCrosProxyResolveSignalInterface,
-                         G_TYPE_STRING, kLibCrosProxyResolveName,
-                         G_TYPE_INVALID, G_TYPE_INVALID)) {
-    LOG(ERROR) << "Error performing D-Bus proxy call "
-               << "'" << kLibCrosServiceResolveNetworkProxyMethodName << "'"
-               << ": " << GetGErrorMessage(gerror);
-    return false;
-  }
-
-  // Setup a timeout in case the browser doesn't respond with our signal
-  g_timeout_add_seconds(timeout, &HandleBrowserTimeout, main_loop);
-
-  // Loop until we either get the proxy-resolved signal, or until the
-  // timeout is reached.
-  g_main_loop_run(main_loop);
-
-  // If there are no proxies, then we failed to get the proxy-resolved
-  // signal (e.g. timeout was reached).
-  if (proxies.empty())
-    return false;
-
-  for (std::deque<std::string>::const_iterator it = proxies.begin();
-       it != proxies.end(); ++it) {
-    printf("%s\n", (*it).c_str());
-  }
-  return true;
-}
-
-int main(int argc, char *argv[]) {
-  CommandLine::Init(argc, argv);
-  CommandLine* cl = CommandLine::ForCurrentProcess();
-
-  if (cl->HasSwitch(switches::kHelp)) {
-    LOG(INFO) << switches::kHelpMessage;
-    return 0;
-  }
-
-  bool quiet = cl->HasSwitch(switches::kQuiet);
-  bool verbose = cl->HasSwitch(switches::kVerbose);
-
-  unsigned timeout = switches::kTimeoutDefault;
-  std::string str_timeout = cl->GetSwitchValueASCII(switches::kTimeout);
-  if (!str_timeout.empty() && !base::StringToUint(str_timeout, &timeout)) {
-    LOG(ERROR) << "Invalid timeout value: " << str_timeout;
-    return 1;
-  }
-
-  // Default to logging to syslog.
-  int init_flags = chromeos::kLogToSyslog;
-  // Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
-  // was passed.
-
-  if ((!quiet && isatty(STDERR_FILENO)) || verbose)
-    init_flags |= chromeos::kLogToStderr;
-  chromeos::InitLog(init_flags);
-
-  ::g_type_init();
-
-  std::string url;
-  CommandLine::StringVector urls = cl->GetArgs();
-  if (!urls.empty()) {
-    url = urls[0];
-    LOG(INFO) << "Resolving proxies for URL: " << url;
-  } else {
-    LOG(INFO) << "Resolving proxies without URL";
-  }
-
-  if (!ShowBrowserProxies(url, timeout)) {
-    LOG(ERROR) << "Error resolving proxies via the browser";
-    LOG(INFO) << "Assuming direct proxy";
-    printf("%s\n", kNoProxy);
-  }
-
-  return 0;
-}
diff --git a/crash_reporter/proxy_resolver.cc b/crash_reporter/proxy_resolver.cc
new file mode 100644
index 0000000..a4cc732
--- /dev/null
+++ b/crash_reporter/proxy_resolver.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "crash-reporter/proxy_resolver.h"
+
+#include <chromeos/dbus/dbus_method_invoker.h>
+
+#include "crash-reporter/libproxies.h"
+
+namespace crash_reporter {
+
+DBusProxyResolver::DBusProxyResolver(dbus::Bus* bus) : bus_(bus) {}
+
+void DBusProxyResolver::Init() {
+  lib_cros_service_proxy_ = bus_->GetObjectProxy(
+      kLibCrosServiceName, dbus::ObjectPath(kLibCrosServicePath));
+  if (!lib_cros_service_proxy_) {
+    LOG(WARNING) << "Unable to connect to LibCrosService.";
+  }
+}
+
+std::vector<std::string> DBusProxyResolver::GetProxiesForUrl(
+    const std::string& url, const base::TimeDelta& timeout) {
+  if (!lib_cros_service_proxy_) return {kNoProxy};
+
+  auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout(
+      timeout.InMilliseconds(), lib_cros_service_proxy_,
+      kLibCrosProxyResolveSignalInterface,
+      kLibCrosServiceResolveNetworkProxyMethodName, url);
+  if (response) {
+    std::string returned_message;
+    if (chromeos::dbus_utils::ExtractMethodCallResults(response.get(), nullptr,
+                                                       &returned_message)) {
+      return ParseProxyString(returned_message);
+    }
+  }
+  return {kNoProxy};
+}
+
+}  // namespace crash_reporter
diff --git a/crash_reporter/proxy_resolver.h b/crash_reporter/proxy_resolver.h
new file mode 100644
index 0000000..a6d1989
--- /dev/null
+++ b/crash_reporter/proxy_resolver.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASH_REPORTER_PROXY_RESOLVER_H_
+#define CRASH_REPORTER_PROXY_RESOLVER_H_
+
+#include <string>
+#include <vector>
+
+#include <dbus/bus.h>
+#include <dbus/object_proxy.h>
+
+namespace base {
+class TimeDelta;
+}  // namespace base
+
+namespace crash_reporter {
+
+class ProxyResolver {
+ public:
+  virtual ~ProxyResolver() {}
+
+  virtual std::vector<std::string> GetProxiesForUrl(
+      const std::string& url, const base::TimeDelta& timeout) = 0;
+};
+
+class DBusProxyResolver : public ProxyResolver {
+ public:
+  explicit DBusProxyResolver(dbus::Bus* bus);
+  ~DBusProxyResolver() override = default;
+
+  void Init();
+
+  std::vector<std::string> GetProxiesForUrl(
+      const std::string& url, const base::TimeDelta& timeout) override;
+
+ private:
+  scoped_refptr<dbus::Bus> bus_;
+  scoped_refptr<dbus::ObjectProxy> lib_cros_service_proxy_;
+};
+
+}  // namespace crash_reporter
+
+#endif  // CRASH_REPORTER_PROXY_RESOLVER_H_