From mboxrd@z Thu Jan 1 00:00:00 1970 From: Klaus Heinrich Kiwi Subject: [PATCH 06/07][RFC] RACF audit plugin - plugin main code Date: Fri, 28 Sep 2007 10:28:35 -0300 Message-ID: <1190986115.4113.55.camel@klausk.br.ibm.com> References: <1190983565.4113.2.camel@klausk.br.ibm.com> <1190983925.4113.8.camel@klausk.br.ibm.com> <1190984128.4113.12.camel@klausk.br.ibm.com> <1190984843.4113.25.camel@klausk.br.ibm.com> <1190985127.4113.32.camel@klausk.br.ibm.com> <1190985276.4113.35.camel@klausk.br.ibm.com> Reply-To: klausk@br.ibm.com Mime-Version: 1.0 Content-Type: text/plain Content-Transfer-Encoding: 7bit Return-path: In-Reply-To: <1190985276.4113.35.camel@klausk.br.ibm.com> List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-audit-bounces@redhat.com Errors-To: linux-audit-bounces@redhat.com To: Linux-audit@redhat.com List-Id: linux-audit@redhat.com This patch implements the main body for the racf plugin. It uses the auparse_feed() interface to add a callback interface that's called whenever a complete event is read from stdin. The push_event() callback does the BER encoding and enqueues the encoded event. The 'submission_thread' then dequeues it and synchronously submits it to the RACF server (using the ldap interface). SIGHUP is supposed to trigger a configuration file re-read and flush queues and network connections, but seems like there's still a bit of a problem when the submission thread is blocked on dequeueing events and a SIGHUP is caught. Signed-off-by: Klaus Heinrich Kiwi diff -purN audit-1.6.2/audisp/plugins/racf/racf-plugin.c audit-1.6.2_racf/audisp/plugins/racf/racf-plugin.c --- audit-1.6.2/audisp/plugins/racf/racf-plugin.c 1969-12-31 21:00:00.000000000 -0300 +++ audit-1.6.2_racf/audisp/plugins/racf/racf-plugin.c 2007-09-28 09:18:08.000000000 -0300 @@ -0,0 +1,483 @@ +/*************************************************************************** +* Copyright (C) 2007 International Business Machines Corp. * +* All Rights Reserved. * +* * +* 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 2 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, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +* * +* Authors: * +* Klaus Heinrich Kiwi * +***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "auparse.h" +#include "racf-log.h" +#include "racf-ldap.h" +#include "racf-config.h" +#include "racf-queue.h" + +/* + * Global vars + */ +volatile int stop = 0; +volatile int hup = 0; +volatile RACF racf_inst; +static racf_conf_t conf; +static const char *def_config_file = "/etc/audisp/racf.conf"; +static pthread_t submission_thread; +pid_t mypid = 0; + +/* + * SIGTERM handler + */ +static void term_handler(int sig) +{ + log_info("Got SIGTERM - Exiting"); + stop = 1; + nudge_queue(); +} + +/* + * SIGHUP handler - re-read config, reconnect to RACF + */ +static void hup_handler(int sig) +{ + log_info("Got SIGHUP - flushing configuration"); + hup = 1; + nudge_queue(); +} + +/* + * SIGALRM handler - help force exit when terminating daemon + */ +static void alarm_handler(int sig) +{ + log_err("Aborting submission thread"); + pthread_cancel(submission_thread); + abort(); +} + +/* + * The submission thread + * It's job is to dequeue the events from the queue + * and sync submit them to RACF + */ +static void *submission_thread_main(void *arg) +{ + int rc; + + rc = racf_init(&racf_inst, conf.server, + conf.port, conf.user, + conf.password, + conf.timeout); + + if (rc != ICTX_SUCCESS) { + log_err("Error - RACF instance initialization failed"); + stop = 1; + return 0; + } + + while (stop == 0) { + /* block until we have an event */ + BerElement *ber = dequeue(); + + if (ber == NULL) { + if (hup) { + break; + } + continue; + } + debug_ber(ber); + rc = submit_request_s(&racf_inst, ber); + if (rc == ICTX_E_FATAL) { + log_err("Error - Fatal error in event submission"); + stop = 1; + } else if (rc != ICTX_SUCCESS) { + log_err("Event submission failure - event dropped"); + } + else { + log_debug("Event submission success"); + } + ber_free(ber, 1); /* also free BER buffer */ + } + log_debug("Stopping event submission thread"); + racf_destroy(&racf_inst); + + return 0; +} + + +/* + * auparse library callback that's called when an event is ready + */ +void +push_event(auparse_state_t * au, auparse_cb_event_t cb_event_type, + void *user_data) +{ + int rc; + BerElement *ber; + int qualifier; + char timestamp[26]; + char linkValue[RACF_LINK_VALUE_SIZE]; + char logString[RACF_LOGSTRING_SIZE]; + unsigned long linkValue_tmp; + + if (cb_event_type != AUPARSE_CB_EVENT_READY) + return; + + const au_event_t *e = auparse_get_timestamp(au); + if (e == NULL) + return; + /* + * we have an event. Each record will result in a different 'Item' + * (refer ASN.1 definition in racf-ldap.h) + */ + + /* + * Create a new BER element to encode the request + */ + ber = ber_alloc_t(LBER_USE_DER); + if (ber == NULL) { + log_err("Error allocating memory for BER element"); + goto fatal; + } + + /* + * Collect some information to fill in every item + */ + const char *node = auparse_get_node(au); + const char *success = auparse_find_field(au, "success"); + /* roll back event to get 'res' */ + auparse_first_record(au); + const char *res = auparse_find_field(au, "res"); + + /* check if this event is a success or failure one */ + if (success) { + if (strncmp(success, "0", 1) == 0 || + strncmp(success, "no", 2) == 0) + qualifier = RACF_QUALIF_FAIL; + else + qualifier = RACF_QUALIF_SUCCESS; + } else if (res) { + if (strncmp(res, "0", 1) == 0 + || strncmp(res, "failed", 6) == 0) + qualifier = RACF_QUALIF_FAIL; + else + qualifier = RACF_QUALIF_SUCCESS; + } else + qualifier = RACF_QUALIF_INFO; + + /* get timestamp text */ + ctime_r(&e->sec, timestamp); + timestamp[24] = '\0'; /* strip \n' */ + + /* prepare linkValue which will be used for every item */ + linkValue_tmp = htonl(e->serial); /* padronize to use network + * byte order + */ + memset(&linkValue, 0, RACF_LINK_VALUE_SIZE); + memcpy(&linkValue, &linkValue_tmp, sizeof(unsigned long)); + + /* + * Prepare the logString with some meaningful text + */ + sprintf(logString, "Linux (%s):", node); + + /* + * Start writing to BER element. + * There's only one field (version) out of the item sequence. + * Also open item sequence + */ + rc = ber_printf(ber, "{i{", ICTX_REQUESTVER); + if (rc < 0) + goto skip_event; + + /* + * Roll back to first record and iterate through all records + */ + auparse_first_record(au); + do { + const char *type = auparse_find_field(au, "type"); + if (type == NULL) + goto skip_event; + + log_debug("got record: %s", auparse_get_record_text(au)); + + /* + * First field is item Version, same as global version + */ + rc = ber_printf(ber, "{i", ICTX_REQUESTVER); + + /* + * Second field is the itemTag + * use our internal event counter, increasing it + */ + rc |= ber_printf(ber, "i", conf.counter++); + + /* + * Third field is the linkValue + * using ber_put_ostring since it is not null-terminated + */ + rc |= ber_put_ostring(ber, linkValue, + RACF_LINK_VALUE_SIZE, + LBER_OCTETSTRING); + /* + * Fourth field is the violation + * Don't have anything better yet to put here + */ + rc |= ber_printf(ber, "b", 0); + + /* + * Fifth field is the event. + * FIXME: this might be the place to switch on the + * audit record type and map to a more meaningful + * RACF event here + */ + rc |= ber_printf(ber, "i", RACF_EVENT_AUTHORIZATION); + + /* + * Sixth field is the qualifier. We map 'success' or + * 'res' to this field + */ + rc |= ber_printf(ber, "i", qualifier); + + /* + * Seventh field is the Class + * always use '@LINUX' for this version + * max size RACF_CLASS_SIZE + */ + rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG); + rc |= ber_printf(ber, "s", "@LINUX"); + + /* + * Eighth field is the resource + * use the record type (name) as the resource + * max size RACF_RESOURCE_SIZE + */ + rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG); + rc |= ber_printf(ber, "s", type); + + /* + * Nineth field is the LogString + * we try to put something meaningful here + * we also start the relocations sequence + */ + strcat(logString, type); /* concatenate the event type */ + rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG); + rc |= ber_printf(ber, "s{", logString); + + /* + * Now we start adding the relocations. + * Let's add the timestamp as the first one + * so it's out of the field loop + */ + rc |= ber_printf(ber, "{i", RACF_RELOC_TIMESTAMP); + rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG); + rc |= ber_printf(ber, "s}", timestamp); + + /* + * Check that encoding is going OK until now + */ + if (rc < 0) + goto skip_event; + + /* + * Now go to first field, + * and iterate through all fields + */ + auparse_first_field(au); + do { + /* + * we set a maximum of 1024 chars for + * relocation data (field=value pairs) + * Hopefuly this wont overflow too often + */ + char data[1024]; + const char *name = auparse_get_field_name(au); + const char *value = auparse_get_field_str(au); + if (name == NULL || value == NULL) + goto skip_event; + + /* + * First reloc field is the Relocation type + * We use 'OTHER' here since we don't have + * anything better + */ + rc |= ber_printf(ber, "{i", RACF_RELOC_OTHER); + + /* + * Second field is the relocation data + * We use a 'name=value' pair here + * Use up to 1023 chars (one char left for '\0') + */ + snprintf(data, 1023, "%s=%s", name, value); + rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG); + rc |= ber_printf(ber, "s}", data); + + /* + * Check encoding status + */ + if (rc < 0) + goto skip_event; + } while (auparse_next_field(au) > 0); + + /* + * After adding all relocations we are done with + * this item - finalize relocs and item + */ + rc |= ber_printf(ber, "}}"); + + /* + * Check if we are doing well with encoding + */ + if (rc < 0) + goto skip_event; + + } while (auparse_next_record(au) > 0); + + /* + * We have all items in - finalize item sequence & request + */ + rc |= ber_printf(ber, "}}"); + + /* + * Check if everything went alright with encoding + */ + if (rc < 0) + goto skip_event; + + /* + * finally, enqueue request and let the other + * thread process it + */ + log_debug("Encoding done, enqueuing event"); + enqueue(ber); + + return; + +skip_event: + log_warn("Warning - error encoding request, skipping event"); + ber_free(ber, 1); /* free it since we're not enqueuing it */ + return; + +fatal: + log_err("Fatal error while encoding request - aborting"); + stop = 1; +} + +int main(int argc, char *argv[]) +{ + int rc; + char *cpath; + char buf[1024]; + struct sigaction sa; + auparse_state_t *au; + + mypid = getpid(); + + log_info("starting"); + + /* + * sighandlers + */ + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = term_handler; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = hup_handler; + sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = alarm_handler; + sigaction(SIGALRM, &sa, NULL); + + /* + * the main program accepts a single (optional) argument: + * it's configuration file (this is NOT the plugin configuration + * usually located at /etc/audisp/plugin.d) + * We use the default (def_config_file) if no arguments are given + */ + if (argc == 1) { + cpath = def_config_file; + log_warn("No configuration file specified - using default (%s)", cpath); + } else if (argc == 2) { + cpath = argv[1]; + log_info("Configuration file: %s", cpath); + } else { + log_err("Error - invalid number of parameters"); + return 1; + } + + /* initialize record counter */ + conf.counter = 1; + + do { + hup = 0; /* don't flush unless hup==1 */ + + /* initialization is done in 5 steps: */ + rc = load_config(&conf, cpath); /* 1 */ + if (rc != 0) { + log_err("Error - Can't load configuration"); + return -1; + } + + /* initialize auparse */ + au = auparse_init(AUSOURCE_FEED, 0); /* 2 */ + + /* initialize the submission queue */ /* 3 */ + if (init_queue(conf.q_depth) != 0) { + log_err("Error - Can't initialize event queue"); + return -1; + } + + /* Initialize submission thread */ + pthread_create(&submission_thread, NULL, + submission_thread_main, NULL); /* 4 */ + + /* add our event consumer callback */ + auparse_add_callback(au, push_event, NULL, NULL); /* 5 */ + + /* loop reading stdin */ + while (fgets(buf, 1024, stdin) && hup == 0 && stop == 0) { + /* let our callback know of the new data */ + auparse_feed(au, buf, strlen(buf)); + } + /* flush everything, in order */ + auparse_flush_feed(au); /* 5 */ + nudge_queue(); + alarm(5); /* 5 seconds to clear the queue */ + pthread_join(submission_thread, NULL); /* 4 */ + destroy_queue(); /* 3 */ + auparse_destroy(au); /* 2 */ + free_config(&conf); /* 1 */ + } + while (hup && stop == 0); + + + return 0; +}