|
1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 #include <assert.h> |
|
27 #include <dirent.h> |
|
28 #include <errno.h> |
|
29 #include <fcntl.h> |
|
30 #include <libxml/tree.h> |
|
31 #include <limits.h> |
|
32 #include <openssl/evp.h> |
|
33 #include <openssl/md5.h> |
|
34 #include <string.h> |
|
35 #include <sys/stat.h> |
|
36 #include <unistd.h> |
|
37 #include "api_panels.h" |
|
38 |
|
39 #define PANELDESCDIR "/usr/share/vpanels/conf" |
|
40 |
|
41 #define HASH_BSIZE 65536 |
|
42 |
|
43 /* |
|
44 * Function prototypes |
|
45 */ |
|
46 |
|
47 static int md5_file(const char *fname, unsigned char *hash); |
|
48 static data_t *get_text(xmlDoc *doc, xmlNode *node); |
|
49 static char *get_text_str(xmlDoc *doc, xmlNode *node); |
|
50 static data_t *get_prop(xmlNode *node, const char *name); |
|
51 static data_t *file_to_token(rad_locale_t *rlocale, const char *pname, |
|
52 char *file); |
|
53 static conerr_t token_to_file(data_t *t, char **f); |
|
54 static data_t *create_resource(rad_locale_t *rlocale, const char *pname, |
|
55 char *file); |
|
56 static void add_localized(rad_locale_t *rlocale, const char *pname, |
|
57 data_t *farray, char *file); |
|
58 static data_t *read_panel_path(rad_locale_t *rlocale, const char *path); |
|
59 static data_t *read_panel(const char *locale, const char *pname); |
|
60 |
|
61 /* |
|
62 * Static data |
|
63 */ |
|
64 |
|
65 static xmlDtd *dtd; |
|
66 static rad_modinfo_t modinfo = { "panels", "Visual Panels module" }; |
|
67 |
|
68 /* |
|
69 * Static functions |
|
70 */ |
|
71 |
|
72 static int |
|
73 md5_file(const char *fname, unsigned char *hash) |
|
74 { |
|
75 int fd, len = -1; |
|
76 MD5_CTX ctx; |
|
77 |
|
78 char *buffer = malloc(HASH_BSIZE); |
|
79 if (buffer == NULL) |
|
80 return (-1); |
|
81 |
|
82 if ((fd = open(fname, O_RDONLY)) == -1) { |
|
83 free(buffer); |
|
84 return (-1); |
|
85 } |
|
86 |
|
87 if (MD5_Init(&ctx) == 0) |
|
88 goto out; |
|
89 |
|
90 while ((len = read(fd, buffer, HASH_BSIZE)) > 0) |
|
91 if (MD5_Update(&ctx, buffer, len) == 0) { |
|
92 len = -1; |
|
93 break; |
|
94 } |
|
95 if (MD5_Final(hash, &ctx) == 0) |
|
96 len = -1; |
|
97 |
|
98 out: |
|
99 (void) close(fd); |
|
100 free(buffer); |
|
101 return (len); /* Should be 0 or -1 */ |
|
102 } |
|
103 |
|
104 /* |
|
105 * Seems like overkill, but it's better than mixing xml-allocated |
|
106 * and other strings. |
|
107 */ |
|
108 static data_t * |
|
109 get_text(xmlDoc *doc, xmlNode *node) |
|
110 { |
|
111 data_t *result; |
|
112 xmlChar *d = xmlNodeListGetString(doc, node, 1); |
|
113 if (d == NULL) |
|
114 return (NULL); |
|
115 |
|
116 result = data_new_string((char *)d, lt_copy); |
|
117 xmlFree(d); |
|
118 return (result); |
|
119 } |
|
120 |
|
121 static char * |
|
122 get_text_str(xmlDoc *doc, xmlNode *node) |
|
123 { |
|
124 char *result; |
|
125 xmlChar *d = xmlNodeListGetString(doc, node, 1); |
|
126 if (d == NULL) |
|
127 return (NULL); |
|
128 |
|
129 result = strdup((char *)d); |
|
130 xmlFree(d); |
|
131 return (result); |
|
132 } |
|
133 |
|
134 static data_t * |
|
135 get_prop(xmlNode *node, const char *name) |
|
136 { |
|
137 data_t *result; |
|
138 xmlChar *d = xmlGetProp(node, (xmlChar *)name); |
|
139 if (d == NULL) |
|
140 return (NULL); |
|
141 |
|
142 result = data_new_string((char *)d, lt_copy); |
|
143 xmlFree(d); |
|
144 return (result); |
|
145 } |
|
146 |
|
147 static data_t * |
|
148 file_to_token(rad_locale_t *rlocale, const char *pname, char *file) |
|
149 { |
|
150 int llen = strlen(rlocale->locale); |
|
151 int plen = strlen(pname); |
|
152 int flen = strlen(file); |
|
153 int tokenlen = llen + 1 + plen + 1 + flen + 1; |
|
154 |
|
155 char *token = rad_zalloc(tokenlen); |
|
156 if (token == NULL) { |
|
157 return (NULL); |
|
158 } |
|
159 |
|
160 char *p = token; |
|
161 (void) strcpy(p, rlocale->locale); |
|
162 p += llen + 1; |
|
163 (void) strcpy(p, pname); |
|
164 p += plen + 1; |
|
165 (void) strcpy(p, file); |
|
166 |
|
167 return (data_new_opaque(token, tokenlen, lt_free)); |
|
168 } |
|
169 |
|
170 static conerr_t |
|
171 token_to_file(data_t *t, char **f) |
|
172 { |
|
173 char *token = data_to_opaque(t); |
|
174 char tokenlen = opaque_size(t); |
|
175 |
|
176 /* Cursory validation */ |
|
177 int nullcnt = 0; |
|
178 for (int i = 0; i < tokenlen; i++) { |
|
179 if (token[i] == '\0') { |
|
180 nullcnt++; |
|
181 } |
|
182 } |
|
183 if (nullcnt != 3 || token[tokenlen - 1] != '\0') { |
|
184 /* Bad token */ |
|
185 return (ce_object); |
|
186 } |
|
187 |
|
188 char *locale = token; |
|
189 char *pname = locale + strlen(locale) + 1; |
|
190 char *file = pname + strlen(pname) + 1; |
|
191 |
|
192 data_t *panel = read_panel(locale, pname); |
|
193 if (panel == NULL) { |
|
194 /* Bad panel */ |
|
195 return (ce_object); |
|
196 } |
|
197 |
|
198 data_t *resources = struct_get(panel, "resourceDescriptors"); |
|
199 static const char * const path[] = { "file", NULL }; |
|
200 int index = array_search(resources, file, path); |
|
201 data_free(panel); |
|
202 if (index == -1) { |
|
203 /* Bad file */ |
|
204 return (ce_object); |
|
205 } |
|
206 |
|
207 *f = strdup(file); |
|
208 if (*f == NULL) { |
|
209 return (ce_nomem); |
|
210 } |
|
211 |
|
212 return (ce_ok); |
|
213 } |
|
214 |
|
215 static data_t * |
|
216 create_resource(rad_locale_t *rlocale, const char *pname, char *file) |
|
217 { |
|
218 unsigned char hbuf[MD5_DIGEST_LENGTH]; |
|
219 if (md5_file(file, hbuf) != 0) { |
|
220 return (NULL); |
|
221 } |
|
222 |
|
223 data_t *result = data_new_struct(&t__ResourceDescriptor); |
|
224 struct_set(result, "token", file_to_token(rlocale, pname, file)); |
|
225 struct_set(result, "file", data_new_string(file, lt_copy)); |
|
226 struct_set(result, "hashAlgorithm", data_new_string("MD5", lt_const)); |
|
227 struct_set(result, "hash", data_new_opaque(hbuf, MD5_DIGEST_LENGTH, |
|
228 lt_copy)); |
|
229 |
|
230 return (result); |
|
231 } |
|
232 |
|
233 static void |
|
234 add_localized(rad_locale_t *rlocale, const char *pname, data_t *farray, |
|
235 char *file) |
|
236 { |
|
237 if (rlocale != NULL && rlocale->language != NULL && |
|
238 strlen(rlocale->language)) { |
|
239 char path[PATH_MAX + 1]; |
|
240 (void) strlcpy(path, file, PATH_MAX); |
|
241 |
|
242 char *ext = strrchr(path, '.'); |
|
243 if (ext != NULL && strcmp(ext, ".jar") == 0) { |
|
244 *ext = '\0'; |
|
245 char *base = strrchr(path, '/'); |
|
246 if (base == NULL) { |
|
247 return; |
|
248 } |
|
249 *base++ = '\0'; |
|
250 |
|
251 char *fmt[] = {NULL, NULL, NULL}; |
|
252 |
|
253 /* |
|
254 * Use a ResourceBundle.getBundle-like algorithm - |
|
255 * <language>[_<territory>[@<modifier>]] - and order |
|
256 * from most- to least-specific. |
|
257 */ |
|
258 fmt[2] = "%s/locale/%s/%5$s_l10n.jar"; |
|
259 if (rlocale->territory != NULL) { |
|
260 fmt[1] = "%s/locale/%s_%s/%5$s_l10n.jar"; |
|
261 if (rlocale->modifier != NULL) { |
|
262 fmt[0] = "%s/locale/%s_%s@%s/" |
|
263 "%5$s_l10n.jar"; |
|
264 } |
|
265 } |
|
266 |
|
267 char l10njar[PATH_MAX]; |
|
268 for (int i = 0; i < RAD_COUNT(fmt); i++) { |
|
269 if (fmt[i] == NULL) { |
|
270 continue; |
|
271 } |
|
272 /* LINTED: E_SEC_PRINTF_VAR_FMT */ |
|
273 (void) snprintf(l10njar, RAD_COUNT(l10njar), |
|
274 fmt[i], path, rlocale->language, |
|
275 rlocale->territory, rlocale->modifier, |
|
276 base); |
|
277 if (access(l10njar, F_OK) == 0) { |
|
278 (void) array_add(farray, |
|
279 create_resource(rlocale, pname, |
|
280 l10njar)); |
|
281 } |
|
282 } |
|
283 } |
|
284 } |
|
285 (void) array_add(farray, create_resource(rlocale, pname, file)); |
|
286 } |
|
287 |
|
288 static data_t * |
|
289 read_panel_path(rad_locale_t *rlocale, const char *path) |
|
290 { |
|
291 xmlParserCtxt *ctx; |
|
292 xmlValidCtxt *vctx; |
|
293 xmlDoc *doc; |
|
294 data_t *panel = NULL; |
|
295 |
|
296 ctx = xmlNewParserCtxt(); |
|
297 vctx = xmlNewValidCtxt(); |
|
298 if (vctx == NULL || ctx == NULL) |
|
299 return (NULL); |
|
300 |
|
301 doc = xmlCtxtReadFile(ctx, path, NULL, 0); |
|
302 if (doc == NULL) { |
|
303 xmlFreeValidCtxt(vctx); |
|
304 xmlFreeParserCtxt(ctx); |
|
305 rad_log(RL_WARN, "Empty/no such document: %s\n", path); |
|
306 return (NULL); |
|
307 } |
|
308 |
|
309 /* |
|
310 * Validate against *our* DTD. |
|
311 */ |
|
312 if (xmlValidateDtd(vctx, doc, dtd) == 0) { |
|
313 rad_log(RL_WARN, "Invalid document: %s\n", path); |
|
314 goto out; |
|
315 } |
|
316 |
|
317 xmlNodePtr root = xmlDocGetRootElement(doc); |
|
318 if (root == NULL || strcmp((const char *)root->name, "panel") != 0) { |
|
319 rad_log(RL_WARN, "Not a panel definition: %s\n", path); |
|
320 goto out; |
|
321 } |
|
322 |
|
323 panel = data_new_struct(&t__CustomPanel); |
|
324 struct_set(panel, "locale", data_new_string(rlocale->locale, lt_copy)); |
|
325 |
|
326 data_t *pname = get_prop(root, "name"); |
|
327 struct_set(panel, "name", pname); |
|
328 |
|
329 data_t *farray = data_new_array(&t_array__ResourceDescriptor, 1); |
|
330 struct_set(panel, "resourceDescriptors", farray); |
|
331 |
|
332 char *aroot = NULL; |
|
333 for (xmlNode *np = root->children; np != NULL; np = np->next) { |
|
334 if (np->type != XML_ELEMENT_NODE) |
|
335 continue; |
|
336 if (strcmp((const char *)np->name, "mainclass") == 0) { |
|
337 data_t *mc = get_text(doc, np->children); |
|
338 struct_set(panel, "panelDescriptorClassName", mc); |
|
339 } else if (strcmp((const char *)np->name, "approot") == 0) { |
|
340 if (aroot != NULL) |
|
341 continue; /* schema violation */ |
|
342 aroot = get_text_str(doc, np->children); |
|
343 } else if (strcmp((const char *)np->name, "file") == 0) { |
|
344 char *file = get_text_str(doc, np->children); |
|
345 if (file == NULL) { |
|
346 rad_log(RL_WARN, |
|
347 "Empty <file> declaration within %s\n", |
|
348 path); |
|
349 continue; |
|
350 } |
|
351 |
|
352 if (aroot == NULL) { |
|
353 rad_log(RL_WARN, "App root not specified\n"); |
|
354 continue; |
|
355 } |
|
356 |
|
357 char full[PATH_MAX]; |
|
358 (void) snprintf(full, RAD_COUNT(full), "%s/%s", aroot, |
|
359 file); |
|
360 free(file); |
|
361 |
|
362 add_localized(rlocale, data_to_string(pname), farray, |
|
363 full); |
|
364 } |
|
365 } |
|
366 if (aroot != NULL) |
|
367 free(aroot); |
|
368 out: |
|
369 xmlFreeValidCtxt(vctx); |
|
370 xmlFreeDoc(doc); |
|
371 xmlFreeParserCtxt(ctx); |
|
372 |
|
373 return (data_purify_deep(panel)); |
|
374 } |
|
375 |
|
376 static data_t * |
|
377 read_panel(const char *locale, const char *pname) |
|
378 { |
|
379 rad_locale_t *rlocale; |
|
380 if (rad_locale_parse(locale, &rlocale) != 0) { |
|
381 return (NULL); |
|
382 } |
|
383 |
|
384 char path[PATH_MAX]; |
|
385 (void) snprintf(path, RAD_COUNT(path), "%s/%s.xml", PANELDESCDIR, |
|
386 pname); |
|
387 data_t *panel = read_panel_path(rlocale, path); |
|
388 |
|
389 if (panel != NULL) { |
|
390 /* Sanity check - ensure panel @name matches file name */ |
|
391 data_t *nameattr = struct_get(panel, "name"); |
|
392 if (strcmp(data_to_string(nameattr), pname) != 0) { |
|
393 data_free(panel); |
|
394 panel = NULL; |
|
395 } |
|
396 } |
|
397 |
|
398 rad_locale_free(rlocale); |
|
399 |
|
400 return (panel); |
|
401 } |
|
402 |
|
403 /* |
|
404 * Extern functions |
|
405 */ |
|
406 |
|
407 int |
|
408 _rad_init(void *handle) |
|
409 { |
|
410 if (rad_module_register(handle, RAD_MODVERSION, &modinfo) == -1) |
|
411 return (-1); |
|
412 |
|
413 dtd = xmlParseDTD(NULL, |
|
414 (xmlChar *)"/usr/share/lib/xml/dtd/vpanel.dtd.1"); |
|
415 |
|
416 adr_name_t *name = adr_name_fromstr( |
|
417 "com.oracle.solaris.vp.panel.common.api.panel:type=Panel"); |
|
418 (void) cont_insert_singleton(rad_container, name, &interface_Panel_svr); |
|
419 |
|
420 return (0); |
|
421 } |
|
422 |
|
423 /* ARGSUSED */ |
|
424 conerr_t |
|
425 interface_Panel_invoke_getPanel(rad_instance_t *inst, adr_method_t *meth, |
|
426 data_t **ret, data_t **args, int count, data_t **error) |
|
427 { |
|
428 const char *pname = data_to_string(args[0]); |
|
429 const char *locale = args[1] == NULL ? NULL : data_to_string(args[1]); |
|
430 |
|
431 data_t *panel = read_panel(locale, pname); |
|
432 if (panel == NULL) { |
|
433 /* |
|
434 * Could be a memory or system error, but more likely an invalid |
|
435 * name was specified. |
|
436 */ |
|
437 return (ce_object); |
|
438 } |
|
439 *ret = panel; |
|
440 |
|
441 return (ce_ok); |
|
442 } |
|
443 |
|
444 /* ARGSUSED */ |
|
445 conerr_t |
|
446 interface_Panel_read_panelNames(rad_instance_t *inst, adr_attribute_t *attr, |
|
447 data_t **data, data_t **error) |
|
448 { |
|
449 data_t *array = data_new_array(&t_array_string, 0); |
|
450 if (array == NULL) { |
|
451 return (ce_nomem); |
|
452 } |
|
453 |
|
454 DIR *d; |
|
455 if ((d = opendir(PANELDESCDIR)) == NULL) { |
|
456 if (errno == ENOENT) { |
|
457 return (ce_ok); |
|
458 } |
|
459 return (ce_system); |
|
460 } |
|
461 |
|
462 struct dirent *ent; |
|
463 while ((ent = readdir(d)) != NULL) { |
|
464 char *ext = ".xml"; |
|
465 size_t len = strlen(ent->d_name) - strlen(ext); |
|
466 if (len < 1 || strcmp(ent->d_name + len, ext) != 0) { |
|
467 continue; |
|
468 } |
|
469 (void) array_add(array, data_new_nstring(ent->d_name, len)); |
|
470 } |
|
471 |
|
472 (void) closedir(d); |
|
473 *data = data_purify(array); |
|
474 |
|
475 return (*data == NULL ? ce_nomem : ce_ok); |
|
476 } |
|
477 |
|
478 /* ARGSUSED */ |
|
479 conerr_t |
|
480 interface_Panel_invoke_getResource(rad_instance_t *inst, adr_method_t *meth, |
|
481 data_t **ret, data_t **args, int count, data_t **error) |
|
482 { |
|
483 char *file; |
|
484 conerr_t result = token_to_file(args[0], &file); |
|
485 if (result != ce_ok) { |
|
486 return (result); |
|
487 } |
|
488 |
|
489 struct stat st; |
|
490 if (stat(file, &st) != 0) { |
|
491 free(file); |
|
492 return (ce_object); |
|
493 } |
|
494 |
|
495 char *buffer = malloc(st.st_size); |
|
496 if (buffer == NULL) { |
|
497 free(file); |
|
498 return (ce_nomem); |
|
499 } |
|
500 |
|
501 int fd = open(file, O_RDONLY); |
|
502 free(file); |
|
503 if (fd == -1) { |
|
504 free(buffer); |
|
505 return (ce_priv); |
|
506 } |
|
507 |
|
508 if (read(fd, buffer, st.st_size) != st.st_size) { |
|
509 (void) close(fd); |
|
510 free(buffer); |
|
511 return (ce_system); |
|
512 } |
|
513 |
|
514 (void) close(fd); |
|
515 |
|
516 *ret = data_new_opaque(buffer, st.st_size, lt_free); |
|
517 return (*ret == NULL ? ce_nomem : ce_ok); |
|
518 } |