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

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