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, 2013, 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_panel.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 adr_data_t *get_text(xmlDoc *doc, xmlNode *node); |
|
49 static char *get_text_str(xmlDoc *doc, xmlNode *node); |
|
50 static adr_data_t *get_prop(xmlNode *node, const char *name); |
|
51 static adr_data_t *file_to_token(rad_locale_t *rlocale, const char *pname, |
|
52 char *file); |
|
53 static conerr_t token_to_file(adr_data_t *t, char **f); |
|
54 static adr_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 adr_data_t *farray, char *file); |
|
58 static adr_data_t *read_panel_path(rad_locale_t *rlocale, const char *path); |
|
59 static adr_data_t *read_panel(const char *locale, const char *pname); |
|
60 |
|
61 /* |
|
62 * Static data |
|
63 */ |
|
64 |
|
65 static xmlDtd *dtd; |
|
66 |
|
67 /* |
|
68 * Static functions |
|
69 */ |
|
70 |
|
71 static int |
|
72 md5_file(const char *fname, unsigned char *hash) |
|
73 { |
|
74 int fd, len = -1; |
|
75 MD5_CTX ctx; |
|
76 |
|
77 char *buffer = malloc(HASH_BSIZE); |
|
78 if (buffer == NULL) |
|
79 return (-1); |
|
80 |
|
81 if ((fd = open(fname, O_RDONLY)) == -1) { |
|
82 free(buffer); |
|
83 return (-1); |
|
84 } |
|
85 |
|
86 if (MD5_Init(&ctx) == 0) |
|
87 goto out; |
|
88 |
|
89 while ((len = read(fd, buffer, HASH_BSIZE)) > 0) |
|
90 if (MD5_Update(&ctx, buffer, len) == 0) { |
|
91 len = -1; |
|
92 break; |
|
93 } |
|
94 if (MD5_Final(hash, &ctx) == 0) |
|
95 len = -1; |
|
96 |
|
97 out: |
|
98 (void) close(fd); |
|
99 free(buffer); |
|
100 return (len); /* Should be 0 or -1 */ |
|
101 } |
|
102 |
|
103 /* |
|
104 * Seems like overkill, but it's better than mixing xml-allocated |
|
105 * and other strings. |
|
106 */ |
|
107 static adr_data_t * |
|
108 get_text(xmlDoc *doc, xmlNode *node) |
|
109 { |
|
110 adr_data_t *result; |
|
111 xmlChar *d = xmlNodeListGetString(doc, node, 1); |
|
112 if (d == NULL) |
|
113 return (NULL); |
|
114 |
|
115 result = adr_data_new_string((char *)d, LT_COPY); |
|
116 xmlFree(d); |
|
117 return (result); |
|
118 } |
|
119 |
|
120 static char * |
|
121 get_text_str(xmlDoc *doc, xmlNode *node) |
|
122 { |
|
123 char *result; |
|
124 xmlChar *d = xmlNodeListGetString(doc, node, 1); |
|
125 if (d == NULL) |
|
126 return (NULL); |
|
127 |
|
128 result = strdup((char *)d); |
|
129 xmlFree(d); |
|
130 return (result); |
|
131 } |
|
132 |
|
133 static adr_data_t * |
|
134 get_prop(xmlNode *node, const char *name) |
|
135 { |
|
136 adr_data_t *result; |
|
137 xmlChar *d = xmlGetProp(node, (xmlChar *)name); |
|
138 if (d == NULL) |
|
139 return (NULL); |
|
140 |
|
141 result = adr_data_new_string((char *)d, LT_COPY); |
|
142 xmlFree(d); |
|
143 return (result); |
|
144 } |
|
145 |
|
146 static adr_data_t * |
|
147 file_to_token(rad_locale_t *rlocale, const char *pname, char *file) |
|
148 { |
|
149 int llen = strlen(rlocale->locale); |
|
150 int plen = strlen(pname); |
|
151 int flen = strlen(file); |
|
152 int tokenlen = llen + 1 + plen + 1 + flen + 1; |
|
153 |
|
154 char *token = rad_zalloc(tokenlen); |
|
155 if (token == NULL) { |
|
156 return (NULL); |
|
157 } |
|
158 |
|
159 char *p = token; |
|
160 (void) strcpy(p, rlocale->locale); |
|
161 p += llen + 1; |
|
162 (void) strcpy(p, pname); |
|
163 p += plen + 1; |
|
164 (void) strcpy(p, file); |
|
165 |
|
166 return (adr_data_new_opaque(token, tokenlen, LT_FREE)); |
|
167 } |
|
168 |
|
169 static conerr_t |
|
170 token_to_file(adr_data_t *t, char **f) |
|
171 { |
|
172 char *token = adr_data_to_opaque(t); |
|
173 char tokenlen = adr_opaque_size(t); |
|
174 |
|
175 /* Cursory validation */ |
|
176 int nullcnt = 0; |
|
177 for (int i = 0; i < tokenlen; i++) { |
|
178 if (token[i] == '\0') { |
|
179 nullcnt++; |
|
180 } |
|
181 } |
|
182 if (nullcnt != 3 || token[tokenlen - 1] != '\0') { |
|
183 /* Bad token */ |
|
184 return (CE_OBJECT); |
|
185 } |
|
186 |
|
187 char *locale = token; |
|
188 char *pname = locale + strlen(locale) + 1; |
|
189 char *file = pname + strlen(pname) + 1; |
|
190 |
|
191 adr_data_t *panel = read_panel(locale, pname); |
|
192 if (panel == NULL) { |
|
193 /* Bad panel */ |
|
194 return (CE_OBJECT); |
|
195 } |
|
196 |
|
197 adr_data_t *resources = adr_struct_get(panel, "resourceDescriptors"); |
|
198 static const char * const path[] = { "file", NULL }; |
|
199 int index = adr_array_search(resources, file, path); |
|
200 adr_data_free(panel); |
|
201 if (index == -1) { |
|
202 /* Bad file */ |
|
203 return (CE_OBJECT); |
|
204 } |
|
205 |
|
206 *f = strdup(file); |
|
207 if (*f == NULL) { |
|
208 return (CE_NOMEM); |
|
209 } |
|
210 |
|
211 return (CE_OK); |
|
212 } |
|
213 |
|
214 static adr_data_t * |
|
215 create_resource(rad_locale_t *rlocale, const char *pname, char *file) |
|
216 { |
|
217 unsigned char hbuf[MD5_DIGEST_LENGTH]; |
|
218 if (md5_file(file, hbuf) != 0) { |
|
219 return (NULL); |
|
220 } |
|
221 |
|
222 adr_data_t *result = adr_data_new_struct(&t__ResourceDescriptor); |
|
223 adr_struct_set(result, "token", file_to_token(rlocale, pname, file)); |
|
224 adr_struct_set(result, "file", adr_data_new_string(file, LT_COPY)); |
|
225 adr_struct_set(result, "hashAlgorithm", |
|
226 adr_data_new_string("MD5", LT_CONST)); |
|
227 adr_struct_set(result, "hash", |
|
228 adr_data_new_opaque(hbuf, MD5_DIGEST_LENGTH, LT_COPY)); |
|
229 |
|
230 return (result); |
|
231 } |
|
232 |
|
233 static void |
|
234 add_localized(rad_locale_t *rlocale, const char *pname, adr_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) adr_array_add(farray, |
|
279 create_resource(rlocale, pname, |
|
280 l10njar)); |
|
281 } |
|
282 } |
|
283 } |
|
284 } |
|
285 (void) adr_array_add(farray, create_resource(rlocale, pname, file)); |
|
286 } |
|
287 |
|
288 static adr_data_t * |
|
289 read_panel_path(rad_locale_t *rlocale, const char *path) |
|
290 { |
|
291 xmlParserCtxt *ctx; |
|
292 xmlValidCtxt *vctx; |
|
293 xmlDoc *doc; |
|
294 adr_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 = adr_data_new_struct(&t__CustomPanel); |
|
324 adr_struct_set(panel, "locale", |
|
325 adr_data_new_string(rlocale->locale, LT_COPY)); |
|
326 |
|
327 adr_data_t *pname = get_prop(root, "name"); |
|
328 adr_struct_set(panel, "name", pname); |
|
329 |
|
330 adr_data_t *farray = |
|
331 adr_data_new_array(&t_array__ResourceDescriptor, 1); |
|
332 adr_struct_set(panel, "resourceDescriptors", farray); |
|
333 |
|
334 char *aroot = NULL; |
|
335 for (xmlNode *np = root->children; np != NULL; np = np->next) { |
|
336 if (np->type != XML_ELEMENT_NODE) |
|
337 continue; |
|
338 if (strcmp((const char *)np->name, "mainclass") == 0) { |
|
339 adr_data_t *mc = get_text(doc, np->children); |
|
340 adr_struct_set(panel, "panelDescriptorClassName", mc); |
|
341 } else if (strcmp((const char *)np->name, "approot") == 0) { |
|
342 if (aroot != NULL) |
|
343 continue; /* schema violation */ |
|
344 aroot = get_text_str(doc, np->children); |
|
345 } else if (strcmp((const char *)np->name, "file") == 0) { |
|
346 char *file = get_text_str(doc, np->children); |
|
347 if (file == NULL) { |
|
348 rad_log(RL_WARN, |
|
349 "Empty <file> declaration within %s\n", |
|
350 path); |
|
351 continue; |
|
352 } |
|
353 |
|
354 if (aroot == NULL) { |
|
355 rad_log(RL_WARN, "App root not specified\n"); |
|
356 continue; |
|
357 } |
|
358 |
|
359 char full[PATH_MAX]; |
|
360 (void) snprintf(full, RAD_COUNT(full), "%s/%s", aroot, |
|
361 file); |
|
362 free(file); |
|
363 |
|
364 add_localized(rlocale, adr_data_to_string(pname), |
|
365 farray, full); |
|
366 } |
|
367 } |
|
368 if (aroot != NULL) |
|
369 free(aroot); |
|
370 out: |
|
371 xmlFreeValidCtxt(vctx); |
|
372 xmlFreeDoc(doc); |
|
373 xmlFreeParserCtxt(ctx); |
|
374 |
|
375 return (adr_data_purify_deep(panel)); |
|
376 } |
|
377 |
|
378 static adr_data_t * |
|
379 read_panel(const char *locale, const char *pname) |
|
380 { |
|
381 rad_locale_t *rlocale; |
|
382 if (rad_locale_parse(locale, &rlocale) != 0) { |
|
383 return (NULL); |
|
384 } |
|
385 |
|
386 char path[PATH_MAX]; |
|
387 (void) snprintf(path, RAD_COUNT(path), "%s/%s.xml", PANELDESCDIR, |
|
388 pname); |
|
389 adr_data_t *panel = read_panel_path(rlocale, path); |
|
390 |
|
391 if (panel != NULL) { |
|
392 /* Sanity check - ensure panel @name matches file name */ |
|
393 adr_data_t *nameattr = adr_struct_get(panel, "name"); |
|
394 if (strcmp(adr_data_to_string(nameattr), pname) != 0) { |
|
395 adr_data_free(panel); |
|
396 panel = NULL; |
|
397 } |
|
398 } |
|
399 |
|
400 rad_locale_free(rlocale); |
|
401 |
|
402 return (panel); |
|
403 } |
|
404 |
|
405 /* |
|
406 * Extern functions |
|
407 */ |
|
408 |
|
409 int |
|
410 _rad_init(void) |
|
411 { |
|
412 dtd = xmlParseDTD(NULL, |
|
413 (xmlChar *)"/usr/share/lib/xml/dtd/vpanel.dtd.1"); |
|
414 |
|
415 adr_name_t *aname = adr_name_vcreate(MOD_DOMAIN, 1, "type", "Panel"); |
|
416 conerr_t cerr = rad_cont_insert_singleton(rad_container, aname, |
|
417 &modinfo, &interface_Panel_svr); |
|
418 adr_name_rele(aname); |
|
419 if (cerr != CE_OK) { |
|
420 rad_log(RL_ERROR, "(mod_panels) failed to insert Panel"); |
|
421 return (-1); |
|
422 } |
|
423 |
|
424 return (0); |
|
425 } |
|
426 |
|
427 /* |
|
428 * _rad_fini is called by the RAD daemon when the module is unloaded. Any |
|
429 * module finalisation is completed here. |
|
430 */ |
|
431 /*ARGSUSED*/ |
|
432 void |
|
433 _rad_fini(void *unused) |
|
434 { |
|
435 } |
|
436 |
|
437 /* ARGSUSED */ |
|
438 conerr_t |
|
439 interface_Panel_invoke_getPanel(rad_instance_t *inst, adr_method_t *meth, |
|
440 adr_data_t **ret, adr_data_t **args, int count, adr_data_t **error) |
|
441 { |
|
442 const char *pname = adr_data_to_string(args[0]); |
|
443 const char *locale = args[1] == NULL ? NULL : |
|
444 adr_data_to_string(args[1]); |
|
445 |
|
446 adr_data_t *panel = read_panel(locale, pname); |
|
447 if (panel == NULL) { |
|
448 /* |
|
449 * Could be a memory or system error, but more likely an invalid |
|
450 * name was specified. |
|
451 */ |
|
452 return (CE_OBJECT); |
|
453 } |
|
454 *ret = panel; |
|
455 |
|
456 return (CE_OK); |
|
457 } |
|
458 |
|
459 /* ARGSUSED */ |
|
460 conerr_t |
|
461 interface_Panel_read_panelNames(rad_instance_t *inst, adr_attribute_t *attr, |
|
462 adr_data_t **data, adr_data_t **error) |
|
463 { |
|
464 adr_data_t *array = adr_data_new_array(&adr_t_array_string, 0); |
|
465 if (array == NULL) { |
|
466 return (CE_NOMEM); |
|
467 } |
|
468 |
|
469 DIR *d; |
|
470 if ((d = opendir(PANELDESCDIR)) == NULL) { |
|
471 if (errno == ENOENT) { |
|
472 return (CE_OK); |
|
473 } |
|
474 return (CE_SYSTEM); |
|
475 } |
|
476 |
|
477 struct dirent *ent; |
|
478 while ((ent = readdir(d)) != NULL) { |
|
479 char *ext = ".xml"; |
|
480 size_t len = strlen(ent->d_name) - strlen(ext); |
|
481 if (len < 1 || strcmp(ent->d_name + len, ext) != 0) { |
|
482 continue; |
|
483 } |
|
484 (void) adr_array_add(array, |
|
485 adr_data_new_nstring(ent->d_name, len)); |
|
486 } |
|
487 |
|
488 (void) closedir(d); |
|
489 *data = adr_data_purify(array); |
|
490 |
|
491 return (*data == NULL ? CE_NOMEM : CE_OK); |
|
492 } |
|
493 |
|
494 /* ARGSUSED */ |
|
495 conerr_t |
|
496 interface_Panel_invoke_getResource(rad_instance_t *inst, adr_method_t *meth, |
|
497 adr_data_t **ret, adr_data_t **args, int count, adr_data_t **error) |
|
498 { |
|
499 char *file; |
|
500 conerr_t result = token_to_file(args[0], &file); |
|
501 if (result != CE_OK) { |
|
502 return (result); |
|
503 } |
|
504 |
|
505 struct stat st; |
|
506 if (stat(file, &st) != 0) { |
|
507 free(file); |
|
508 return (CE_OBJECT); |
|
509 } |
|
510 |
|
511 char *buffer = malloc(st.st_size); |
|
512 if (buffer == NULL) { |
|
513 free(file); |
|
514 return (CE_NOMEM); |
|
515 } |
|
516 |
|
517 int fd = open(file, O_RDONLY); |
|
518 free(file); |
|
519 if (fd == -1) { |
|
520 free(buffer); |
|
521 return (CE_PRIV); |
|
522 } |
|
523 |
|
524 if (read(fd, buffer, st.st_size) != st.st_size) { |
|
525 (void) close(fd); |
|
526 free(buffer); |
|
527 return (CE_SYSTEM); |
|
528 } |
|
529 |
|
530 (void) close(fd); |
|
531 |
|
532 *ret = adr_data_new_opaque(buffer, st.st_size, LT_FREE); |
|
533 return (*ret == NULL ? CE_NOMEM : CE_OK); |
|
534 } |
|