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