]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
debuginfod: User-Agent and X-Forwarded-For header relay
authorFrank Ch. Eigler <fche@redhat.com>
Wed, 25 Mar 2020 03:46:30 +0000 (23:46 -0400)
committerFrank Ch. Eigler <fche@redhat.com>
Fri, 27 Mar 2020 16:44:42 +0000 (12:44 -0400)
Extend the debuginfod client API with a function to stuff outgoing
headers into libcurl http transfers.  Use this from debuginfod so
federated trees of debuginfod/httpd can trace back to to the
originating client for administrative purposes.  Docs & test included.

Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
debuginfod/ChangeLog
debuginfod/debuginfod-client.c
debuginfod/debuginfod.cxx
debuginfod/debuginfod.h
debuginfod/libdebuginfod.map
doc/ChangeLog
doc/Makefile.am
doc/debuginfod_add_http_header.3 [new file with mode: 0644]
doc/debuginfod_find_debuginfo.3
tests/ChangeLog
tests/run-debuginfod-find.sh

index 989a90295459b53fb636b7d19562de044b7fafd4..3329be3510d2cef3034c047f550a19956d41c8e4 100644 (file)
@@ -1,3 +1,15 @@
+2020-03-24  Frank Ch. Eigler  <fche@redhat.com>
+
+       * debuginfod.h, libdebuginfod.map: New functions for _add_url_header.
+       * debuginfod-client.c (struct debuginfod_client): Add headers fields.
+       (debuginfod_add_http_header): New client api to add outgoing headers.
+       (add_default_headers): Renamed from add_extra_headers, skip if flag.
+       (debuginfod_query_server): Pass accumulated headers to libcurl.
+       (debuginfod_end): Clean accumulated headers.
+       (debuginfod_find_*): Add default headers at this point.
+       * debuginfod.cxx (handle_buildid): Add conn pointer.  Use it to relay
+       incoming UA and XFF headers to federated upstream debuginfods.
+
 2020-03-26  Frank Ch. Eigler <fche@redhat.com>
 
        * debuginfod.cxx (handler_cb): Export two families of metrics for
index b0d5cb02850827e6110e2c8cc8e517458c39f5c8..043e8aa24fac96c801935ef6b095541deb88b49e 100644 (file)
@@ -85,6 +85,10 @@ struct debuginfod_client
   /* Stores current/last url, if any. */
   char* url;
 
+  /* Accumulates outgoing http header names/values. */
+  int user_agent_set_p; /* affects add_default_headers */
+  struct curl_slist *headers;
+
   /* Can contain all other context, like cache_path, server_urls,
      timeout or other info gotten from environment variables, the
      handle data, etc. So those don't have to be reparsed and
@@ -311,8 +315,11 @@ debuginfod_clean_cache(debuginfod_client *c,
 
 
 static void
-add_extra_headers(CURL *handle)
+add_default_headers(debuginfod_client *client)
 {
+  if (client->user_agent_set_p)
+    return;
+
   /* Compute a User-Agent: string to send.  The more accurately this
      describes this host, the likelier that the debuginfod servers
      might be able to locate debuginfo for us. */
@@ -372,7 +379,7 @@ add_extra_headers(CURL *handle)
     }
 
   char *ua = NULL;
