Commit 18c6e139 authored by Andrea Shepard's avatar Andrea Shepard
Browse files

Expose authority certificate download statuses on the control port

parent 8cf9fe5b
Loading
Loading
Loading
Loading
+183 −23
Original line number Diff line number Diff line
@@ -2053,6 +2053,29 @@ getinfo_helper_dir(control_connection_t *control_conn,
  return 0;
}

/** Turn a smartlist of digests into a human-readable list of hex strings */

static char *
digest_list_to_string(smartlist_t *sl)
{
  int len;
  char *result, *s;

  /* Allow for newlines, and a \0 at the end */
  len = smartlist_len(sl) * (HEX_DIGEST_LEN + 1) + 1;
  result = tor_malloc_zero(len);

  s = result;
  SMARTLIST_FOREACH_BEGIN(sl, char *, digest) {
    base16_encode(s, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN);
    s[HEX_DIGEST_LEN] = '\n';
    s += HEX_DIGEST_LEN + 1;
  } SMARTLIST_FOREACH_END(digest);
  *s = '\0';

  return result;
}

/** Turn a download_status_t into a human-readable description in a newly
 * allocated string. */

@@ -2155,6 +2178,135 @@ download_status_to_string(const download_status_t *dl)
  return rv;
}

/** Handle the consensus download cases for getinfo_helper_downloads() */
static void
getinfo_helper_downloads_networkstatus(const char *flavor,
                                       download_status_t **dl_to_emit,
                                       const char **errmsg)
{
  /*
   * We get the one for the current bootstrapped status by default, or
   * take an extra /bootstrap or /running suffix
   */
  if (strcmp(flavor, "ns") == 0) {
    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
  } else if (strcmp(flavor, "ns/bootstrap") == 0) {
    *dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
  } else if (strcmp(flavor, "ns/running") == 0 ) {
    *dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
  } else if (strcmp(flavor, "microdesc") == 0) {
    *dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
  } else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
    *dl_to_emit =
      networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
  } else if (strcmp(flavor, "microdesc/running") == 0) {
    *dl_to_emit =
      networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
  } else {
    *errmsg = "Unknown flavor";
  }
}

/** Handle the cert download cases for getinfo_helper_downloads() */
static void
getinfo_helper_downloads_cert(const char *fp_sk_req,
                              download_status_t **dl_to_emit,
                              smartlist_t **digest_list,
                              const char **errmsg)
{
  const char *sk_req;
  char id_digest[DIGEST_LEN];
  char sk_digest[DIGEST_LEN];

  /*
   * We have to handle four cases; fp_sk_req is the request with
   * a prefix of "downloads/cert/" snipped off.
   *
   * Case 1: fp_sk_req = "fps"
   *  - We should emit a digest_list with a list of all the identity
   *    fingerprints that can be queried for certificate download status;
   *    get it by calling list_authority_ids_with_downloads().
   *
   * Case 2: fp_sk_req = "fp/<fp>" for some fingerprint fp
   *  - We want the default certificate for this identity fingerprint's
   *    download status; this is the download we get from URLs starting
   *    in /fp/ on the directory server.  We can get it with
   *    id_only_download_status_for_authority_id().
   *
   * Case 3: fp_sk_req = "fp/<fp>/sks" for some fingerprint fp
   *  - We want a list of all signing key digests for this identity
   *    fingerprint which can be queried for certificate download status.
   *    Get it with list_sk_digests_for_authority_id().
   *
   * Case 4: fp_sk_req = "fp/<fp>/<sk>" for some fingerprint fp and
   *         signing key digest sk
   *   - We want the download status for the certificate for this specific
   *     signing key and fingerprint.  These correspond to the ones we get
   *     from URLs starting in /fp-sk/ on the directory server.  Get it with
   *     list_sk_digests_for_authority_id().
   */

  if (strcmp(fp_sk_req, "fps") == 0) {
    *digest_list = list_authority_ids_with_downloads();
    if (!(*digest_list)) {
      *errmsg = "Failed to get list of authority identity digests (!)";
    }
  } else if (!strcmpstart(fp_sk_req, "fp/")) {
    fp_sk_req += strlen("fp/");
    /* Okay, look for another / to tell the fp from fp-sk cases */
    sk_req = strchr(fp_sk_req, '/');
    if (sk_req) {
      /* okay, split it here and try to parse <fp> */
      if (base16_decode(id_digest, DIGEST_LEN,
                        fp_sk_req, sk_req - fp_sk_req) == DIGEST_LEN) {
        /* Skip past the '/' */
        ++sk_req;
        if (strcmp(sk_req, "sks") == 0) {
          /* We're asking for the list of signing key fingerprints */
          *digest_list = list_sk_digests_for_authority_id(id_digest);
          if (!(*digest_list)) {
            *errmsg = "Failed to get list of signing key digests for this "
                      "authority identity digest";
          }
        } else {
          /* We've got a signing key digest */
          if (base16_decode(sk_digest, DIGEST_LEN,
                            sk_req, strlen(sk_req)) == DIGEST_LEN) {
            *dl_to_emit =
              download_status_for_authority_id_and_sk(id_digest, sk_digest);
            if (!(*dl_to_emit)) {
              *errmsg = "Failed to get download status for this identity/"
                        "signing key digest pair";
            }
          } else {
            *errmsg = "That didn't look like a signing key digest";
          }
        }
      } else {
        *errmsg = "That didn't look like an identity digest";
      }
    } else {
      /* We're either in downloads/certs/fp/<fp>, or we can't parse <fp> */
      if (strlen(fp_sk_req) == HEX_DIGEST_LEN) {
        if (base16_decode(id_digest, DIGEST_LEN,
                          fp_sk_req, strlen(fp_sk_req)) == DIGEST_LEN) {
          *dl_to_emit = id_only_download_status_for_authority_id(id_digest);
          if (!(*dl_to_emit)) {
            *errmsg = "Failed to get download status for this authority "
                      "identity digest";
          }
        } else {
          *errmsg = "That didn't look like a digest";
        }
      } else {
        *errmsg = "That didn't look like a digest";
      }
    }
  } else {
    *errmsg = "Unknown certificate download status query";
  }
}

