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 }