-  rc = asprintf(& ua, "%s/%s,%s,%s/%s",
+  rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
                 PACKAGE_NAME, PACKAGE_VERSION,
                 utspart ?: "",
                 id ?: "",
@@ -381,7 +388,7 @@ add_extra_headers(CURL *handle)
     ua = NULL;
 
   if (ua)
-    curl_easy_setopt(handle, CURLOPT_USERAGENT, (void*) ua); /* implicit strdup */
+    (void) debuginfod_add_http_header (client, ua);
 
   free (ua);
   free (id);
@@ -465,6 +472,8 @@ debuginfod_query_server (debuginfod_client *c,
   free (c->url);
   c->url = NULL;
 
+  add_default_headers(c);
+
   /* Is there any server we can query?  If not, don't do any work,
      just return with ENOSYS.  Don't even access the cache.  */
   urls_envvar = getenv(server_urls_envvar);
@@ -724,7 +733,7 @@ debuginfod_query_server (debuginfod_client *c,
 #endif
       curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
       curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
-      add_extra_headers(data[i].handle);
+      curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
 
       curl_multi_add_handle(curlm, data[i].handle);
       server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
@@ -977,6 +986,10 @@ debuginfod_get_url(debuginfod_client *client)
 void
 debuginfod_end (debuginfod_client *client)
 {
+  if (client == NULL)
+    return;
+
+  curl_slist_free_all (client->headers);
   free (client->url);
   free (client);
 }
@@ -1011,6 +1024,23 @@ int debuginfod_find_source(debuginfod_client *client,
 }
 
 
+/* Add an outgoing HTTP header.  */
+int debuginfod_add_http_header (debuginfod_client *client, const char* header)
+{
+  struct curl_slist *temp = curl_slist_append (client->headers, header);
+  if (temp == NULL)
+    return -ENOMEM;
+
+  /* Track if User-Agent: is being set.  If so, signal not to add the
+     default one. */
+  if (strncmp (header, "User-Agent:", 11) == 0)
+    client->user_agent_set_p = 1;
+
+  client->headers = temp;
+  return 0;
+}
+
+
 void
 debuginfod_set_progressfn(debuginfod_client *client,
                          debuginfod_progressfn_t fn)
index 98491c2c6e3c1c52f82460458dafdc64e8889905..74eb443720990cf089933c55a25fad9f4b1871d4 100644 (file)
@@ -1433,11 +1433,12 @@ debuginfod_find_progress (debuginfod_client *, long a, long b)
 }
 
 
-static struct MHD_Response* handle_buildid (const string& buildid /* unsafe */,
-                                            const string& artifacttype /* unsafe */,
-                                            const string& suffix /* unsafe */,
-                                            int *result_fd
-                                            )
+static struct MHD_Response*
+handle_buildid (MHD_Connection* conn,
+                const string& buildid /* unsafe */,
+                const string& artifacttype /* unsafe */,
+                const string& suffix /* unsafe */,
+                int *result_fd)
 {
   // validate artifacttype
   string atype_code;
@@ -1526,6 +1527,35 @@ static struct MHD_Response* handle_buildid (const string& buildid /* unsafe */,
     {
       debuginfod_set_progressfn (client, & debuginfod_find_progress);
 
+      if (conn)
+        {
+          // Transcribe incoming User-Agent:
+          string ua = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
+          string ua_complete = string("User-Agent: ") + ua;
+          debuginfod_add_http_header (client, ua_complete.c_str());
+
+          // Compute larger XFF:, for avoiding info loss during
+          // federation, and for future cyclicity detection.
+          string xff = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
+          if (xff != "")
+            xff += string(", "); // comma separated list
+
+          // Compute the client's numeric IP address only - so can't merge with conninfo()
+          const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
+                                                                       MHD_CONNECTION_INFO_CLIENT_ADDRESS);
+          struct sockaddr *so = u ? u->client_addr : 0;
+          char hostname[256] = ""; // RFC1035
+          if (so && so->sa_family == AF_INET)
+            (void) getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), NULL, 0,
+                                NI_NUMERICHOST);
+          else if (so && so->sa_family == AF_INET6)
+            (void) getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), NULL, 0,
+                                NI_NUMERICHOST);
+
+          string xff_complete = string("X-Forwarded-For: ")+xff+string(hostname);
+          debuginfod_add_http_header (client, xff_complete.c_str());
+        }
+
       if (artifacttype == "debuginfo")
        fd = debuginfod_find_debuginfo (client,
                                        (const unsigned char*) buildid.c_str(),
@@ -1735,10 +1765,9 @@ handler_cb (void * /*cls*/,
             }
 
           inc_metric("http_requests_total", "type", artifacttype);
-
           // get the resulting fd so we can report its size
           int fd;
-          r = handle_buildid(buildid, artifacttype, suffix, &fd);
+          r = handle_buildid(connection, buildid, artifacttype, suffix, &fd);
           if (r)
             {
               struct stat fs;
@@ -1837,7 +1866,7 @@ dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
           struct MHD_Response *r = 0;
           try
             {
-              r = handle_buildid (buildid, "debuginfo", "", &alt_fd);
+              r = handle_buildid (0, buildid, "debuginfo", "", &alt_fd);
             }
           catch (const reportable_exception& e)
             {
index 2ace5f3504884ea215ff0cc41fd062bfae5af4ef..8d90838b4cb2aabb55ada0231aa3b1f43fd3c0d2 100644 (file)
@@ -84,6 +84,9 @@ void* debuginfod_get_user_data (debuginfod_client *client);
 /* Get the current or last active URL, if known.  */
 const char* debuginfod_get_url (debuginfod_client *client);
 
+/* Add an outgoing HTTP request  "Header: Value".  Copies string.  */
+int debuginfod_add_http_header (debuginfod_client *client, const char* header);
+
 /* Release debuginfod client connection context handle.  */
 void debuginfod_end (debuginfod_client *client);
 
index d84e8924f7f8878c7037602f4eb81ef7f1a3ce87..b8edfb016054a3a49629bc608afa620a7592cb5e 100644 (file)
@@ -13,4 +13,5 @@ ELFUTILS_0.179 {
   debuginfod_set_user_data;
   debuginfod_get_user_data;
   debuginfod_get_url;
+  debuginfod_add_http_header;
 };
index cfb03b380ca0c5c956db245c01934d944ce92202..38ce441c40761b31358900ab3817207a6cc18bf0 100644 (file)
@@ -1,3 +1,9 @@
+2020-03-24  Frank Ch. Eigler  <fche@redhat.com>
+
+       * debuginfod_add_http_header.3: New function, documented ...
+       * debuginfod_find_debuginfo.3: ... here.
+       * Makefile.am (notrans_dist_*_man3): Add it.
+
 2020-03-26  Frank Ch. Eigler  <fche@redhat.com>
 
        * debuginfod.8 (-R): Note zstd compression complications
index 38b8226d775c428aa9f5dfd79aefd5a733548b08..f0c7e55dfa410a1effc83d1d247785411ca2e85a 100644 (file)
@@ -24,6 +24,7 @@ notrans_dist_man1_MANS=
 
 if DEBUGINFOD
 notrans_dist_man8_MANS += debuginfod.8
+notrans_dist_man3_MANS += debuginfod_add_http_header.3
 notrans_dist_man3_MANS += debuginfod_begin.3
 notrans_dist_man3_MANS += debuginfod_end.3
 notrans_dist_man3_MANS += debuginfod_find_debuginfo.3
diff --git a/doc/debuginfod_add_http_header.3 b/doc/debuginfod_add_http_header.3
new file mode 100644 (file)
index 0000000..1627993
--- /dev/null
@@ -0,0 +1 @@
+.so man3/debuginfod_find_debuginfo.3
index 42f2265b255c987dac401dd4bf43e43f2fcce13a..1c7c49918b512ff0531d4ecf0471c8bad8bb5e58 100644 (file)
@@ -54,6 +54,8 @@ OPTIONAL FUNCTIONS
 .BI "                              void *" data ");"
 .BI "void* debuginfod_get_user_data(debuginfod_client *" client ");"
 .BI "const char* debuginfod_get_url(debuginfod_client *" client ");"
+.BI "int debuginfod_add_http_header(debuginfod_client *" client ","
+.BI "                               const char* " header ");"
 
 .SH DESCRIPTION
 
@@ -160,6 +162,21 @@ The resulting string is owned by the library, and must not be modified
 or freed.  The caller should copy it if it is needed beyond the release
 of the client object.
 
+.SS "HTTP HEADER"
+
+Before a lookup function is initiated, a client application may
+add HTTP request headers to future downloads.
+.BR \%debuginfod_add_http_header ()
+may be called with strings of the form
+.BR \%"Header:\~value" .
+These strings are copied by the library.  A zero return value
+indicates success, but out-of-memory conditions may result in
+a non-zero \fI-ENOMEM\fP.
+
+By default, the library adds a descriptive \fIUser-Agent:\fP
+header to outgoing requests.  If the client application adds
+a header with the same name, this default is suppressed.
+
 .SH "CACHE"
 If the query is successful, the \fBdebuginfod_find_*\fP() functions save
 the target file to a local cache. The location of the cache is controlled
index ba88ecf6aeb9d3cb7841c544bd24aebf18169450..d1c9e952870a51fd1c0127660e83955abc3a6da1 100644 (file)
@@ -1,3 +1,8 @@
+2020-03-24  Frank Ch. Eigler  <fche@redhat.com>
+
+       * run-debuginfod-find.sh: Test relay of UA and XFF headers across
+       federating debuginfods.
+
 2020-03-26  Frank Ch. Eigler <fche@redhat.com>
 
        * run-debuginfod-find.sh: Look for debuginfod's new
index db36420f75ecbec0ec4b46d6dbf0fbf36ffe6d86..799b7005a26ead43628f8e69d2f422af3ec30502 100755 (executable)
@@ -95,8 +95,9 @@ wait_ready()
   fi
 }
 
-env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -F -R -d $DB -p $PORT1 -t0 -g0 --fdcache-fds 1 --fdcache-mbs 2 -Z .tar.xz -Z .tar.bz2=bzcat R F Z L &
+env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -F -R -d $DB -p $PORT1 -t0 -g0 --fdcache-fds 1 --fdcache-mbs 2 -Z .tar.xz -Z .tar.bz2=bzcat -v R F Z L > vlog4 2>&1 &
 PID1=$!
+tempfiles vlog4
 # Server must become ready
 wait_ready $PORT1 'ready' 1
 export DEBUGINFOD_URLS=http://127.0.0.1:$PORT1/   # or without trailing /
@@ -388,6 +389,14 @@ fi
 rm -rf $DEBUGINFOD_CACHE_PATH
 testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID
 
+# send a request to stress XFF and User-Agent federation relay;
+# we'll grep for the two patterns in vlog4
+curl -s -H 'User-Agent: TESTCURL' -H 'X-Forwarded-For: TESTXFF' $DEBUGINFOD_URLS/buildid/deaddeadbeef00000000/debuginfo -o /dev/null || true
+
+grep UA:TESTCURL vlog4
+grep XFF:TESTXFF vlog4
+
+
 # confirm that first server can't resolve symlinked info in L/ but second can
 BUILDID=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \
          -a L/foo | grep 'Build ID' | cut -d ' ' -f 7`