/* ELAPI Audit log dispatcher interface implementation. Copyright (C) Dmitri Pal 2009 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "elapi_debug.h" #include "elapi_dispatcher.h" #include "elapi_collection.h" #include "elapi_sink.h" char sink_collection[] = "sinks"; char def_application_name[] = "default"; char *default_sinks[] = { "remote","altremote","kernel","syslog","db","file","failover","stderr", NULL }; /* Structure to pass data from logging function to sinks */ struct sink_context { struct collection_item *event; struct dispatcher_handle *handle; char *format; char *previous; int previous_status; }; /* The structure to hold a command and a result of the command execution */ struct get_sink { int action; int found; }; #ifdef ELAPI_LOG_DEBUG #define DEBUG_SINK(sink_data) print_sink(sink_data); #else #define DEBUG_SINK(sink_data) ; #endif /* Debug function */ static void print_sink(struct sink_descriptor *sink_data) { DEBUG_NUMBER("SINK data address",sink_data); DEBUG_NUMBER("SINK data DBLOCK address",&(sink_data->dblock)); DEBUG_NUMBER("SINK data DBLOCK internals",(&(sink_data->dblock))->internal_data); } static void init_sink(struct sink_descriptor *sink_data, int status) { int error; struct timeval tv; DEBUG_STRING("init_sink","Entry"); DEBUG_STRING("In init_sink application name:",sink_data->dblock.appname); /* Pass in the data block storage */ error = (sink_data->sink_cpb_block).init_cb(&(sink_data->dblock)); DEBUG_NUMBER("DBLOCK in init_sink",&(sink_data->dblock)); if(error != EOK) { DEBUG_NUMBER("Failed to initialize the sink:",error); (void)gettimeofday(&tv,NULL); sink_data->suspended = ELAPI_SINK_SUSPENDED; sink_data->lasttry = tv.tv_sec; } else { DEBUG_NUMBER("Initialized the sink. Status set to:",status); sink_data->suspended = status; sink_data->lasttry = 0; } DEBUG_SINK(sink_data); DEBUG_STRING("init_sink","Exit"); } static void wash_sink(struct sink_descriptor *sink_data) { int error; struct timeval tv; DEBUG_STRING("wash_sink","Entry"); DEBUG_NUMBER("Descriptor",sink_data); DEBUG_NUMBER("Capability",&(sink_data->sink_cpb_block)); DEBUG_NUMBER("Callback",(sink_data->sink_cpb_block).close_cb); DEBUG_NUMBER("DBLOCK in wash",&(sink_data->dblock)); /* Pass in the data block storage */ (sink_data->sink_cpb_block).close_cb(&(sink_data->dblock)); DEBUG_STRING("wash_sink","Exit"); } /* Function to load sink library */ static int load_sink(struct sink_descriptor *sink_data,char *sink_name) { char sink_lib_name[SINK_LIB_NAME_SIZE]; capability_fn get_lib_info; void *lib_handle; char *lib_error; int error = EOK; DEBUG_STRING("load_sink","Entry"); sprintf(sink_lib_name,SINK_NAME_TEMPLATE,sink_name); DEBUG_STRING("Name of the library to load",sink_lib_name); /* Load library */ sink_data->lib_handle = dlopen (sink_lib_name, RTLD_LAZY); if (!(sink_data->lib_handle)) { DEBUG_STRING("Dlopen returned error",dlerror()); return ELIBACC; } /* Clear any existing error */ dlerror(); /* Get addres to the main entry point */ get_lib_info = (capability_fn)(dlsym(sink_data->lib_handle, SINK_ENTRY_POINT)); if ((lib_error = dlerror()) != NULL) { DEBUG_STRING("Dlsym returned error",lib_error); dlclose(sink_data->lib_handle); return ELIBACC; } /* Init data */ get_lib_info(&(sink_data->sink_cpb_block)); DEBUG_NUMBER("Instance:",sink_data->sink_cpb_block.instance); DEBUG_NUMBER("Flags:",sink_data->sink_cpb_block.flags); /* Check if it is a single mode sink */ if((sink_data->sink_cpb_block.instance > 0) && ((sink_data->sink_cpb_block.flags & SINK_FLAG_LOAD_SINGLE) > 0)) { /* This is the sink that should be loaded only once */ /* We should fail and not even create the sink */ DEBUG_STRING("Attempt to load the sink twice",""); dlclose(sink_data->lib_handle); return ELIBEXEC; } /* Call library initialization function */ init_sink(sink_data,ELAPI_SINK_OK); /* If we return ELIBACC it would indicate that the desired library is missing */ DEBUG_STRING("load_sink","Returning Success"); return EOK; } /* Function to add a sink in the list */ static int add_sink_to_list(struct collection_item *sink_list, char *sink, char *appname) { int error = EOK; int found = 0; struct sink_descriptor sink_data; DEBUG_STRING("add_sink_to_list","Entry"); error = is_item_in_collection(sink_list, sink, ELAPI_TYPE_ANY, ELAPI_TRAVERSE_DEFAULT, &found); if(error) { DEBUG_NUMBER("Search returned error",error); return error; } /* Check if it was found */ if(found) { DEBUG_STRING("Attempt to add an exiting sink.",""); return EINVAL; } /* Save the pointer to application name into the sink's data block */ sink_data.dblock.appname = appname; DEBUG_STRING("add_sink_to_list - saving appname:",sink_data.dblock.appname); /* Try to load the sink library */ error = load_sink(&sink_data,sink); if(error != 0) { DEBUG_NUMBER("Failed to load sink",error); return error; } /* We got a valid sink so add it to the collection */ error=add_binary_property(sink_list,NULL, sink,(void *)(&sink_data), sizeof(struct sink_descriptor)); if(error != 0) { DEBUG_NUMBER("Failed to add sink data as property",error); return error; } DEBUG_NUMBER("add_sink_to_list returning",error); return error; } /* Handler to change the sink status data */ static int update_sink_handle(char *sink, int sink_len, int type, void *data, int length, void *passed_data, int *stop) { int error = EOK; struct sink_descriptor *sink_data; struct get_sink *get_sink_op; DEBUG_STRING("update_sink_handler","Entry."); sink_data = (struct sink_descriptor *)(data); get_sink_op = (struct get_sink *)(passed_data); switch(get_sink_op->action) { case ELAPI_SINK_ACTION_DISABLE: DEBUG_STRING("Disabling sink:",sink); wash_sink(sink_data); sink_data->suspended = ELAPI_SINK_DISABLED; break; case ELAPI_SINK_ACTION_ENABLE: DEBUG_STRING("Enabling sink:",sink); wash_sink(sink_data); /* Init sink will set the correct status */ init_sink(sink_data,ELAPI_SINK_OK); break; case ELAPI_SINK_ACTION_PULSE: DEBUG_STRING("Pulsing sink:",sink); wash_sink(sink_data); init_sink(sink_data,ELAPI_SINK_PULSE); break; default: DEBUG_STRING("update_sink_handler","ERROR Invalid argument"); return EINVAL; } *stop = 1; /* Indicate that we found item */ get_sink_op->found = 1; DEBUG_STRING("update_sink_handler","Return."); return EOK; } /* Handler to clean sinks at the end */ static int close_sink_handler(char *sink, int sink_len, int type, void *data, int length, void *passed_data, int *stop) { int error = EOK; struct sink_descriptor *sink_data; DEBUG_STRING("close_sink_handler","Entry."); DEBUG_STRING("Sink:",sink); if(type == ELAPI_TYPE_COLLECTION) return EOK; sink_data = (struct sink_descriptor *)(data); wash_sink(sink_data); if(sink_data->lib_handle != NULL) { DEBUG_STRING("Closing lib handle",""); dlclose(sink_data->lib_handle); sink_data->lib_handle = NULL; } DEBUG_STRING("close_sink_handler","Return."); return EOK; } /* Handler for logging through the sinks */ static int sink_handler(char *sink, int sink_len, int type, void *data, int length, void *passed_data, int *stop) { struct sink_context *sink_env; struct sink_descriptor *sink_data; int status = 0; int error = EOK; struct timeval tv; DEBUG_STRING("sink_handler","Entry."); DEBUG_STRING("Sink:",sink); sink_env = (struct sink_context *)(passed_data); sink_data = (struct sink_descriptor *)(data); /* When porcessing header just initialize and continue */ if(type == ELAPI_TYPE_COLLECTION) { sink_env->previous = NULL; sink_env->previous_status = 0; return error; } /* Check if the sink is healthy to use */ if(sink_data->suspended!= ELAPI_SINK_OK) { DEBUG_NUMBER("Sink is suspended:",sink_data->suspended); if(sink_data->suspended == ELAPI_SINK_DISABLED) { DEBUG_STRING("Sink is suspended by caller. Skipping sink.",sink); return EOK; } if(sink_data->suspended == ELAPI_SINK_PULSE) { DEBUG_STRING("Sink is suspended by caller for one time. Skipping sink but resetting to Ok.",sink); sink_data->suspended = ELAPI_SINK_OK; return EOK; } if(sink_data->suspended == ELAPI_SINK_SUSPENDED) { DEBUG_STRING("Sink is suspended is it time to retry.",sink); /* Is the sink permanently suspended ? */ if((sink_data->sink_cpb_block).retry_interval == SINK_NEVER_RETRY) { DEBUG_STRING("Sink is suspended forever since the sink tells not to retry.",sink); /* For future: should we delete the sink ?*/ /* IMO we should not becuase the calling * application can potentially do something to fix the issue and * explicitely re-enable the sink after it. */ return EOK; } /* Check interval */ (void)gettimeofday(&tv,NULL); if(difftime(tv.tv_sec,sink_data->lasttry + (sink_data->sink_cpb_block).retry_interval) < 0) { DEBUG_STRING("Sink is suspended is it not time to retry.",sink); return EOK; } /* Time to retry a suspended sink */ init_sink(sink_data,ELAPI_SINK_OK); if(sink_data->suspended == ELAPI_SINK_SUSPENDED) { DEBUG_STRING("Sink is still suspended. Problem still exists.",sink); return EOK; } } else { DEBUG_STRING("Status is invalid for sink.",sink); return EINVAL; } } /* Call router function */ status = (sink_env->handle)->router(sink, sink_env->previous, sink_env->previous_status, sink_env->event, sink_env->format, sink_data, (sink_env->handle)->custom_data, &error); /* Check the status */ switch(status) { case ELAPI_DISPATCHER_SKIP: /* Ignore current sink as if it is abcent */ DEBUG_STRING("Ignoring sink.",sink); /* Do not change suspended status */ /* Do not change previous fields */ break; case ELAPI_DISPATCHER_DONE: /* Sink is done with this event */ DEBUG_STRING("Done with this event.",sink); *stop = 1; sink_data->suspended = ELAPI_SINK_OK; break; case ELAPI_DISPATCHER_NEXT: /* Sink thinks somone else would want to log this event */ DEBUG_STRING("Go to the next sink.",sink); sink_env->previous = sink; sink_env->previous_status = status; sink_data->suspended = ELAPI_SINK_OK; break; case ELAPI_DISPATCHER_ERROR: /* An error occured */ DEBUG_NUMBER("Go to the next sink due to error.",error); /* FIXME: In future may be store the error and log it later * when the sink recovers from failure. * Alternatively may be log it into another sink. */ /* Record the fact that the sink returned error */ sink_env->previous = sink; sink_env->previous_status = status; /* Suspend this sink */ sink_data->suspended = ELAPI_SINK_SUSPENDED; sink_data->lasttry=tv.tv_sec; wash_sink(sink_data); break; default: /* This should not happen - codding error */ DEBUG_STRING("Status is invalid for sink.",sink); return EINVAL; } DEBUG_STRING("sink_handler","Success Exit."); return EOK; } /* Router function returns status not error */ static int default_router(char *sink, char *previous_sink, int previous_status, struct collection_item *event, char *format_string, struct sink_descriptor *sink_data, void *custom_data, int *error) { DEBUG_STRING("default_router","Entry"); /* FIXME * default implementation of the routing function: * Log every event into every facility regardless * of the outcome. */ *error = log_event_to_sink(sink_data,event,format_string,custom_data); DEBUG_STRING("default_router","Returning"); return ELAPI_DISPATCHER_NEXT; } /* ================== SINK LIST MANAGEMENT ======================== */ /* Function to create a list of sinks */ static int construct_sink_list(struct dispatcher_handle *handle) { int error = EOK; char **current_sink; DEBUG_STRING("construct_sink_list","Entry"); /* Allocate collection to store sinks */ error=create_collection(&(handle->sink_list),sink_collection); if(error != 0) { DEBUG_NUMBER("Failed to create sink collection. Error",error); /* No cleanup here. * The calling function will call a cleanup * of the dispatcher as a whole.*/ return error; } current_sink = handle->sinks; handle->sink_counter = 0; /* Add sinks as properties to the sink collection */ while (*current_sink != NULL) { DEBUG_STRING("Current sink",*current_sink); DEBUG_STRING("Will use appname:",handle->appname); /* Load sink */ error = add_sink_to_list(handle->sink_list,*current_sink,handle->appname); if((error != 0) && (error != ELIBACC)) { DEBUG_NUMBER("Failed to add sink",error); /* No cleanup here. */ return error; } handle->sink_counter++; current_sink++; } /* Check if we have any sinks available */ if(handle->sink_counter == 0) { DEBUG_NUMBER("No sinks",""); /* No cleanup here. */ /* Return "Cannot access a needed shared library" */ return ELIBACC; } DEBUG_STRING("construct_sink_list","Returning success"); return EOK; } /* Function to delete sink list collection */ static void delete_sink_list(struct dispatcher_handle *handle) { DEBUG_STRING("delete_sink_list","Entry point"); if(handle->sink_list != (struct collection_item *)(NULL)) { DEBUG_STRING("delete_sink_list","Deleting sink collection"); destroy_collection(handle->sink_list); } DEBUG_STRING("delete_sink_list","Exit"); } /* ========================= MAIN INTERFACE FUNCTIONS ============================ */ /* Function to create an audit dispatcher */ int create_audit_dispatcher(struct dispatcher_handle **dispatcher, const char *appname, char **desired_sinks, event_router_fn desired_router, void *custom_data) { struct dispatcher_handle *handle; int error = EOK; DEBUG_STRING("create_audit_dispatcher","Entry point"); /* Make sure the memory for handle is passed in */ if(dispatcher == (struct dispatcher_handle **)(NULL)) { DEBUG_STRING("create_audit_dispatcher","Invalid parameter."); return EINVAL; } /* Allocate memory */ handle = (struct dispatcher_handle *) malloc(sizeof(struct dispatcher_handle)); if(handle == (struct dispatcher_handle *)(NULL)) { error = errno; DEBUG_NUMBER("Memory allocation failed. Error",error); return error; } /* Save application name in the handle */ if(appname != NULL) handle->appname = strdup(appname); else handle->appname = strdup(def_application_name); DEBUG_STRING("Application name:",handle->appname); /* Check error */ if(handle->appname == NULL) { error = errno; DEBUG_NUMBER("Memory allocation failed. Error",error); return error; } /* Check if there is no desired sinks provided */ if(desired_sinks != (char **)(NULL)) handle->sinks = desired_sinks; else handle->sinks = default_sinks; /* Check the router. If it is empty use the default router */ if(desired_router != (event_router_fn)(NULL)) handle->router = desired_router; else handle->router = default_router; handle->custom_data = custom_data; /* Create the list of sinks */ error = construct_sink_list(handle); if(error != EOK) { DEBUG_NUMBER("Failed to create sink list. Error",error); destroy_audit_dispatcher(handle); return error; } *dispatcher = handle; DEBUG_STRING("create_audit_dispatcher","Returning Success."); return EOK; } /* Function to clean memory associated with the audit dispatcher */ void destroy_audit_dispatcher(struct dispatcher_handle *dispatcher) { DEBUG_STRING("destroy_audit_dispatcher","Entry"); if(dispatcher != (struct dispatcher_handle *)(NULL)) { /* Offload libs */ (void)traverse_collection(dispatcher->sink_list,ELAPI_TRAVERSE_ONELEVEL,close_sink_handler,(void *)(NULL)); DEBUG_STRING("Deleting sink list.",""); delete_sink_list(dispatcher); DEBUG_STRING("Freeing application name.",""); free((void *)(dispatcher->appname)); DEBUG_STRING("Freeing dispatcher.",""); free((void *)(dispatcher)); } DEBUG_STRING("destroy_audit_dispatcher","Exit"); } /* Log evento into a specific sink */ int log_event_to_sink(struct sink_descriptor *sink_data, struct collection_item *event, char *format_string, void *custom_data) { int error = EOK; struct sink_capability *sink_cpb; DEBUG_STRING("log_event_to_sink","Entry"); sink_cpb = &(sink_data->sink_cpb_block); DEBUG_NUMBER("DBLOCK in dispatcher",&(sink_data->dblock)); DEBUG_NUMBER("internal data in dispatcher",(&(sink_data->dblock))->internal_data); DEBUG_SINK(sink_data); /* Format (serialize the event) */ error = sink_cpb->format_cb(&(sink_data->dblock),format_string,event); if(error != EOK) { DEBUG_NUMBER("Format function returned error",error); return error; } /* Submit the event */ error = sink_cpb->submit_cb(&(sink_data->dblock)); DEBUG_NUMBER("Format function returned error",error); /* Clean internal per event data in any case */ sink_cpb->cleanup_cb(&(sink_data->dblock)); DEBUG_STRING("log_event_to_sink","Return"); return error; } /* Function to clean memory associated with the audit dispatcher */ void log_audit_event(struct dispatcher_handle *dispatcher, char *format_str, struct collection_item *event) { struct sink_context sink_env; DEBUG_STRING("log_audit_event","Entry"); if((dispatcher == (struct dispatcher_handle *)(NULL)) || (event == (struct collection_item *)(NULL))) { DEBUG_STRING("log_audit_event","ERROR Invalid argument"); DEBUG_NUMBER("Dispatcher",dispatcher); DEBUG_NUMBER("Event",event); DEBUG_STRING("log_audit_event","ERROR Return"); return; } sink_env.handle = dispatcher; sink_env.event = event; sink_env.format = format_str; /* Logging an event is just iterating through the sinks and calling the sink_handler */ (void)traverse_collection(dispatcher->sink_list,ELAPI_TRAVERSE_ONELEVEL,sink_handler,(void *)(&sink_env)); DEBUG_STRING("log_audit_event","Return"); } /* Managing the sink collection */ int alter_audit_dispatcher(struct dispatcher_handle *dispatcher, char *sink, int action) { int error; struct get_sink get_sink_op; DEBUG_STRING("alter_audit_dispatcher","Entry"); if((dispatcher == (struct dispatcher_handle *)(NULL)) || (sink == (char *)(NULL))) { DEBUG_STRING("log_audit_event","ERROR Invalid argument"); DEBUG_NUMBER("Dispatcher",dispatcher); DEBUG_NUMBER("Sink",sink); DEBUG_STRING("log_audit_event","ERROR Return"); return EINVAL; } switch(action) { case ELAPI_SINK_ACTION_ADD: /* Try to add it and return whatever the attmpt returned */ error = add_sink_to_list(dispatcher->sink_list,sink,dispatcher->appname); DEBUG_NUMBER("alter_audit_dispatcher ADD returning",error); return error; case ELAPI_SINK_ACTION_DELETE: /* Try to delete the sink */ error = get_item_and_do(dispatcher->sink_list, sink, ELAPI_TYPE_ANY, ELAPI_TRAVERSE_DEFAULT, close_sink_handler, NULL); if(error) { DEBUG_NUMBER("alter_audit_dispatcher close sink returning",error); return error; } error = delete_property(dispatcher->sink_list, sink, ELAPI_TYPE_ANY, ELAPI_TRAVERSE_DEFAULT); DEBUG_NUMBER("alter_audit_dispatcher DELETE returning",error); return error; case ELAPI_SINK_ACTION_DISABLE: case ELAPI_SINK_ACTION_ENABLE: case ELAPI_SINK_ACTION_PULSE: /* Try to modify the sink */ get_sink_op.action = action; get_sink_op.found = 0; error = get_item_and_do(dispatcher->sink_list, sink, ELAPI_TYPE_ANY, ELAPI_TRAVERSE_DEFAULT, update_sink_handle, (void *)(&get_sink_op)); if(get_sink_op.found == 0) { DEBUG_STRING("alter_audit_dispatcher DISABLE/ENABLE/PULSE sink not found",""); return EINVAL; } DEBUG_NUMBER("alter_audit_dispatcher DISABLE/ENABLE/PULSE returning",error); return error; default: DEBUG_STRING("alter_audit_dispatcher","Invalid action"); return EINVAL; } /* Unreachable */ }