d76c62
From 4abdfae3b67295a0143f650768630e009d1b2798 Mon Sep 17 00:00:00 2001
d76c62
Message-Id: <4abdfae3b67295a0143f650768630e009d1b2798@dist-git>
d76c62
From: Peter Krempa <pkrempa@redhat.com>
d76c62
Date: Mon, 16 Mar 2020 22:11:57 +0100
d76c62
Subject: [PATCH] conf: Add support for cookies for HTTP based disks
d76c62
MIME-Version: 1.0
d76c62
Content-Type: text/plain; charset=UTF-8
d76c62
Content-Transfer-Encoding: 8bit
d76c62
d76c62
Add possibility to specify one or more cookies for http based disks.
d76c62
This patch adds the config parser, storage and validation of the
d76c62
cookies.
d76c62
d76c62
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
d76c62
Reviewed-by: Ján Tomko <jtomko@redhat.com>
d76c62
(cherry picked from commit 3b076391befc3fe72deb0c244ac6c2b4c100b410)
d76c62
d76c62
https://bugzilla.redhat.com/show_bug.cgi?id=1804750
d76c62
Message-Id: <3135a30f0d0a1a4bb8da02c49f10a1bcf3a394f4.1584391727.git.pkrempa@redhat.com>
d76c62
Reviewed-by: Ján Tomko <jtomko@redhat.com>
d76c62
---
d76c62
 docs/formatdomain.html.in                     |  10 ++
d76c62
 docs/schemas/domaincommon.rng                 |  24 ++++
d76c62
 src/conf/domain_conf.c                        |  82 +++++++++++++
d76c62
 src/libvirt_private.syms                      |   1 +
d76c62
 src/util/virstoragefile.c                     | 115 ++++++++++++++++++
d76c62
 src/util/virstoragefile.h                     |  15 +++
d76c62
 .../disk-network-http.xml                     |   8 ++
d76c62
 7 files changed, 255 insertions(+)
d76c62
d76c62
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
d76c62
index 2cce247958..5a10d64e83 100644
d76c62
--- a/docs/formatdomain.html.in
d76c62
+++ b/docs/formatdomain.html.in
d76c62
@@ -2839,6 +2839,9 @@
d76c62
     <driver name='qemu' type='raw'/>
d76c62
     <source protocol="http" name="url_path">
d76c62
       <host name="hostname" port="80"/>
d76c62
+      <cookies>
d76c62
+        <cookie name="test">somevalue</cookie>
d76c62
+      </cookies>
d76c62
     </source>
d76c62
     <target dev='hde' bus='ide' tray='open'/>
d76c62
     <readonly/>
d76c62
@@ -3382,6 +3385,13 @@
d76c62
             certificate validation. Supported values are yes and
d76c62
             no. Since 6.2.0
d76c62
           
d76c62
+          
cookies
d76c62
+          
d76c62
+            For http and https accessed storage it's
d76c62
+            possible to pass one or more cookies. The cookie name and value
d76c62
+            must conform to the HTTP specification.
d76c62
+            Since 6.2.0
d76c62
+          
d76c62
         
d76c62
 
d76c62
         

d76c62
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
d76c62
index 548601b61c..bdf35e64f6 100644
d76c62
--- a/docs/schemas/domaincommon.rng
d76c62
+++ b/docs/schemas/domaincommon.rng
d76c62
@@ -1817,6 +1817,24 @@
d76c62
     </element>
d76c62
   </define>
d76c62
 
