[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