1 /*- 2 * Copyright (c) 2006 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * This software was developed by Robert Watson for the TrustedBSD Project. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $P4: //depot/projects/trustedbsd/openbsm/bin/auditfilterd/auditfilterd_conf.c#5 $ 29 */ 30 31 /* 32 * Configuration file parser for auditfilterd. The configuration file is a 33 * very simple format, similar to other BSM configuration files, consisting 34 * of configuration entries of one line each. The configuration function is 35 * aware of previous runs, and will update the current configuration as 36 * needed. 37 * 38 * Modules are in one of two states: attached, or detached. If attach fails, 39 * detach is not called because it was not attached. If a module is attached 40 * and a call to its reinit method fails, we will detach it. 41 * 42 * Modules are passed a (void *) reference to their configuration state so 43 * that they may pass this into any common APIs we provide which may rely on 44 * that state. Currently, the only such API is the cookie API, which allows 45 * per-instance state to be maintained by a module. In the future, this will 46 * also be used to support per-instance preselection state. 47 */ 48 49 #include <sys/types.h> 50 51 #include <config/config.h> 52 #ifdef HAVE_FULL_QUEUE_H 53 #include <sys/queue.h> 54 #else 55 #include <compat/queue.h> 56 #endif 57 58 #include <bsm/libbsm.h> 59 #include <bsm/audit_filter.h> 60 61 #include <dlfcn.h> 62 #include <err.h> 63 #include <errno.h> 64 #include <limits.h> 65 #include <stdio.h> 66 #include <stdlib.h> 67 #include <string.h> 68 69 #include "auditfilterd.h" 70 71 /* 72 * Free an individual auditfilter_module structure. Will not shut down the 73 * module, just frees the memory. Does so conditional on pointers being 74 * non-NULL so that it can be used on partially allocated structures. 75 */ 76 static void 77 auditfilter_module_free(struct auditfilter_module *am) 78 { 79 80 if (am->am_modulename != NULL) 81 free(am->am_modulename); 82 if (am->am_arg_buffer != NULL) 83 free(am->am_arg_buffer); 84 if (am->am_argv != NULL) 85 free(am->am_argv); 86 } 87 88 /* 89 * Free all memory associated with an auditfilter_module list. Does not 90 * dlclose() or shut down the modules, just free the memory. Use 91 * auditfilter_module_list_detach() for that, if required. 92 */ 93 static void 94 auditfilter_module_list_free(struct auditfilter_module_list *list) 95 { 96 struct auditfilter_module *am; 97 98 while (!(TAILQ_EMPTY(list))) { 99 am = TAILQ_FIRST(list); 100 TAILQ_REMOVE(list, am, am_list); 101 auditfilter_module_free(am); 102 } 103 } 104 105 /* 106 * Detach an attached module from an auditfilter_module structure. Does not 107 * free the data structure itself. 108 */ 109 static void 110 auditfilter_module_detach(struct auditfilter_module *am) 111 { 112 113 if (am->am_detach != NULL) 114 am->am_detach(am); 115 am->am_cookie = NULL; 116 (void)dlclose(am->am_dlhandle); 117 am->am_dlhandle = NULL; 118 } 119 120 /* 121 * Walk an auditfilter_module list, detaching each module. Intended to be 122 * combined with auditfilter_module_list_free(). 123 */ 124 static void 125 auditfilter_module_list_detach(struct auditfilter_module_list *list) 126 { 127 struct auditfilter_module *am; 128 129 TAILQ_FOREACH(am, list, am_list) 130 auditfilter_module_detach(am); 131 } 132 133 /* 134 * Given a filled out auditfilter_module, use dlopen() and dlsym() to attach 135 * the module. If we fail, leave fields in the state we found them. 136 * 137 * XXXRW: Need a better way to report errors. 138 */ 139 static int 140 auditfilter_module_attach(struct auditfilter_module *am) 141 { 142 143 am->am_dlhandle = dlopen(am->am_modulename, RTLD_NOW); 144 if (am->am_dlhandle == NULL) { 145 warnx("auditfilter_module_attach: %s: %s", am->am_modulename, 146 dlerror()); 147 return (-1); 148 } 149 150 /* 151 * Not implementing these is not considered a failure condition, 152 * although we might want to consider warning if obvious stuff is 153 * not implemented, such as am_record. 154 */ 155 am->am_attach = dlsym(am->am_dlhandle, AUDIT_FILTER_ATTACH_STRING); 156 am->am_reinit = dlsym(am->am_dlhandle, AUDIT_FILTER_REINIT_STRING); 157 am->am_record = dlsym(am->am_dlhandle, AUDIT_FILTER_RECORD_STRING); 158 am->am_rawrecord = dlsym(am->am_dlhandle, 159 AUDIT_FILTER_RAWRECORD_STRING); 160 am->am_detach = dlsym(am->am_dlhandle, AUDIT_FILTER_DETACH_STRING); 161 162 if (am->am_attach != NULL) { 163 if (am->am_attach(am, am->am_argc, am->am_argv) 164 != AUDIT_FILTER_SUCCESS) { 165 warnx("auditfilter_module_attach: %s: failed", 166 am->am_modulename); 167 dlclose(am->am_dlhandle); 168 am->am_dlhandle = NULL; 169 am->am_cookie = NULL; 170 am->am_attach = NULL; 171 am->am_reinit = NULL; 172 am->am_record = NULL; 173 am->am_rawrecord = NULL; 174 am->am_detach = NULL; 175 return (-1); 176 } 177 } 178 179 return (0); 180 } 181 182 /* 183 * When the arguments for a module are changed, we notify the module through 184 * a call to its reinit method, if any. Return 0 on success, or -1 on 185 * failure. 186 */ 187 static int 188 auditfilter_module_reinit(struct auditfilter_module *am) 189 { 190 191 if (am->am_reinit == NULL) 192 return (0); 193 194 if (am->am_reinit(am, am->am_argc, am->am_argv) != 195 AUDIT_FILTER_SUCCESS) { 196 warnx("auditfilter_module_reinit: %s: failed", 197 am->am_modulename); 198 return (-1); 199 } 200 201 return (0); 202 } 203 204 /* 205 * Given a configuration line, generate an auditfilter_module structure that 206 * describes it; caller will not pass comments in, so they are not looked 207 * for. Do not attempt to instantiate it. Will destroy the contents of 208 * 'buffer'. 209 * 210 * Configuration lines consist of two parts: the module name and arguments 211 * separated by a ':', and then a ','-delimited list of arguments. 212 * 213 * XXXRW: Need to decide where to send the warning output -- stderr for now. 214 */ 215 struct auditfilter_module * 216 auditfilter_module_parse(const char *filename, int linenumber, char *buffer) 217 { 218 char *arguments, *module, **ap; 219 struct auditfilter_module *am; 220 221 am = malloc(sizeof(*am)); 222 if (am == NULL) { 223 warn("auditfilter_module_parse: %s:%d", filename, linenumber); 224 return (NULL); 225 } 226 bzero(am, sizeof(*am)); 227 228 /* 229 * First, break out the module and arguments strings. We look for 230 * one extra argument to make sure there are no more :'s in the line. 231 * That way, we prevent modules from using argument strings that, in 232 * the future, may cause problems for adding additional columns. 233 */ 234 arguments = buffer; 235 module = strsep(&arguments, ":"); 236 if (module == NULL || arguments == NULL) { 237 warnx("auditfilter_module_parse: %s:%d: parse error", 238 filename, linenumber); 239 return (NULL); 240 } 241 242 am->am_modulename = strdup(module); 243 if (am->am_modulename == NULL) { 244 warn("auditfilter_module_parse: %s:%d", filename, linenumber); 245 auditfilter_module_free(am); 246 return (NULL); 247 } 248 249 am->am_arg_buffer = strdup(buffer); 250 if (am->am_arg_buffer == NULL) { 251 warn("auditfilter_module_parse: %s:%d", filename, linenumber); 252 auditfilter_module_free(am); 253 return (NULL); 254 } 255 256 /* 257 * Now, break out the arguments string into a series of arguments. 258 * This is a bit more complicated, and requires cleanup if things go 259 * wrong. 260 */ 261 am->am_argv = malloc(sizeof(char *) * AUDITFILTERD_CONF_MAXARGS); 262 if (am->am_argv == NULL) { 263 warn("auditfilter_module_parse: %s:%d", filename, linenumber); 264 auditfilter_module_free(am); 265 return (NULL); 266 } 267 bzero(am->am_argv, sizeof(char *) * AUDITFILTERD_CONF_MAXARGS); 268 am->am_argc = 0; 269 for (ap = am->am_argv; (*ap = strsep(&arguments, " \t")) != NULL;) { 270 if (**ap != '\0') { 271 am->am_argc++; 272 if (++ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS]) 273 break; 274 } 275 } 276 if (ap >= &am->am_argv[AUDITFILTERD_CONF_MAXARGS]) { 277 warnx("auditfilter_module_parse: %s:%d: too many arguments", 278 filename, linenumber); 279 auditfilter_module_free(am); 280 return (NULL); 281 } 282 283 return (am); 284 } 285 286 /* 287 * Read a configuration file, and populate 'list' with the configuration 288 * lines. Does not attempt to instantiate the configuration, just read it 289 * into a useful set of data structures. 290 */ 291 static int 292 auditfilterd_conf_read(const char *filename, FILE *fp, 293 struct auditfilter_module_list *list) 294 { 295 int error, linenumber, syntaxerror; 296 struct auditfilter_module *am; 297 char buffer[LINE_MAX]; 298 299 syntaxerror = 0; 300 linenumber = 0; 301 while (!feof(fp) && !ferror(fp)) { 302 if (fgets(buffer, LINE_MAX, fp) == NULL) 303 break; 304 linenumber++; 305 if (buffer[0] == '#' || strlen(buffer) < 1) 306 continue; 307 buffer[strlen(buffer)-1] = '\0'; 308 am = auditfilter_module_parse(filename, linenumber, buffer); 309 if (am == NULL) { 310 syntaxerror = 1; 311 break; 312 } 313 TAILQ_INSERT_HEAD(list, am, am_list); 314 } 315 316 /* 317 * File I/O error. 318 */ 319 if (ferror(fp)) { 320 error = errno; 321 auditfilter_module_list_free(list); 322 errno = error; 323 return (-1); 324 } 325 326 /* 327 * Syntax error. 328 */ 329 if (syntaxerror) { 330 auditfilter_module_list_free(list); 331 errno = EINVAL; 332 return (-1); 333 } 334 return (0); 335 } 336 337 /* 338 * Apply changes necessary to bring a new configuration into force. The new 339 * configuration data is passed in, and the current configuration is updated 340 * to match it. The contents of 'list' are freed or otherwise disposed of 341 * before return. 342 * 343 * The algorithms here are not very efficient, but this is an infrequent 344 * operation on very short lists. 345 */ 346 static void 347 auditfilterd_conf_apply(struct auditfilter_module_list *list) 348 { 349 struct auditfilter_module *am1, *am2, *am_tmp; 350 int argc_tmp, found; 351 char **argv_tmp; 352 353 /* 354 * First, remove remove and detach any entries that appear in the 355 * current configuration, but not the new configuration. 356 */ 357 TAILQ_FOREACH_SAFE(am1, &filter_list, am_list, am_tmp) { 358 found = 0; 359 TAILQ_FOREACH(am2, list, am_list) { 360 if (strcmp(am1->am_modulename, am2->am_modulename) 361 == 0) { 362 found = 1; 363 break; 364 } 365 } 366 if (found) 367 continue; 368 369 /* 370 * am1 appears in filter_list, but not the new list, detach 371 * and free the module. 372 */ 373 warnx("detaching module %s", am1->am_modulename); 374 TAILQ_REMOVE(&filter_list, am1, am_list); 375 auditfilter_module_detach(am1); 376 auditfilter_module_free(am1); 377 } 378 379 /* 380 * Next, update the configuration of any modules that appear in both 381 * lists. We do this by swapping the two argc and argv values and 382 * freeing the new one, rather than detaching the old one and 383 * attaching the new one. That way module state is preserved. 384 */ 385 TAILQ_FOREACH(am1, &filter_list, am_list) { 386 found = 0; 387 TAILQ_FOREACH(am2, list, am_list) { 388 if (strcmp(am1->am_modulename, am2->am_modulename) 389 == 0) { 390 found = 1; 391 break; 392 } 393 } 394 if (!found) 395 continue; 396 397 /* 398 * Swap the arguments. 399 */ 400 argc_tmp = am1->am_argc; 401 argv_tmp = am1->am_argv; 402 am1->am_argc = am2->am_argc; 403 am1->am_argv = am2->am_argv; 404 am2->am_argc = argc_tmp; 405 am2->am_argv = argv_tmp; 406 407 /* 408 * The reinit is a bit tricky: if reinit fails, we actually 409 * remove the old entry and detach that, as we don't allow 410 * running modules to be out of sync with the configuration 411 * file. 412 */ 413 warnx("reiniting module %s", am1->am_modulename); 414 if (auditfilter_module_reinit(am1) != 0) { 415 warnx("reinit failed for module %s, detaching", 416 am1->am_modulename); 417 TAILQ_REMOVE(&filter_list, am1, am_list); 418 auditfilter_module_detach(am1); 419 auditfilter_module_free(am1); 420 } 421 422 /* 423 * Free the entry from the new list, which will discard the 424 * old arguments. No need to detach, as it was never 425 * attached in the first place. 426 */ 427 TAILQ_REMOVE(list, am2, am_list); 428 auditfilter_module_free(am2); 429 } 430 431 /* 432 * Finally, attach any new entries that don't appear in the old 433 * configuration, and if they attach successfully, move them to the 434 * real configuration list. 435 */ 436 TAILQ_FOREACH(am1, list, am_list) { 437 found = 0; 438 TAILQ_FOREACH(am2, &filter_list, am_list) { 439 if (strcmp(am1->am_modulename, am2->am_modulename) 440 == 0) { 441 found = 1; 442 break; 443 } 444 } 445 if (found) 446 continue; 447 /* 448 * Attach the entry. If it succeeds, add to filter_list, 449 * otherwise, free. No need to detach if attach failed. 450 */ 451 warnx("attaching module %s", am1->am_modulename); 452 TAILQ_REMOVE(list, am1, am_list); 453 if (auditfilter_module_attach(am1) != 0) { 454 warnx("attaching module %s failed", 455 am1->am_modulename); 456 auditfilter_module_free(am1); 457 } else 458 TAILQ_INSERT_HEAD(&filter_list, am1, am_list); 459 } 460 461 if (TAILQ_FIRST(list) != NULL) 462 warnx("auditfilterd_conf_apply: new list not empty\n"); 463 } 464 465 /* 466 * Read the new configuration file into a local list. If the configuration 467 * file is parsed OK, then apply the changes. 468 */ 469 int 470 auditfilterd_conf(const char *filename, FILE *fp) 471 { 472 struct auditfilter_module_list list; 473 474 TAILQ_INIT(&list); 475 if (auditfilterd_conf_read(filename, fp, &list) < 0) 476 return (-1); 477 478 auditfilterd_conf_apply(&list); 479 480 return (0); 481 } 482 483 /* 484 * Detach and free all active filter modules for daemon shutdown. 485 */ 486 void 487 auditfilterd_conf_shutdown(void) 488 { 489 490 auditfilter_module_list_detach(&filter_list); 491 auditfilter_module_list_free(&filter_list); 492 } 493 494 /* 495 * APIs to allow modules to query and set their per-instance cookie. 496 */ 497 void 498 audit_filter_getcookie(void *instance, void **cookie) 499 { 500 struct auditfilter_module *am; 501 502 am = (struct auditfilter_module *)instance; 503 *cookie = am->am_cookie; 504 } 505 506 void 507 audit_filter_setcookie(void *instance, void *cookie) 508 { 509 struct auditfilter_module *am; 510 511 am = (struct auditfilter_module *)instance; 512 am->am_cookie = cookie; 513 } 514