1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2015 Joyent, Inc.
  14  */
  15 
  16 /*
  17  * Utility functions for working with XML documents that are validated against
  18  * Document Type Definitions (DTD) shipped in the operating system.
  19  */
  20 
  21 #include <stdlib.h>
  22 #include <stdio.h>
  23 #include <string.h>
  24 #include <strings.h>
  25 #include <errno.h>
  26 #include <zone.h>
  27 
  28 #include <libxml/parser.h>
  29 #include <libxml/xmlmemory.h>
  30 
  31 #include "os_dtd.h"
  32 
  33 struct os_dtd_path {
  34         os_dtd_id_t odp_id;
  35         const char *odp_name;
  36         const char *odp_public_ident;
  37         const char *odp_path;
  38 };
  39 
  40 static os_dtd_path_t os_dtd_paths[] = {
  41         /*
  42          * DTDs for Zones infrastructure:
  43          */
  44         { OS_DTD_ZONES_BRAND, "brand",
  45             "-//Sun Microsystems Inc//DTD Brands//EN",
  46             "/usr/share/lib/xml/dtd/brand.dtd.1" },
  47         { OS_DTD_ZONES_ZONE, "zone",
  48             "-//Sun Microsystems Inc//DTD Zones//EN",
  49             "/usr/share/lib/xml/dtd/zonecfg.dtd.1" },
  50         { OS_DTD_ZONES_PLATFORM, "platform",
  51             "-//Sun Microsystems Inc//Zones Platform//EN",
  52             "/usr/share/lib/xml/dtd/zone_platform.dtd.1" },
  53 
  54         /*
  55          * DTDs for smf(5):
  56          */
  57         { OS_DTD_SMF_SERVICE_BUNDLE, "service_bundle",
  58             NULL,
  59             "/usr/share/lib/xml/dtd/service_bundle.dtd.1" },
  60 
  61         { OS_DTD_UNKNOWN, NULL, NULL, NULL }
  62 };
  63 
  64 /*
  65  * Check this document to see if it references the public identifier of a
  66  * well-known DTD that we ship with the operating system.  If there is no DTD,
  67  * or the public identifier is unknown to us, return OS_DTD_UNKNOWN.
  68  */
  69 os_dtd_id_t
  70 os_dtd_identify(xmlDocPtr doc)
  71 {
  72         xmlDtdPtr dp;
  73         int i;
  74 
  75         if ((dp = xmlGetIntSubset(doc)) == NULL) {
  76                 /*
  77                  * This document does not have an internal subset pointing
  78                  * to a DTD.
  79                  */
  80                 errno = EIO;
  81                 return (OS_DTD_UNKNOWN);
  82         }
  83 
  84         /*
  85          * The use of a symbolic name via the public identifier is preferred.
  86          * Check to see if the document refers to a public identifier for any
  87          * well-known DTD:
  88          */
  89         for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
  90                 os_dtd_path_t *odp = &os_dtd_paths[i];
  91                 const xmlChar *pubid = (const xmlChar *)odp->odp_public_ident;
  92 
  93                 if (dp->ExternalID == NULL || odp->odp_public_ident == NULL) {
  94                         continue;
  95                 }
  96 
  97                 if (xmlStrEqual(pubid, dp->ExternalID)) {
  98                         return (odp->odp_id);
  99                 }
 100         }
 101 
 102         /*
 103          * If a public identifier is not present, or does not match any known
 104          * DTD, fall back to inspecting the system identifier.
 105          */
 106         for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
 107                 os_dtd_path_t *odp = &os_dtd_paths[i];
 108                 char uri[sizeof ("file://") + MAXPATHLEN];
 109                 const xmlChar *path = (const xmlChar *)odp->odp_path;
 110 
 111                 if (dp->SystemID == NULL || odp->odp_path == NULL) {
 112                         continue;
 113                 }
 114 
 115                 /*
 116                  * The system identifier may be a regular path.
 117                  */
 118                 if (xmlStrEqual(path, dp->SystemID)) {
 119                         return (odp->odp_id);
 120                 }
 121 
 122                 /*
 123                  * The system identifier may also be a "file://"
 124                  * URI referring to a path:
 125                  */
 126                 (void) snprintf(uri, sizeof (uri), "file://%s", odp->odp_path);
 127                 if (xmlStrEqual((const xmlChar *)uri, dp->SystemID)) {
 128                         return (odp->odp_id);
 129                 }
 130         }
 131 
 132         errno = ENOENT;
 133         return (OS_DTD_UNKNOWN);
 134 }
 135 
 136 static os_dtd_path_t *
 137 os_dtd_lookup(os_dtd_id_t id)
 138 {
 139         int i;
 140 
 141         for (i = 0; os_dtd_paths[i].odp_id != OS_DTD_UNKNOWN; i++) {
 142                 os_dtd_path_t *odp = &os_dtd_paths[i];
 143 
 144                 if (odp->odp_id == id) {
 145                         return (odp);
 146                 }
 147         }
 148 
 149         errno = ENOENT;
 150         return (NULL);
 151 }
 152 
 153 /*
 154  * If this document references a DTD, remove that reference (the "internal
 155  * subset").  Install a new internal subset reference to the well-known
 156  * operating system DTD passed by the caller.  The URI in this reference will
 157  * respect the current native system prefix (e.g. "/native") if there is one,
 158  * such as when running in a branded zone.
 159  */
 160 int
 161 os_dtd_attach(xmlDocPtr doc, os_dtd_id_t id)
 162 {
 163         xmlDtdPtr dp;
 164         os_dtd_path_t *odp;
 165         const char *zroot = zone_get_nroot();
 166         char uri[sizeof ("file://") + MAXPATHLEN];
 167 
 168         if ((odp = os_dtd_lookup(id)) == NULL) {
 169                 return (-1);
 170         }
 171 
 172         if ((dp = xmlGetIntSubset(doc)) != NULL) {
 173                 /*
 174                  * This document already has an internal subset.  Remove it
 175                  * before attaching the new one.
 176                  */
 177                 xmlUnlinkNode((xmlNodePtr)dp);
 178                 xmlFreeNode((xmlNodePtr)dp);
 179         }
 180 
 181         /*
 182          * The "system identifier" of this internal subset must refer to the
 183          * path in the filesystem where the DTD file (the external subset) is
 184          * stored.  If we are running in a branded zone, that file may be at a
 185          * different path (e.g. under "/native").
 186          */
 187         (void) snprintf(uri, sizeof (uri), "file://%s%s", zroot != NULL ?
 188             zroot : "", odp->odp_path);
 189 
 190         if (xmlCreateIntSubset(doc, (const xmlChar *)odp->odp_name,
 191             (const xmlChar *)odp->odp_public_ident,
 192             (const xmlChar *)uri) == NULL) {
 193                 errno = EIO;
 194                 return (-1);
 195         }
 196 
 197         return (0);
 198 }
 199 
 200 /*ARGSUSED*/
 201 static void
 202 os_dtd_print_nothing(void *ctx, const char *msg, ...)
 203 {
 204 }
 205 
 206 int
 207 os_dtd_validate(xmlDocPtr doc, boolean_t emit_messages, boolean_t *valid)
 208 {
 209         int ret;
 210         xmlValidCtxtPtr cvp;
 211         os_dtd_id_t dtdid;
 212 
 213         if ((dtdid = os_dtd_identify(doc)) != OS_DTD_UNKNOWN) {
 214                 /*
 215                  * This document refers to a well-known DTD shipped with
 216                  * the operating system.  Ensure that it points to the
 217                  * correct local path for validation in the current context.
 218                  */
 219                 if (os_dtd_attach(doc, dtdid) != 0) {
 220                         return (-1);
 221                 }
 222         }
 223 
 224         if ((cvp = xmlNewValidCtxt()) == NULL) {
 225                 return (-1);
 226         }
 227 
 228         if (!emit_messages) {
 229                 cvp->error = os_dtd_print_nothing;
 230                 cvp->warning = os_dtd_print_nothing;
 231         }
 232 
 233         ret = xmlValidateDocument(cvp, doc);
 234         xmlFreeValidCtxt(cvp);
 235 
 236         *valid = (ret == 1 ? B_TRUE : B_FALSE);
 237         return (0);
 238 }