[Libguestfs] [PATCH nbdkit RFC 2/2] curl: Implement authorization scripts.

Richard W.M. Jones rjones at redhat.com
Tue Jul 14 19:05:22 UTC 2020


On Tue, Jul 14, 2020 at 05:56:30PM +0100, Richard W.M. Jones wrote:
> +/* This is called with the lock held when we must run or re-run the
> + * auth script.
> + */
> +static int
> +run_auth_script (struct curl_handle *h)
> +{
> +  int fd;
> +  char tmpfile[] = "/tmp/errorsXXXXXX";
> +  FILE *fp;
> +  CLEANUP_FREE char *cmd = NULL, *line = NULL;
> +  size_t len = 0, linelen = 0;
> +
> +  assert (auth_script != NULL); /* checked by caller */
> +
> +  /* Reset the list of headers and cookies. */
> +  string_vector_iter (&script_headers, (void *) free);
> +  string_vector_iter (&script_cookies, (void *) free);
> +  string_vector_reset (&script_headers);
> +  string_vector_reset (&script_cookies);
> +
> +  /* Create a temporary file for the errors so we can redirect them
> +   * into nbdkit_error.
> +   */
> +  fd = mkstemp (tmpfile);
> +  if (fd == -1) {
> +    nbdkit_error ("mkstemp");
> +    return -1;
> +  }
> +  close (fd);
> +
> +  /* Generate the full script with the local $url variable. */
> +  fp = open_memstream (&cmd, &len);
> +  if (fp == NULL) {
> +    nbdkit_error ("open_memstream: %m");
> +    return -1;
> +  }
> +  fprintf (fp, "exec </dev/null\n");    /* Avoid stdin leaking (nbdkit -s). */
> +  fprintf (fp, "exec 2>%s\n", tmpfile); /* Catch errors to a temporary file. */
> +  fprintf (fp, "url=");                 /* Set the shell variable. */
> +  shell_quote (url, fp);
> +  putc ('\n', fp);
> +  putc ('\n', fp);
> +  fprintf (fp, "%s", auth_script);      /* The script or command. */
> +  if (fclose (fp) == EOF) {
> +    nbdkit_error ("memstream failed");
> +    return -1;
> +  }
> +
> +  /* Run the script and read the headers/cookies. */
> +  nbdkit_debug ("curl: running authorization script");
> +  fp = popen (cmd, "r");
> +  if (fp == NULL) {
> +    nbdkit_error ("popen: %m");
> +    return -1;
> +  }
> +  while ((len = getline (&line, &linelen, fp)) != -1) {
> +    char *p;
> +
> +    if (len > 0 && line[len-1] == '\n')
> +      line[len-1] = '\0';
> +
> +    if (strncasecmp (line, "cookie:", 7) == 0) {
> +      p = strdup (&line[7]);
> +      if (p == NULL || string_vector_append (&script_cookies, p) == -1) {
> +        nbdkit_error ("malloc");
> +        pclose (fp);
> +        return -1;
> +      }
> +    }
> +    else {
> +      p = strdup (line);
> +      if (p == NULL || string_vector_append (&script_headers, p) == -1) {
> +        nbdkit_error ("malloc");
> +        pclose (fp);
> +        return -1;
> +      }
> +    }
> +  }
> +
> +  /* If the command failed, this should return EOF and the error
> +   * message should be in the temporary file (but we only read the
> +   * first line).
> +   */
> +  if (pclose (fp) == EOF) {
> +    fp = fopen (tmpfile, "r");
> +    if ((len = getline (&line, &linelen, fp)) >= 0) {
> +      if (len > 0 && line[len-1] == '\n')
> +        line[len-1] = '\0';
> +      nbdkit_error ("authorization script failed: %s", line);
> +    }
> +    else
> +      nbdkit_error ("authorization script failed");
> +    return -1;
> +  }
> +
> +  nbdkit_debug ("authorization script returned %zu header(s) and %zu cookie(s)",
> +                script_headers.size, script_cookies.size);
> +
> +  return 0;
> +}
> +
> +static int
> +set_headers (struct curl_handle *h)
> +{
> +  struct curl_slist *p;
> +  size_t i;
> +
> +  /* Curl does not save a copy of the headers passed to
> +   * CURLOPT_HTTPHEADER so we have to store it in the handle ourselves
> +   * and be careful to unset it in the Curl handle before we free the
> +   * list.
> +   */
> +  if (h->auth_headers) {
> +    curl_easy_setopt (h->c, CURLOPT_HTTPHEADER, NULL);
> +    curl_slist_free_all (h->auth_headers);
> +    h->auth_headers = NULL;
> +  }
> +
> +  /* Copy the header=... parameters. */
> +  for (p = headers; p != NULL; p = p->next) {
> +    h->auth_headers = curl_slist_append (h->auth_headers, p->data);
> +    if (h->auth_headers == NULL) {
> +      nbdkit_error ("curl_slist_append: %m");
> +      return -1;
> +    }
> +  }
> +
> +  /* Copy the headers output by the script. */
> +  for (i = 0; i < script_headers.size; ++i) {
> +    h->auth_headers =
> +      curl_slist_append (h->auth_headers, script_headers.ptr[i]);
> +    if (h->auth_headers == NULL) {
> +      nbdkit_error ("curl_slist_append: %m");
> +      return -1;
> +    }
> +  }
> +
> +  /* Set them in the handle. */
> +  curl_easy_setopt (h->c, CURLOPT_HTTPHEADER, h->auth_headers);
> +  return 0;
> +}
> +
> +static int
> +set_cookies (struct curl_handle *h)
> +{
> +  CLEANUP_FREE char *s = NULL;
> +  size_t i;
> +
> +  /* For cookies we have to append the cookies from the command line
> +   * with the cookies from the auth script.  Either might be empty.
> +   */
> +  if (cookie != NULL) {
> +    s = strdup (cookie);
> +    if (s == NULL) {
> +      nbdkit_error ("strdup: %m");
> +      return -1;
> +    }
> +  }
> +
> +  for (i = 0; i < script_cookies.size; ++i) {
> +    char *ns;
> +
> +    if (asprintf (&ns, "%s%s%s",
> +                  s ? s : "",
> +                  s ? " ; " : "",
> +                  script_cookies.ptr[i]) == -1) {
> +      nbdkit_error ("asprintf: %m");
> +      return -1;
> +    }
> +    s = ns;
> +  }
> +
> +  /* Curl saves a copy of this string in the handle so it's OK to free
> +   * it after calling this.
> +   */
> +  if (s)
> +    curl_easy_setopt (h->c, CURLOPT_COOKIE, s);
> +
> +  return 0;
> +}

After looking again at this very complicated implementation, an
alternative comes to mind.

We would have "header-script" and "cookie-script".  These would
replace "header" and "cookie" respectively -- and be incompatible, so
there's no need to deal with appending header lists and cookie
strings.

There would still need to be a "renew" or "script-renew" parameter to
trigger these to rerun to get a new token.

What do you think about that?

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-p2v converts physical machines to virtual machines.  Boot with a
live CD or over the network (PXE) and turn machines into KVM guests.
http://libguestfs.org/virt-v2v




More information about the Libguestfs mailing list