/** Implementation helper for GETINFO: knows the answers for questions about
 * download status information. */
static int
@@ -2162,8 +2314,8 @@ getinfo_helper_downloads(control_connection_t *control_conn,
                   const char *question, char **answer,
                   const char **errmsg)
{
  const char *flavor;
  download_status_t *dl_to_emit = NULL;
  smartlist_t *digest_list = NULL;

  /* Assert args are sane */
  tor_assert(control_conn != NULL);
@@ -2176,33 +2328,26 @@ getinfo_helper_downloads(control_connection_t *control_conn,

  /* Are we after networkstatus downloads? */
  if (!strcmpstart(question, "downloads/networkstatus/")) {
    flavor = question + strlen("downloads/networkstatus/");
    /*
     * We get the one for the current bootstrapped status by default, or
     * take an extra /bootstrap or /running suffix
     */
    if (strcmp(flavor, "ns") == 0) {
      dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_NS);
    } else if (strcmp(flavor, "ns/bootstrap") == 0) {
      dl_to_emit = networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_NS);
    } else if (strcmp(flavor, "ns/running") == 0 ) {
      dl_to_emit = networkstatus_get_dl_status_by_flavor_running(FLAV_NS);
    } else if (strcmp(flavor, "microdesc") == 0) {
      dl_to_emit = networkstatus_get_dl_status_by_flavor(FLAV_MICRODESC);
    } else if (strcmp(flavor, "microdesc/bootstrap") == 0) {
      dl_to_emit =
        networkstatus_get_dl_status_by_flavor_bootstrap(FLAV_MICRODESC);
    } else if (strcmp(flavor, "microdesc/running") == 0) {
      dl_to_emit =
        networkstatus_get_dl_status_by_flavor_running(FLAV_MICRODESC);
    getinfo_helper_downloads_networkstatus(
        question + strlen("downloads/networkstatus/"),
        &dl_to_emit, errmsg);
  } else if (!strcmpstart(question, "downloads/cert/")) {
    getinfo_helper_downloads_cert(
        question + strlen("downloads/cert/"),
        &dl_to_emit, &digest_list, errmsg);
  } else {
      *errmsg = "Unknown flavor";
    }
    *errmsg = "Unknown download status query";
  }

  if (dl_to_emit) {
    *answer = download_status_to_string(dl_to_emit);

    return 0;
  } else if (digest_list) {
    *answer = digest_list_to_string(digest_list);
    SMARTLIST_FOREACH(digest_list, void *, s, tor_free(s));
    smartlist_free(digest_list);

    return 0;
  } else {
    if (!(*errmsg)) {
@@ -2666,6 +2811,21 @@ static const getinfo_item_t getinfo_items[] = {
      "Download status for bootstrap-time microdesc download"),
  DOC("downloads/networkstatus/microdesc/running",
      "Download status for run-time microdesc download"),
  PREFIX("downloads/cert/", downloads,
         "Download statuses for certificates, by id fingerprint and "
         "signing key"),
  DOC("downloads/cert/fps",
      "List of authority fingerprints for which any download statuses "
      "exist"),
  DOC("downloads/cert/fp/<fp>",
      "Download status for <fp> with the default signing key; corresponds "
      "to /fp/ URLs on directory server."),
  DOC("downloads/cert/fp/<fp>/sks",
      "List of signing keys for which specific download statuses are "
      "available for this id fingerprint"),
  DOC("downloads/cert/fp/<fp>/<sk>",
      "Download status for <fp> with signing key <sk>; corresponds "
      "to /fp-sk/ URLs on directory server."),
  ITEM("info/names", misc,
       "List of GETINFO options, types, and documentation."),
  ITEM("events/names", misc,
+106 −0
Original line number Diff line number Diff line
@@ -253,6 +253,112 @@ get_cert_list(const char *id_digest)
  return cl;
}

/** Return a list of authority ID digests with potentially enumerable lists
 * of download_status_t objects; used by controller GETINFO queries.
 */

smartlist_t *
list_authority_ids_with_downloads(void)
{
  smartlist_t *ids = smartlist_new();
  digestmap_iter_t *i;
  const char *digest;
  char *tmp;
  void *cl;

  if (trusted_dir_certs) {
    for (i = digestmap_iter_init(trusted_dir_certs);
         !(digestmap_iter_done(i));
         i = digestmap_iter_next(trusted_dir_certs, i)) {
      /*
       * We always have at least dl_status_by_id to query, so no need to
       * probe deeper than the existence of a cert_list_t.
       */
      digestmap_iter_get(i, &digest, &cl);
      tmp = tor_malloc(DIGEST_LEN);
      memcpy(tmp, digest, DIGEST_LEN);
      smartlist_add(ids, tmp);
    }
  }
  /* else definitely no downlaods going since nothing even has a cert list */

  return ids;
}

/** Given an authority ID digest, return a pointer to the default download
 * status, or NULL if there is no such entry in trusted_dir_certs */

download_status_t *
id_only_download_status_for_authority_id(const char *digest)
{
  download_status_t *dl = NULL;
  cert_list_t *cl;

  if (trusted_dir_certs) {
    cl = digestmap_get(trusted_dir_certs, digest);
    if (cl) {
      dl = &(cl->dl_status_by_id);
    }
  }

  return dl;
}

/** Given an authority ID digest, return a smartlist of signing key digests
 * for which download_status_t is potentially queryable, or NULL if no such
 * authority ID digest is known. */

smartlist_t *
list_sk_digests_for_authority_id(const char *digest)
{
  smartlist_t *sks = NULL;
  cert_list_t *cl;
  dsmap_iter_t *i;
  const char *sk_digest;
  char *tmp;
  download_status_t *dl;

  if (trusted_dir_certs) {
    cl = digestmap_get(trusted_dir_certs, digest);
    if (cl) {
      sks = smartlist_new();
      if (cl->dl_status_map) {
        for (i = dsmap_iter_init(cl->dl_status_map);
             !(dsmap_iter_done(i));
             i = dsmap_iter_next(cl->dl_status_map, i)) {
          /* Pull the digest out and add it to the list */
          dsmap_iter_get(i, &sk_digest, &dl);
          tmp = tor_malloc(DIGEST_LEN);
          memcpy(tmp, sk_digest, DIGEST_LEN);
          smartlist_add(sks, tmp);
        }
      }
    }
  }

  return sks;
}

/** Given an authority ID digest and a signing key digest, return the
 * download_status_t or NULL if none exists. */

download_status_t *
download_status_for_authority_id_and_sk(const char *id_digest,
                                        const char *sk_digest)
{
  download_status_t *dl = NULL;
  cert_list_t *cl = NULL;

  if (trusted_dir_certs) {
    cl = digestmap_get(trusted_dir_certs, id_digest);
    if (cl && cl->dl_status_map) {
      dl = dsmap_get(cl->dl_status_map, sk_digest);
    }
  }

  return dl;
}

/** Release all space held by a cert_list_t */
static void
cert_list_free(cert_list_t *cl)
+8 −0
Original line number Diff line number Diff line
@@ -104,6 +104,14 @@ void routerlist_remove(routerlist_t *rl, routerinfo_t *ri, int make_old,
void routerlist_free_all(void);
void routerlist_reset_warnings(void);

smartlist_t * list_authority_ids_with_downloads(void);
download_status_t * id_only_download_status_for_authority_id(
                                                    const char *digest);
smartlist_t * list_sk_digests_for_authority_id(const char *digest);
download_status_t * download_status_for_authority_id_and_sk(
                                                    const char *id_digest,
                                                    const char *sk_digest);

static int WRA_WAS_ADDED(was_router_added_t s);
static int WRA_WAS_OUTDATED(was_router_added_t s);
static int WRA_WAS_REJECTED(was_router_added_t s);