d76c62
+  <define name="diskSourceNetworkProtocolHTTPCookies">
d76c62
+    <element name="cookies">
d76c62
+      <oneOrMore>
d76c62
+        <element name="cookie">
d76c62
+          <attribute name="name">
d76c62
+            <data type="string">
d76c62
+              <param name="pattern">[!#$%&'*+\-.0-9A-Z\^_`a-z|~]+</param>
d76c62
+            </data>
d76c62
+          </attribute>
d76c62
+          <data type="string">
d76c62
+            <param name="pattern">[!#$%&'()*+\-./0-9:>=<?@A-Z\^_`\[\]a-z|~]+</param>
d76c62
+          </data>
d76c62
+        </element>
d76c62
+      </oneOrMore>
d76c62
+      <empty/>
d76c62
+    </element>
d76c62
+  </define>
d76c62
+
d76c62
   <define name="diskSourceNetworkProtocolHTTPS">
d76c62
     <element name="source">
d76c62
       <attribute name="protocol">
d76c62
@@ -1833,6 +1851,9 @@
d76c62
       <optional>
d76c62
         <ref name="diskSourceNetworkProtocolSSLVerify"/>
d76c62
       </optional>
d76c62
+      <optional>
d76c62
+        <ref name="diskSourceNetworkProtocolHTTPCookies"/>
d76c62
+      </optional>
d76c62
     </element>
d76c62
   </define>
d76c62
 
d76c62
@@ -1849,6 +1870,9 @@
d76c62
       <optional>
d76c62
         <ref name="encryption"/>
d76c62
       </optional>
d76c62
+      <optional>
d76c62
+        <ref name="diskSourceNetworkProtocolHTTPCookies"/>
d76c62
+      </optional>
d76c62
     </element>
d76c62
   </define>
d76c62
 
d76c62
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
d76c62
index 70bbc35bb3..d066d3aac1 100644
d76c62
--- a/src/conf/domain_conf.c
d76c62
+++ b/src/conf/domain_conf.c
d76c62
@@ -9249,6 +9249,62 @@ virDomainDiskSourcePoolDefParse(xmlNodePtr node,
d76c62
 }
d76c62
 
d76c62
 
d76c62
+static virStorageNetCookieDefPtr
d76c62
+virDomainStorageNetCookieParse(xmlNodePtr node,
d76c62
+                               xmlXPathContextPtr ctxt)
d76c62
+{
d76c62
+    VIR_XPATH_NODE_AUTORESTORE(ctxt);
d76c62
+    g_autoptr(virStorageNetCookieDef) cookie = NULL;
d76c62
+
d76c62
+    ctxt->node = node;
d76c62
+
d76c62
+    cookie = g_new0(virStorageNetCookieDef, 1);
d76c62
+
d76c62
+    if (!(cookie->name = virXPathString("string(./@name)", ctxt))) {
d76c62
+        virReportError(VIR_ERR_XML_ERROR, "%s", _("missing cookie name"));
d76c62
+        return NULL;
d76c62
+    }
d76c62
+
d76c62
+    if (!(cookie->value = virXPathString("string(.)", ctxt))) {
d76c62
+        virReportError(VIR_ERR_XML_ERROR, _("missing value for cookie '%s'"),
d76c62
+                       cookie->name);
d76c62
+        return NULL;
d76c62
+    }
d76c62
+
d76c62
+    return g_steal_pointer(&cookie);
d76c62
+}
d76c62
+
d76c62
+
d76c62
+static int
d76c62
+virDomainStorageNetCookiesParse(xmlNodePtr node,
d76c62
+                                xmlXPathContextPtr ctxt,
d76c62
+                                virStorageSourcePtr src)
d76c62
+{
d76c62
+    VIR_XPATH_NODE_AUTORESTORE(ctxt);
d76c62
+    g_autofree xmlNodePtr *nodes = NULL;
d76c62
+    ssize_t nnodes;
d76c62
+    size_t i;
d76c62
+
d76c62
+    ctxt->node = node;
d76c62
+
d76c62
+    if ((nnodes = virXPathNodeSet("./cookie", ctxt, &nodes)) < 0)
d76c62
+        return -1;
d76c62
+
d76c62
+    src->cookies = g_new0(virStorageNetCookieDefPtr, nnodes);
d76c62
+    src->ncookies = nnodes;
d76c62
+
d76c62
+    for (i = 0; i < nnodes; i++) {
d76c62
+        if (!(src->cookies[i] = virDomainStorageNetCookieParse(nodes[i], ctxt)))
d76c62
+            return -1;
d76c62
+    }
d76c62
+
d76c62
+    if (virStorageSourceNetCookiesValidate(src) < 0)
d76c62
+        return -1;
d76c62
+
d76c62
+    return 0;
d76c62
+}
d76c62
+
d76c62
+
d76c62
 static int
d76c62
 virDomainDiskSourceNetworkParse(xmlNodePtr node,
d76c62
                                 xmlXPathContextPtr ctxt,
d76c62
@@ -9260,6 +9316,7 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
d76c62
     g_autofree char *haveTLS = NULL;
d76c62
     g_autofree char *tlsCfg = NULL;
d76c62
     g_autofree char *sslverifystr = NULL;
d76c62
+    xmlNodePtr tmpnode;
d76c62
 
d76c62
     if (!(protocol = virXMLPropString(node, "protocol"))) {
d76c62
         virReportError(VIR_ERR_XML_ERROR, "%s",
d76c62
@@ -9345,6 +9402,13 @@ virDomainDiskSourceNetworkParse(xmlNodePtr node,
d76c62
         src->sslverify = verify;
d76c62
     }
d76c62
 
d76c62
+    if ((src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
d76c62
+         src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) &&
d76c62
+        (tmpnode = virXPathNode("./cookies", ctxt))) {
d76c62
+        if (virDomainStorageNetCookiesParse(tmpnode, ctxt, src) < 0)
d76c62
+            return -1;
d76c62
+    }
d76c62
+
d76c62
     return 0;
d76c62
 }
d76c62
 
d76c62
@@ -24281,6 +24345,22 @@ virDomainSourceDefFormatSeclabel(virBufferPtr buf,
d76c62
 }
d76c62
 
d76c62
 
d76c62
+static void
d76c62
+virDomainDiskSourceFormatNetworkCookies(virBufferPtr buf,
d76c62
+                                        virStorageSourcePtr src)
d76c62
+{
d76c62
+    g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
d76c62
+    size_t i;
d76c62
+
d76c62
+    for (i = 0; i < src->ncookies; i++) {
d76c62
+        virBufferEscapeString(&childBuf, "<cookie name='%s'>", src->cookies[i]->name);
d76c62
+        virBufferEscapeString(&childBuf, "%s</cookie>\n", src->cookies[i]->value);
d76c62
+    }
d76c62
+
d76c62
+    virXMLFormatElement(buf, "cookies", NULL, &childBuf);
d76c62
+}
d76c62
+
d76c62
+
d76c62
 static int
d76c62
 virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
d76c62
                                  virBufferPtr childBuf,
d76c62
@@ -24331,6 +24411,8 @@ virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
d76c62
                           virTristateBoolTypeToString(src->sslverify));
d76c62
     }
d76c62
 
d76c62
+    virDomainDiskSourceFormatNetworkCookies(childBuf, src);
d76c62
+
d76c62
     return 0;
d76c62
 }
d76c62
 
d76c62
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
d76c62
index dbbec0d567..ac5527ef01 100644
d76c62
--- a/src/libvirt_private.syms
d76c62
+++ b/src/libvirt_private.syms
d76c62
@@ -3123,6 +3123,7 @@ virStorageSourceIsEmpty;
d76c62
 virStorageSourceIsLocalStorage;
d76c62
 virStorageSourceIsRelative;
d76c62
 virStorageSourceIsSameLocation;
d76c62
+virStorageSourceNetCookiesValidate;
d76c62
 virStorageSourceNetworkAssignDefaultPorts;
d76c62
 virStorageSourceNew;
d76c62
 virStorageSourceNewFromBacking;
d76c62
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
d76c62
index cfa77fccf8..6350168d73 100644
d76c62
--- a/src/util/virstoragefile.c
d76c62
+++ b/src/util/virstoragefile.c
d76c62
@@ -2157,6 +2157,118 @@ virStorageSourceSeclabelsCopy(virStorageSourcePtr to,
d76c62
 }
d76c62
 
d76c62
 
d76c62
+void
d76c62
+virStorageNetCookieDefFree(virStorageNetCookieDefPtr def)
d76c62
+{
d76c62
+    if (!def)
d76c62
+        return;
d76c62
+
d76c62
+    g_free(def->name);
d76c62
+    g_free(def->value);
d76c62
+
d76c62
+    g_free(def);
d76c62
+}
d76c62
+
d76c62
+
d76c62
+static void
d76c62
+virStorageSourceNetCookiesClear(virStorageSourcePtr src)
d76c62
+{
d76c62
+    size_t i;
d76c62
+
d76c62
+    if (!src || !src->cookies)
d76c62
+        return;
d76c62
+
d76c62
+    for (i = 0; i < src->ncookies; i++)
d76c62
+        virStorageNetCookieDefFree(src->cookies[i]);
d76c62
+
d76c62
+    g_clear_pointer(&src->cookies, g_free);
d76c62
+    src->ncookies = 0;
d76c62
+}
d76c62
+
d76c62
+
d76c62
+static void
d76c62
+virStorageSourceNetCookiesCopy(virStorageSourcePtr to,
d76c62
+                               const virStorageSource *from)
d76c62
+{
d76c62
+    size_t i;
d76c62
+
d76c62
+    if (from->ncookies == 0)
d76c62
+        return;
d76c62
+
d76c62
+    to->cookies = g_new0(virStorageNetCookieDefPtr, from->ncookies);
d76c62
+    to->ncookies = from->ncookies;
d76c62
+
d76c62
+    for (i = 0; i < from->ncookies; i++) {
d76c62
+        to->cookies[i]->name = g_strdup(from->cookies[i]->name);
d76c62
+        to->cookies[i]->value = g_strdup(from->cookies[i]->value);
d76c62
+    }
d76c62
+}
d76c62
+
d76c62
+
d76c62
+/* see https://tools.ietf.org/html/rfc6265#section-4.1.1 */
d76c62
+static const char virStorageSourceCookieValueInvalidChars[] =
d76c62
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
d76c62
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
d76c62
+ " \",;\\";
d76c62
+
d76c62
+/* in addition cookie name can't contain these */
d76c62
+static const char virStorageSourceCookieNameInvalidChars[] =
d76c62
+ "()<>@:/[]?={}";
d76c62
+
d76c62
+static int
d76c62
+virStorageSourceNetCookieValidate(virStorageNetCookieDefPtr def)
d76c62
+{
d76c62
+    /* name must have at least 1 character */
d76c62
+    if (*(def->name) == '\0') {
d76c62
+        virReportError(VIR_ERR_XML_ERROR, "%s",
d76c62
+                       _("cookie name must not be empty"));
d76c62
+        return -1;
d76c62
+    }
d76c62
+
d76c62
+    /* check invalid characters in name */
d76c62
+    if (virStringHasChars(def->name, virStorageSourceCookieValueInvalidChars) ||
d76c62
+        virStringHasChars(def->name, virStorageSourceCookieNameInvalidChars)) {
d76c62
+        virReportError(VIR_ERR_XML_ERROR,
d76c62
+                       _("cookie name '%s' contains invalid characters"),
d76c62
+                       def->name);
d76c62
+        return -1;
d76c62
+    }
d76c62
+
d76c62
+    /* check invalid characters in value */
d76c62
+    if (virStringHasChars(def->value, virStorageSourceCookieValueInvalidChars)) {
d76c62
+        virReportError(VIR_ERR_XML_ERROR,
d76c62
+                       _("value of cookie '%s' contains invalid characters"),
d76c62
+                       def->name);
d76c62
+        return -1;
d76c62
+    }
d76c62
+
d76c62
+    return 0;
d76c62
+}
d76c62
+
d76c62
+
d76c62
+int
d76c62
+virStorageSourceNetCookiesValidate(virStorageSourcePtr src)
d76c62
+{
d76c62
+    size_t i;
d76c62
+    size_t j;
d76c62
+
d76c62
+    for (i = 0; i < src->ncookies; i++) {
d76c62
+        if (virStorageSourceNetCookieValidate(src->cookies[i]) < 0)
d76c62
+            return -1;
d76c62
+
d76c62
+        for (j = i + 1; j < src->ncookies; j++) {
d76c62
+            if (STREQ(src->cookies[i]->name, src->cookies[j]->name)) {
d76c62
+                virReportError(VIR_ERR_XML_ERROR, _("duplicate cookie '%s'"),
d76c62
+                               src->cookies[i]->name);
d76c62
+                return -1;
d76c62
+            }
d76c62
+        }
d76c62
+    }
d76c62
+
d76c62
+    return 0;
d76c62
+}
d76c62
+
d76c62
+
d76c62
 static virStorageTimestampsPtr
d76c62
 virStorageTimestampsCopy(const virStorageTimestamps *src)
d76c62
 {
d76c62
@@ -2299,6 +2411,8 @@ virStorageSourceCopy(const virStorageSource *src,
d76c62
         def->nhosts = src->nhosts;
d76c62
     }
d76c62
 
d76c62
+    virStorageSourceNetCookiesCopy(def, src);
d76c62
+
d76c62
     if (src->srcpool &&
d76c62
         !(def->srcpool = virStorageSourcePoolDefCopy(src->srcpool)))
d76c62
         return NULL;
d76c62
@@ -2560,6 +2674,7 @@ virStorageSourceClear(virStorageSourcePtr def)
d76c62
     VIR_FREE(def->volume);
d76c62
     VIR_FREE(def->snapshot);
d76c62
     VIR_FREE(def->configFile);
d76c62
+    virStorageSourceNetCookiesClear(def);
d76c62
     virStorageSourcePoolDefFree(def->srcpool);
d76c62
     virBitmapFree(def->features);
d76c62
     VIR_FREE(def->compat);
d76c62
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
d76c62
index fab4248c3d..1c7c046ad6 100644
d76c62
--- a/src/util/virstoragefile.h
d76c62
+++ b/src/util/virstoragefile.h
d76c62
@@ -162,6 +162,17 @@ struct _virStorageNetHostDef {
d76c62
     char *socket;  /* path to unix socket */
d76c62
 };
d76c62
 
d76c62
+typedef struct _virStorageNetCookieDef virStorageNetCookieDef;
d76c62
+typedef virStorageNetCookieDef *virStorageNetCookieDefPtr;
d76c62
+struct _virStorageNetCookieDef {
d76c62
+    char *name;
d76c62
+    char *value;
d76c62
+};
d76c62
+
d76c62
+void virStorageNetCookieDefFree(virStorageNetCookieDefPtr def);
d76c62
+
d76c62
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageNetCookieDef, virStorageNetCookieDefFree);
d76c62
+
d76c62
 /* Information for a storage volume from a virStoragePool */
d76c62
 
d76c62
 /*
d76c62
@@ -276,6 +287,8 @@ struct _virStorageSource {
d76c62
                          the source definition */
d76c62
     size_t nhosts;
d76c62
     virStorageNetHostDefPtr hosts;
d76c62
+    size_t ncookies;
d76c62
+    virStorageNetCookieDefPtr *cookies;
d76c62
     virStorageSourcePoolDefPtr srcpool;
d76c62
     virStorageAuthDefPtr auth;
d76c62
     bool authInherited;
d76c62
@@ -477,6 +490,8 @@ int virStorageSourceUpdateCapacity(virStorageSourcePtr src,
d76c62
 int virStorageSourceNewFromBacking(virStorageSourcePtr parent,
d76c62
                                    virStorageSourcePtr *backing);
d76c62
 
d76c62
+int virStorageSourceNetCookiesValidate(virStorageSourcePtr src);
d76c62
+
d76c62
 virStorageSourcePtr virStorageSourceCopy(const virStorageSource *src,
d76c62
                                          bool backingChain)
d76c62
     ATTRIBUTE_NONNULL(1);
d76c62
diff --git a/tests/genericxml2xmlindata/disk-network-http.xml b/tests/genericxml2xmlindata/disk-network-http.xml
d76c62
index bdcc1977f2..bafb77c8ec 100644
d76c62
--- a/tests/genericxml2xmlindata/disk-network-http.xml
d76c62
+++ b/tests/genericxml2xmlindata/disk-network-http.xml
d76c62
@@ -33,6 +33,10 @@
d76c62
       <driver name='qemu' type='raw'/>
d76c62
       <source protocol='http' name='test3.img'>
d76c62
         <host name='example.org' port='1234'/>
d76c62
+        <cookies>
d76c62
+          <cookie name='test'>testcookievalue</cookie>
d76c62
+          <cookie name='test2'>blurb</cookie>
d76c62
+        </cookies>
d76c62
       </source>
d76c62
       <target dev='vdc' bus='virtio'/>
d76c62
     </disk>
d76c62
@@ -41,6 +45,10 @@
d76c62
       <source protocol='https' name='test4.img'>
d76c62
         <host name='example.org' port='1234'/>
d76c62
         <ssl verify='yes'/>
d76c62
+        <cookies>
d76c62
+          <cookie name='test'>testcookievalue</cookie>
d76c62
+          <cookie name='test2'>blurb</cookie>
d76c62
+        </cookies>
d76c62
       </source>
d76c62
       <target dev='vdd' bus='virtio'/>
d76c62
     </disk>
d76c62
-- 
d76c62
2.25.1
d76c62