aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/audit/src/auditd-listen.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/src/auditd-listen.c')
-rw-r--r--framework/src/audit/src/auditd-listen.c1065
1 files changed, 1065 insertions, 0 deletions
diff --git a/framework/src/audit/src/auditd-listen.c b/framework/src/audit/src/auditd-listen.c
new file mode 100644
index 00000000..d1977c63
--- /dev/null
+++ b/framework/src/audit/src/auditd-listen.c
@@ -0,0 +1,1065 @@
+/* auditd-listen.c --
+ * Copyright 2008,2009,2011 Red Hat Inc., Durham, North Carolina.
+ * 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:
+ * DJ Delorie <dj@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <fcntl.h> /* O_NOFOLLOW needs gnu defined */
+#include <libgen.h>
+#include <arpa/inet.h>
+#include <limits.h> /* INT_MAX */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#ifdef HAVE_LIBWRAP
+#include <tcpd.h>
+#endif
+#ifdef USE_GSSAPI
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <krb5.h>
+#endif
+#include "libaudit.h"
+#include "auditd-event.h"
+#include "auditd-config.h"
+#include "private.h"
+
+#include "ev.h"
+
+extern volatile int stop;
+extern int send_audit_event(int type, const char *str);
+#define DEFAULT_BUF_SZ 192
+
+typedef struct ev_tcp {
+ struct ev_io io;
+ struct sockaddr_in addr;
+ struct ev_tcp *next, *prev;
+ unsigned int bufptr;
+ int client_active;
+#ifdef USE_GSSAPI
+ /* This holds the negotiated security context for this client. */
+ gss_ctx_id_t gss_context;
+ char *remote_name;
+ int remote_name_len;
+#endif
+ unsigned char buffer [MAX_AUDIT_MESSAGE_LENGTH + 17];
+} ev_tcp;
+
+static int listen_socket;
+static struct ev_io tcp_listen_watcher;
+static struct ev_periodic periodic_watcher;
+static int min_port, max_port, max_per_addr;
+static int use_libwrap = 1;
+#ifdef USE_GSSAPI
+/* This is used to hold our own private key. */
+static gss_cred_id_t server_creds;
+static char *my_service_name, *my_gss_realm;
+static int use_gss = 0;
+static char msgbuf[MAX_AUDIT_MESSAGE_LENGTH + 1];
+#endif
+
+static struct ev_tcp *client_chain = NULL;
+
+static char *sockaddr_to_ipv4(struct sockaddr_in *addr)
+{
+ unsigned char *uaddr = (unsigned char *)&(addr->sin_addr);
+ static char buf[40];
+
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u",
+ uaddr[0], uaddr[1], uaddr[2], uaddr[3]);
+ return buf;
+}
+
+static char *sockaddr_to_addr4(struct sockaddr_in *addr)
+{
+ unsigned char *uaddr = (unsigned char *)&(addr->sin_addr);
+ static char buf[40];
+
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u:%u",
+ uaddr[0], uaddr[1], uaddr[2], uaddr[3],
+ ntohs (addr->sin_port));
+ return buf;
+}
+
+static void set_close_on_exec (int fd)
+{
+ int flags = fcntl (fd, F_GETFD);
+ if (flags == -1)
+ flags = 0;
+ flags |= FD_CLOEXEC;
+ fcntl (fd, F_SETFD, flags);
+}
+
+static void release_client(struct ev_tcp *client)
+{
+ char emsg[DEFAULT_BUF_SZ];
+
+ snprintf(emsg, sizeof(emsg), "addr=%s port=%d res=success",
+ sockaddr_to_ipv4(&client->addr), ntohs (client->addr.sin_port));
+ send_audit_event(AUDIT_DAEMON_CLOSE, emsg);
+#ifdef USE_GSSAPI
+ if (client->remote_name)
+ free (client->remote_name);
+#endif
+ shutdown(client->io.fd, SHUT_RDWR);
+ close(client->io.fd);
+ if (client_chain == client)
+ client_chain = client->next;
+ if (client->next)
+ client->next->prev = client->prev;
+ if (client->prev)
+ client->prev->next = client->next;
+}
+
+static void close_client(struct ev_tcp *client)
+{
+ release_client (client);
+ free (client);
+}
+
+static int ar_write (int sock, const void *buf, int len)
+{
+ int rc = 0, w;
+ while (len > 0) {
+ do {
+ w = write(sock, buf, len);
+ } while (w < 0 && errno == EINTR);
+ if (w < 0)
+ return w;
+ if (w == 0)
+ break;
+ rc += w;
+ len -= w;
+ buf = (const void *)((const char *)buf + w);
+ }
+ return rc;
+}
+
+#ifdef USE_GSSAPI
+static int ar_read (int sock, void *buf, int len)
+{
+ int rc = 0, r;
+ while (len > 0) {
+ do {
+ r = read(sock, buf, len);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ rc += r;
+ len -= r;
+ buf = (void *)((char *)buf + r);
+ }
+ return rc;
+}
+
+
+/* Communications under GSS is done by token exchanges. Each "token"
+ may contain a message, perhaps signed, perhaps encrypted. The
+ messages within are what we're interested in, but the network sees
+ the tokens. The protocol we use for transferring tokens is to send
+ the length first, four bytes MSB first, then the token data. We
+ return nonzero on error. */
+static int recv_token (int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ ret = ar_read(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ audit_msg(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ } else if (!ret) {
+ return 0;
+ } else if (ret != 4) {
+ audit_msg(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ }
+
+ len = ((lenbuf[0] << 24)
+ | (lenbuf[1] << 16)
+ | (lenbuf[2] << 8)
+ | lenbuf[3]);
+ if (len > MAX_AUDIT_MESSAGE_LENGTH) {
+ audit_msg(LOG_ERR,
+ "GSS-API error: event length excedes MAX_AUDIT_LENGTH");
+ return -1;
+ }
+ tok->length = len;
+
+ tok->value = (char *) malloc(tok->length ? tok->length : 1);
+ if (tok->length && tok->value == NULL) {
+ audit_msg(LOG_ERR, "Out of memory allocating token data");
+ return -1;
+ }
+
+ ret = ar_read(s, (char *) tok->value, tok->length);
+ if (ret < 0) {
+ audit_msg(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ } else if (ret != (int) tok->length) {
+ audit_msg(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ }
+
+ return 1;
+}
+
+/* Same here. */
+int send_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ if (tok->length > 0xffffffffUL)
+ return -1;
+ len = tok->length;
+ lenbuf[0] = (len >> 24) & 0xff;
+ lenbuf[1] = (len >> 16) & 0xff;
+ lenbuf[2] = (len >> 8) & 0xff;
+ lenbuf[3] = len & 0xff;
+
+ ret = ar_write(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ audit_msg(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ } else if (ret != 4) {
+ audit_msg(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ }
+
+ ret = ar_write(s, tok->value, tok->length);
+ if (ret < 0) {
+ audit_msg(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ } else if (ret != (int) tok->length) {
+ audit_msg(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void gss_failure_2 (const char *msg, int status, int type)
+{
+ OM_uint32 message_context = 0;
+ OM_uint32 min_status = 0;
+ gss_buffer_desc status_string;
+
+ do {
+ gss_display_status (&min_status,
+ status,
+ type,
+ GSS_C_NO_OID,
+ &message_context,
+ &status_string);
+
+ audit_msg (LOG_ERR, "GSS error: %s: %s",
+ msg, (char *)status_string.value);
+
+ gss_release_buffer(&min_status, &status_string);
+ } while (message_context != 0);
+}
+
+static void gss_failure (const char *msg, int major_status, int minor_status)
+{
+ gss_failure_2 (msg, major_status, GSS_C_GSS_CODE);
+ if (minor_status)
+ gss_failure_2 (msg, minor_status, GSS_C_MECH_CODE);
+}
+
+#define KCHECK(x,f) if (x) { \
+ const char *kstr = krb5_get_error_message(kcontext, x); \
+ audit_msg(LOG_ERR, "krb5 error: %s in %s\n", kstr, f); \
+ krb5_free_error_message(kcontext, kstr); \
+ return -1; }
+
+/* These are our private credentials, which come from a key file on
+ our server. They are aquired once, at program start. */
+static int server_acquire_creds(const char *service_name,
+ gss_cred_id_t *server_creds)
+{
+ gss_buffer_desc name_buf;
+ gss_name_t server_name;
+ OM_uint32 major_status, minor_status;
+
+ krb5_context kcontext = NULL;
+ int krberr;
+
+ my_service_name = strdup (service_name);
+ name_buf.value = (char *)service_name;
+ name_buf.length = strlen(name_buf.value) + 1;
+ major_status = gss_import_name(&minor_status, &name_buf,
+ (gss_OID) gss_nt_service_name,
+ &server_name);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("importing name", major_status, minor_status);
+ return -1;
+ }
+
+ major_status = gss_acquire_cred(&minor_status,
+ server_name, GSS_C_INDEFINITE,
+ GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+ server_creds, NULL, NULL);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("acquiring credentials",
+ major_status, minor_status);
+ return -1;
+ }
+
+ (void) gss_release_name(&minor_status, &server_name);
+
+ krberr = krb5_init_context (&kcontext);
+ KCHECK (krberr, "krb5_init_context");
+ krberr = krb5_get_default_realm (kcontext, &my_gss_realm);
+ KCHECK (krberr, "krb5_get_default_realm");
+
+ audit_msg(LOG_DEBUG, "GSS creds for %s acquired", service_name);
+
+ return 0;
+}
+
+/* This is where we negotiate a security context with the client. In
+ the case of Kerberos, this is where the key exchange happens.
+ FIXME: While everything else is strictly nonblocking, this
+ negotiation blocks. */
+static int negotiate_credentials (ev_tcp *io)
+{
+ gss_buffer_desc send_tok, recv_tok;
+ gss_name_t client;
+ OM_uint32 maj_stat, min_stat, acc_sec_min_stat;
+ gss_ctx_id_t *context;
+ OM_uint32 sess_flags;
+ char *slashptr, *atptr;
+
+ context = & io->gss_context;
+ *context = GSS_C_NO_CONTEXT;
+
+ maj_stat = GSS_S_CONTINUE_NEEDED;
+ do {
+ /* STEP 1 - get a token from the client. */
+
+ if (recv_token(io->io.fd, &recv_tok) <= 0) {
+ audit_msg(LOG_ERR,
+ "TCP session from %s will be closed, error ignored",
+ sockaddr_to_addr4(&io->addr));
+ return -1;
+ }
+ if (recv_tok.length == 0)
+ continue;
+
+ /* STEP 2 - let GSS process that token. */
+
+ maj_stat = gss_accept_sec_context(&acc_sec_min_stat,
+ context, server_creds,
+ &recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS, &client,
+ NULL, &send_tok, &sess_flags,
+ NULL, NULL);
+ if (recv_tok.value) {
+ free(recv_tok.value);
+ recv_tok.value = NULL;
+ }
+ if (maj_stat != GSS_S_COMPLETE
+ && maj_stat != GSS_S_CONTINUE_NEEDED) {
+ gss_release_buffer(&min_stat, &send_tok);
+ if (*context != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&min_stat, context,
+ GSS_C_NO_BUFFER);
+ gss_failure("accepting context", maj_stat,
+ acc_sec_min_stat);
+ return -1;
+ }
+
+ /* STEP 3 - send any tokens to the client that GSS may
+ ask us to send. */
+
+ if (send_tok.length != 0) {
+ if (send_token(io->io.fd, &send_tok) < 0) {
+ gss_release_buffer(&min_stat, &send_tok);
+ audit_msg(LOG_ERR,
+ "TCP session from %s will be closed, error ignored",
+ sockaddr_to_addr4(&io->addr));
+ if (*context != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&min_stat,
+ context, GSS_C_NO_BUFFER);
+ return -1;
+ }
+ gss_release_buffer(&min_stat, &send_tok);
+ }
+ } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+ maj_stat = gss_display_name(&min_stat, client, &recv_tok, NULL);
+ gss_release_name(&min_stat, &client);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ gss_failure("displaying name", maj_stat, min_stat);
+ return -1;
+ }
+
+ audit_msg(LOG_INFO, "GSS-API Accepted connection from: %s",
+ (char *)recv_tok.value);
+ io->remote_name = strdup (recv_tok.value);
+ io->remote_name_len = strlen (recv_tok.value);
+ gss_release_buffer(&min_stat, &recv_tok);
+
+ slashptr = strchr (io->remote_name, '/');
+ atptr = strchr (io->remote_name, '@');
+
+ if (!slashptr || !atptr) {
+ audit_msg(LOG_ERR, "Invalid GSS name from remote client: %s",
+ io->remote_name);
+ return -1;
+ }
+
+ *slashptr = 0;
+ if (strcmp (io->remote_name, my_service_name)) {
+ audit_msg(LOG_ERR, "Unauthorized GSS client name: %s (not %s)",
+ io->remote_name, my_service_name);
+ return -1;
+ }
+ *slashptr = '/';
+
+ if (strcmp (atptr+1, my_gss_realm)) {
+ audit_msg(LOG_ERR, "Unauthorized GSS client realm: %s (not %s)",
+ atptr+1, my_gss_realm);
+ return -1;
+ }
+
+ return 0;
+}
+#endif /* USE_GSSAPI */
+
+/* This is called from auditd-event after the message has been logged.
+ The header is already filled in. */
+static void client_ack (void *ack_data, const unsigned char *header,
+ const char *msg)
+{
+ ev_tcp *io = (ev_tcp *)ack_data;
+#ifdef USE_GSSAPI
+ if (use_gss) {
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc utok, etok;
+ int rc, mlen;
+
+ mlen = strlen (msg);
+ utok.length = AUDIT_RMW_HEADER_SIZE + mlen;
+ utok.value = malloc (utok.length + 1);
+
+ memcpy (utok.value, header, AUDIT_RMW_HEADER_SIZE);
+ memcpy (utok.value+AUDIT_RMW_HEADER_SIZE, msg, mlen);
+
+ /* Wrapping the message creates a token for the
+ client. Then we just have to worry about sending
+ the token. */
+
+ major_status = gss_wrap (&minor_status,
+ io->gss_context,
+ 1,
+ GSS_C_QOP_DEFAULT,
+ &utok,
+ NULL,
+ &etok);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("encrypting message", major_status,
+ minor_status);
+ free (utok.value);
+ return;
+ }
+ // FIXME: What were we going to do with rc?
+ rc = send_token (io->io.fd, &etok);
+ free (utok.value);
+ (void) gss_release_buffer(&minor_status, &etok);
+
+ return;
+ }
+#endif
+ // Send the header and a text error message if it exists
+ ar_write (io->io.fd, header, AUDIT_RMW_HEADER_SIZE);
+ if (msg[0])
+ ar_write (io->io.fd, msg, strlen(msg));
+}
+
+static void client_message (struct ev_tcp *io, unsigned int length,
+ unsigned char *header)
+{
+ unsigned char ch;
+ uint32_t type, mlen, seq;
+ int hver, mver;
+
+ if (AUDIT_RMW_IS_MAGIC (header, length)) {
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, mlen, seq)
+
+ ch = header[length];
+ header[length] = 0;
+ if (length > 1 && header[length-1] == '\n')
+ header[length-1] = 0;
+ if (type == AUDIT_RMW_TYPE_HEARTBEAT) {
+ unsigned char ack[AUDIT_RMW_HEADER_SIZE];
+ AUDIT_RMW_PACK_HEADER (ack, 0, AUDIT_RMW_TYPE_ACK,
+ 0, seq);
+ client_ack (io, ack, "");
+ } else
+ enqueue_formatted_event(header+AUDIT_RMW_HEADER_SIZE,
+ client_ack, io, seq);
+ header[length] = ch;
+ } else {
+ header[length] = 0;
+ if (length > 1 && header[length-1] == '\n')
+ header[length-1] = 0;
+ enqueue_formatted_event (header, NULL, NULL, 0);
+ }
+}
+
+static void auditd_tcp_client_handler( struct ev_loop *loop,
+ struct ev_io *_io, int revents )
+{
+ struct ev_tcp *io = (struct ev_tcp *) _io;
+ int i, r;
+ int total_this_call = 0;
+
+ io->client_active = 1;
+
+ /* The socket is non-blocking, but we have a limited buffer
+ size. In the event that we get a packet that's bigger than
+ our buffer, we need to read it in multiple parts. Thus, we
+ keep reading/parsing/processing until we run out of ready
+ data. */
+read_more:
+ r = read (io->io.fd,
+ io->buffer + io->bufptr,
+ MAX_AUDIT_MESSAGE_LENGTH - io->bufptr);
+
+ if (r < 0 && errno == EAGAIN)
+ r = 0;
+
+ /* We need to keep track of the difference between "no data
+ * because it's closed" and "no data because we've read it
+ * all". */
+ if (r == 0 && total_this_call > 0) {
+ return;
+ }
+
+ /* If the connection is gracefully closed, the first read we
+ try will return zero. If the connection times out or
+ otherwise fails, the read will return -1. */
+ if (r <= 0) {
+ if (r < 0)
+ audit_msg (LOG_WARNING,
+ "client %s socket closed unexpectedly",
+ sockaddr_to_addr4(&io->addr));
+
+ /* There may have been a final message without a LF. */
+ if (io->bufptr) {
+ client_message (io, io->bufptr, io->buffer);
+
+ }
+
+ ev_io_stop (loop, _io);
+ close_client (io);
+ return;
+ }
+
+ total_this_call += r;
+
+more_messages:
+#ifdef USE_GSSAPI
+ /* If we're using GSS at all, everything will be encrypted,
+ one record per token. */
+ if (use_gss) {
+ gss_buffer_desc utok, etok;
+ io->bufptr += r;
+ uint32_t len;
+ OM_uint32 major_status, minor_status;
+
+ /* We need at least four bytes to test the length. If
+ we have more than four bytes, we can tell if we
+ have a whole token (or more). */
+
+ if (io->bufptr < 4)
+ return;
+
+ len = ( ((uint32_t)(io->buffer[0] & 0xFF) << 24)
+ | ((uint32_t)(io->buffer[1] & 0xFF) << 16)
+ | ((uint32_t)(io->buffer[2] & 0xFF) << 8)
+ | (uint32_t)(io->buffer[3] & 0xFF));
+
+ /* Make sure we got something big enough and not too big */
+ if (io->bufptr < 4 + len || len > MAX_AUDIT_MESSAGE_LENGTH)
+ return;
+ i = len + 4;
+
+ etok.length = len;
+ etok.value = io->buffer + 4;
+
+ /* Unwrapping the token gives us the original message,
+ which we know is already a single record. */
+ major_status = gss_unwrap (&minor_status, io->gss_context,
+ &etok, &utok, NULL, NULL);
+
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("decrypting message", major_status,
+ minor_status);
+ } else {
+ /* client_message() wants to NUL terminate it,
+ so copy it to a bigger buffer. Plus, we
+ want to add our own tag. */
+ memcpy (msgbuf, utok.value, utok.length);
+ while (utok.length > 0 && msgbuf[utok.length-1] == '\n')
+ utok.length --;
+ snprintf (msgbuf + utok.length,
+ MAX_AUDIT_MESSAGE_LENGTH - utok.length,
+ " krb5=%s", io->remote_name);
+ utok.length += 6 + io->remote_name_len;
+ client_message (io, utok.length, msgbuf);
+ gss_release_buffer(&minor_status, &utok);
+ }
+ } else
+#endif
+ if (AUDIT_RMW_IS_MAGIC (io->buffer, (io->bufptr+r))) {
+ uint32_t type, len, seq;
+ int hver, mver;
+ unsigned char *header = (unsigned char *)io->buffer;
+
+ io->bufptr += r;
+
+ if (io->bufptr < AUDIT_RMW_HEADER_SIZE)
+ return;
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, len, seq);
+
+ /* Make sure len is not too big */
+ if (len > MAX_AUDIT_MESSAGE_LENGTH)
+ return;
+
+ i = len;
+ i += AUDIT_RMW_HEADER_SIZE;
+
+ /* See if we have enough bytes to extract the whole message. */
+ if (io->bufptr < i)
+ return;
+
+ /* We have an I-byte message in buffer. Send ACK */
+ client_message (io, i, io->buffer);
+
+ } else {
+ /* At this point, the buffer has IO->BUFPTR+R bytes in it.
+ The first IO->BUFPTR bytes do not have a LF in them (we've
+ already checked), we must check the R new bytes. */
+
+ for (i = io->bufptr; i < io->bufptr + r; i ++)
+ if (io->buffer [i] == '\n')
+ break;
+
+ io->bufptr += r;
+
+ /* Check for a partial message, with no LF yet. */
+ if (i == io->bufptr)
+ return;
+
+ i++;
+
+ /* We have an I-byte message in buffer. Send ACK */
+ client_message (io, i, io->buffer);
+ }
+
+ /* Now copy any remaining bytes to the beginning of the
+ buffer. */
+ memmove(io->buffer, io->buffer + i, io->bufptr - i);
+ io->bufptr -= i;
+
+ /* See if this packet had more than one message in it. */
+ if (io->bufptr > 0) {
+ r = io->bufptr;
+ io->bufptr = 0;
+ goto more_messages;
+ }
+
+ /* Go back and see if there's more data to read. */
+ goto read_more;
+}
+
+#ifndef HAVE_LIBWRAP
+#define auditd_tcpd_check(s) ({ 0; })
+#else
+int allow_severity = LOG_INFO, deny_severity = LOG_NOTICE;
+static int auditd_tcpd_check(int sock)
+{
+ struct request_info request;
+
+ request_init(&request, RQ_DAEMON, "auditd", RQ_FILE, sock, 0);
+ fromhost(&request);
+ if (! hosts_access(&request))
+ return 1;
+ return 0;
+}
+#endif
+
+/*
+ * This function counts the number of concurrent connections and returns
+ * a 1 if there are too many and a 0 otherwise. It assumes the incoming
+ * connection has not been added to the linked list yet.
+ */
+static int check_num_connections(struct sockaddr_in *aaddr)
+{
+ int num = 0;
+ struct ev_tcp *client = client_chain;
+
+ while (client) {
+ if (memcmp(&aaddr->sin_addr, &client->addr.sin_addr,
+ sizeof(struct in_addr)) == 0) {
+ num++;
+ if (num >= max_per_addr)
+ return 1;
+ }
+ client = client->next;
+ }
+ return 0;
+}
+
+static void auditd_tcp_listen_handler( struct ev_loop *loop,
+ struct ev_io *_io, int revents )
+{
+ int one=1;
+ int afd;
+ socklen_t aaddrlen;
+ struct sockaddr_in aaddr;
+ struct ev_tcp *client;
+ char emsg[DEFAULT_BUF_SZ];
+
+ /* Accept the connection and see where it's coming from. */
+ aaddrlen = sizeof(aaddr);
+ afd = accept (listen_socket, (struct sockaddr *)&aaddr, &aaddrlen);
+ if (afd == -1) {
+ audit_msg(LOG_ERR, "Unable to accept TCP connection");
+ return;
+ }
+
+ if (use_libwrap) {
+ if (auditd_tcpd_check(afd)) {
+ shutdown(afd, SHUT_RDWR);
+ close(afd);
+ audit_msg(LOG_ERR, "TCP connection from %s rejected",
+ sockaddr_to_addr4(&aaddr));
+ snprintf(emsg, sizeof(emsg),
+ "op=wrap addr=%s port=%d res=no",
+ sockaddr_to_ipv4(&aaddr),
+ ntohs (aaddr.sin_port));
+ send_audit_event(AUDIT_DAEMON_ACCEPT, emsg);
+ return;
+ }
+ }
+
+ /* Verify it's coming from an authorized port. We assume the firewall
+ * will block attempts from unauthorized machines. */
+ if (min_port > ntohs (aaddr.sin_port) ||
+ ntohs (aaddr.sin_port) > max_port) {
+ audit_msg(LOG_ERR, "TCP connection from %s rejected",
+ sockaddr_to_addr4(&aaddr));
+ snprintf(emsg, sizeof(emsg),
+ "op=port addr=%s port=%d res=no",
+ sockaddr_to_ipv4(&aaddr),
+ ntohs (aaddr.sin_port));
+ send_audit_event(AUDIT_DAEMON_ACCEPT, emsg);
+ shutdown(afd, SHUT_RDWR);
+ close(afd);
+ return;
+ }
+
+ /* Make sure we don't have too many connections */
+ if (check_num_connections(&aaddr)) {
+ audit_msg(LOG_ERR, "Too many connections from %s - rejected",
+ sockaddr_to_addr4(&aaddr));
+ snprintf(emsg, sizeof(emsg),
+ "op=dup addr=%s port=%d res=no",
+ sockaddr_to_ipv4(&aaddr),
+ ntohs (aaddr.sin_port));
+ send_audit_event(AUDIT_DAEMON_ACCEPT, emsg);
+ shutdown(afd, SHUT_RDWR);
+ close(afd);
+ return;
+ }
+
+ /* Connection is accepted...start setting it up */
+ setsockopt(afd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof (int));
+ setsockopt(afd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof (int));
+ setsockopt(afd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof (int));
+ set_close_on_exec (afd);
+
+ /* Make the client data structure */
+ client = (struct ev_tcp *) malloc (sizeof (struct ev_tcp));
+ if (client == NULL) {
+ audit_msg(LOG_CRIT, "Unable to allocate TCP client data");
+ snprintf(emsg, sizeof(emsg),
+ "op=alloc addr=%s port=%d res=no",
+ sockaddr_to_ipv4(&aaddr),
+ ntohs (aaddr.sin_port));
+ send_audit_event(AUDIT_DAEMON_ACCEPT, emsg);
+ shutdown(afd, SHUT_RDWR);
+ close(afd);
+ return;
+ }
+
+ memset (client, 0, sizeof (struct ev_tcp));
+ client->client_active = 1;
+
+ // Was watching for EV_ERROR, but libev 3.48 took it away
+ ev_io_init (&(client->io), auditd_tcp_client_handler, afd, EV_READ);
+
+ memcpy (&client->addr, &aaddr, sizeof (struct sockaddr_in));
+
+#ifdef USE_GSSAPI
+ if (use_gss && negotiate_credentials (client)) {
+ shutdown(afd, SHUT_RDWR);
+ close(afd);
+ free(client);
+ return;
+ }
+#endif
+
+ fcntl(afd, F_SETFL, O_NONBLOCK | O_NDELAY);
+ ev_io_start (loop, &(client->io));
+
+ /* Add the new connection to a linked list of active clients. */
+ client->next = client_chain;
+ if (client->next)
+ client->next->prev = client;
+ client_chain = client;
+
+ /* And finally log that we accepted the connection */
+ snprintf(emsg, sizeof(emsg),
+ "addr=%s port=%d res=success", sockaddr_to_ipv4(&aaddr),
+ ntohs (aaddr.sin_port));
+ send_audit_event(AUDIT_DAEMON_ACCEPT, emsg);
+}
+
+static void auditd_set_ports(int minp, int maxp, int max_p_addr)
+{
+ min_port = minp;
+ max_port = maxp;
+ max_per_addr = max_p_addr;
+}
+
+static void periodic_handler(struct ev_loop *loop, struct ev_periodic *per,
+ int revents )
+{
+ struct daemon_conf *config = (struct daemon_conf *) per->data;
+ struct ev_tcp *ev, *next = NULL;
+ int active;
+
+ if (!config->tcp_client_max_idle)
+ return;
+
+ for (ev = client_chain; ev; ev = next) {
+ active = ev->client_active;
+ ev->client_active = 0;
+ if (active)
+ continue;
+
+ audit_msg(LOG_NOTICE,
+ "client %s idle too long - closing connection\n",
+ sockaddr_to_addr4(&(ev->addr)));
+ ev_io_stop (loop, &ev->io);
+ release_client(ev);
+ next = ev->next;
+ free(ev);
+ }
+}
+
+int auditd_tcp_listen_init ( struct ev_loop *loop, struct daemon_conf *config )
+{
+ struct sockaddr_in address;
+ int one = 1;
+
+ ev_periodic_init (&periodic_watcher, periodic_handler,
+ 0, config->tcp_client_max_idle, NULL);
+ periodic_watcher.data = config;
+ if (config->tcp_client_max_idle)
+ ev_periodic_start (loop, &periodic_watcher);
+
+ /* If the port is not set, that means we aren't going to
+ listen for connections. */
+ if (config->tcp_listen_port == 0)
+ return 0;
+
+ listen_socket = socket (AF_INET, SOCK_STREAM, 0);
+ if (listen_socket < 0) {
+ audit_msg(LOG_ERR, "Cannot create tcp listener socket");
+ return 1;
+ }
+
+ set_close_on_exec (listen_socket);
+ setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&one, sizeof (int));
+
+ memset (&address, 0, sizeof(address));
+ address.sin_family = AF_INET;
+ address.sin_port = htons(config->tcp_listen_port);
+ address.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ /* This avoids problems if auditd needs to be restarted. */
+ setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&one, sizeof (int));
+
+ if (bind(listen_socket, (struct sockaddr *)&address, sizeof(address))){
+ audit_msg(LOG_ERR,
+ "Cannot bind tcp listener socket to port %ld",
+ config->tcp_listen_port);
+ close(listen_socket);
+ return 1;
+ }
+
+ listen(listen_socket, config->tcp_listen_queue);
+
+ audit_msg(LOG_DEBUG, "Listening on TCP port %ld",
+ config->tcp_listen_port);
+
+ ev_io_init (&tcp_listen_watcher, auditd_tcp_listen_handler,
+ listen_socket, EV_READ);
+ ev_io_start (loop, &tcp_listen_watcher);
+
+ use_libwrap = config->use_libwrap;
+ auditd_set_ports(config->tcp_client_min_port,
+ config->tcp_client_max_port,
+ config->tcp_max_per_addr);
+
+#ifdef USE_GSSAPI
+ if (config->enable_krb5) {
+ const char *princ = config->krb5_principal;
+ const char *key_file;
+ struct stat st;
+
+ if (!princ)
+ princ = "auditd";
+ use_gss = 1;
+ /* This may fail, but we don't care. */
+ unsetenv ("KRB5_KTNAME");
+ if (config->krb5_key_file)
+ key_file = config->krb5_key_file;
+ else
+ key_file = "/etc/audit/audit.key";
+ setenv ("KRB5_KTNAME", key_file, 1);
+
+ if (stat (key_file, &st) == 0) {
+ if ((st.st_mode & 07777) != 0400) {
+ audit_msg (LOG_ERR,
+ "%s is not mode 0400 (it's %#o) - compromised key?",
+ key_file, st.st_mode & 07777);
+ return -1;
+ }
+ if (st.st_uid != 0) {
+ audit_msg (LOG_ERR,
+ "%s is not owned by root (it's %d) - compromised key?",
+ key_file, st.st_uid);
+ return -1;
+ }
+ }
+
+ server_acquire_creds(princ, &server_creds);
+ }
+#endif
+
+ return 0;
+}
+
+void auditd_tcp_listen_uninit ( struct ev_loop *loop,
+ struct daemon_conf *config )
+{
+#ifdef USE_GSSAPI
+ OM_uint32 status;
+#endif
+
+ ev_io_stop ( loop, &tcp_listen_watcher );
+ close ( listen_socket );
+
+#ifdef USE_GSSAPI
+ if (use_gss) {
+ use_gss = 0;
+ gss_release_cred(&status, &server_creds);
+ }
+#endif
+
+ while (client_chain) {
+ unsigned char ack[AUDIT_RMW_HEADER_SIZE];
+
+ AUDIT_RMW_PACK_HEADER (ack, 0, AUDIT_RMW_TYPE_ENDING, 0, 0);
+ client_ack (client_chain, ack, "");
+ ev_io_stop (loop, &client_chain->io);
+ close_client (client_chain);
+ }
+
+ if (config->tcp_client_max_idle)
+ ev_periodic_stop (loop, &periodic_watcher);
+}
+
+static void periodic_reconfigure(struct daemon_conf *config)
+{
+ struct ev_loop *loop = ev_default_loop (EVFLAG_AUTO);
+ if (config->tcp_client_max_idle) {
+ ev_periodic_set (&periodic_watcher, ev_now (loop),
+ config->tcp_client_max_idle, NULL);
+ ev_periodic_start (loop, &periodic_watcher);
+ } else {
+ ev_periodic_stop (loop, &periodic_watcher);
+ }
+}
+
+void auditd_tcp_listen_reconfigure ( struct daemon_conf *nconf,
+ struct daemon_conf *oconf )
+{
+ /* Look at network things that do not need restarting */
+ if (oconf->tcp_client_min_port != nconf->tcp_client_min_port ||
+ oconf->tcp_client_max_port != nconf->tcp_client_max_port ||
+ oconf->tcp_max_per_addr != nconf->tcp_max_per_addr) {
+ oconf->tcp_client_min_port = nconf->tcp_client_min_port;
+ oconf->tcp_client_max_port = nconf->tcp_client_max_port;
+ oconf->tcp_max_per_addr = nconf->tcp_max_per_addr;
+ auditd_set_ports(oconf->tcp_client_min_port,
+ oconf->tcp_client_max_port,
+ oconf->tcp_max_per_addr);
+ }
+ if (oconf->tcp_client_max_idle != nconf->tcp_client_max_idle) {
+ oconf->tcp_client_max_idle = nconf->tcp_client_max_idle;
+ periodic_reconfigure(oconf);
+ }
+ if (oconf->tcp_listen_port != nconf->tcp_listen_port ||
+ oconf->tcp_listen_queue != nconf->tcp_listen_queue) {
+ oconf->tcp_listen_port = nconf->tcp_listen_port;
+ oconf->tcp_listen_queue = nconf->tcp_listen_queue;
+ // FIXME: need to restart the network stuff
+ }
+}