/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*
* transport layer for audit_remote (handles connection establishment, gss
* context initialization, message encryption and verification)
*
*/
#include <assert.h>
#include <audit_plugin.h>
#include <errno.h>
#include <fcntl.h>
#include <libintl.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <unistd.h>
#include <poll.h>
#include <pthread.h>
#include "audit_remote.h"
extern int timeout; /* connection timeout */
extern pthread_mutex_t plugin_mutex;
/*
* The three locks synchronize the simultaneous actions on top of transmission
* queue, socket, gss_context.
*/
/* reset routine synchronization - required by the sending thread */
/* transmission queue helpers */
static void transq_dequeue(transq_node_t *);
uint64_t);
static int transq_retransmit(void);
static void init_recv_record(void);
static void recv_record();
static int connect_timeout(int, struct sockaddr *, int);
static int send_timeout(int, const char *, size_t);
static int recv_timeout(int, char *, size_t);
static int send_token(int *, gss_buffer_t);
static int recv_token(int, gss_buffer_t);
/*
* report_err() - wrapper, mainly due to enhance the code readability - report
* error to syslog via call to __audit_syslog().
*/
static void
{
}
/*
* report_gss_err() - GSS API error reporting
*/
static void
{
char *err_msg;
/* major stat */
msg_ctx = 0;
do {
}
} while (msg_ctx);
/* minor stat */
msg_ctx = 0;
do {
}
} while (msg_ctx);
}
/*
* prot_ver_negotiate() - negotiate/acknowledge the protocol version. Currently,
* there is only one version supported by the plugin - "01".
* Note: connection must be initiated prior version negotiation
*/
static int
{
/*
* Set the version proposal string - once we support more than
* version "01" this part should be extended to solve the concatenation
* of supported version identifiers.
*/
return (-1);
}
return (-1);
}
/*
* because we support only one version and it is represented by
* the "01" string. The received version has to be "01" string as well.
*/
return (-1);
}
/*
* as an application_data field in the gss_channel_bindings_struct
* structure.
*/
if (ver_str_concat == NULL) {
return (-1);
}
return (0);
}
/*
* sock_prepare() - creates and connects socket. Function returns
* reason of failure.
*/
static boolean_t
{
int sock;
switch (host->h_addrtype) {
case AF_INET:
addr_len = sizeof (struct sockaddr_in);
break;
case AF_INET6:
addr_len = sizeof (struct sockaddr_in6);
break;
default:
/* unknown address family */
return (B_FALSE);
}
return (B_FALSE);
}
return (B_FALSE);
}
*sockfdptr));
return (B_TRUE);
}
/*
*
* Note: connection must be established and version negotiated (in plain text)
* prior to establishing context.
*/
static int
{
char *svc_name;
/* GSS service name = gss_svc_name + "@" + remote hostname (fqdn) */
return (-1);
}
if (maj_stat != GSS_S_COMPLETE) {
min_stat);
return (-1);
}
/* initialize channel binding */
(void) pthread_mutex_lock(&gss_ctx_lock);
do {
if (token_ptr != GSS_C_NO_BUFFER) {
}
"Sending init_sec_context token (size=%d)\n",
(void) pthread_mutex_unlock(&gss_ctx_lock);
return (-1);
}
}
}
if (maj_stat != GSS_S_COMPLETE &&
if (gss_ctx == GSS_C_NO_CONTEXT) {
(void) gss_delete_sec_context(&min_stat,
}
(void) pthread_mutex_unlock(&gss_ctx_lock);
return (-1);
}
if (maj_stat == GSS_S_CONTINUE_NEEDED) {
(void) pthread_mutex_unlock(&gss_ctx_lock);
return (-1);
}
}
} while (maj_stat == GSS_S_CONTINUE_NEEDED);
(void) pthread_mutex_unlock(&gss_ctx_lock);
return (0);
}
/*
* delete_context() - release GSS context.
*/
static void
{
}
/*
* send_token() - send GSS token over the wire.
*/
static int
{
char *out_buf;
int fd;
(void) pthread_mutex_lock(&sock_lock);
if (*fdptr == -1) {
(void) pthread_mutex_unlock(&sock_lock);
return (-1);
}
(void) pthread_mutex_unlock(&sock_lock);
return (-1);
}
return (-1);
}
return (0);
}
/*
* recv_token() - receive GSS token over the wire.
*/
static int
{
return (-1);
}
/* simple DOS prevention mechanism */
if (len > MAX_TOK_LEN) {
return (-1);
}
return (-1);
}
return (-1);
}
return (0);
}
/*
* I/O functions
*/
/*
* connect_timeout() - sets nonblocking I/O on a socket and timeout-connects
*/
static int
{
int flags;
int rc;
return (-1);
}
errno == EWOULDBLOCK)) {
return (-1);
}
}
for (;;) {
if (rc == 0) { /* timeout */
return (-1);
} else if (rc < 0) {
continue;
} else {
return (-1);
}
}
&addr_len))
return (-1);
} else {
return (-1);
}
return (0);
}
}
/*
* send_timeout() - send data (in chunks if needed, each chunk in timeout secs).
*/
static int
{
int bytes;
int rc;
while (len) {
if (rc == 0) { /* timeout */
return (-1);
} else if (rc < 0) {
continue;
} else {
return (-1);
}
}
return (-1);
}
if (bytes < 0) {
continue;
} else {
return (-1);
}
} else if (bytes == 0) { /* eof */
return (-1);
}
}
return (0);
}
/*
* recv_timeout() - receive data (in chunks if needed, each chunk in timeout
* secs). In case the function is called from receiving thread, the function
* cycles the poll() call in timeout seconds (waits for input from server).
*/
static int
{
int bytes;
int rc;
while (len) {
if (rc == 0) { /* timeout */
return (-1);
} else if (rc < 0) {
continue;
} else {
return (-1);
}
}
return (-1);
}
if (bytes < 0) {
continue;
} else {
return (-1);
}
} else if (bytes == 0) { /* eof */
return (-1);
}
}
return (0);
}
/*
* read_fd() - reads data of length len from the given file descriptor fd to the
* buffer buf, in chunks if needed. Function returns B_FALSE on failure,
* otherwise B_TRUE. Function preserves errno, if it was set by the read(2).
*/
static boolean_t
{
int bytes;
#ifdef DEBUG
#endif
while (len) {
if (bytes < 0) { /* err */
continue;
} else {
return (B_FALSE);
}
} else if (bytes == 0) { /* eof */
return (B_FALSE);
}
}
return (B_TRUE);
}
/*
* write_fd() - writes buf of length len to the opened file descriptor fd, in
* chunks if needed. The data from the pipe are processed in the receiving
* thread. Function returns B_FALSE on failure, otherwise B_TRUE. Function
* preserves errno, if it was set by the write(2).
*/
static boolean_t
{
int bytes;
#ifdef DEBUG
#endif
while (len) {
continue;
} else {
return (B_FALSE);
}
}
}
return (B_TRUE);
}
/*
* Plug-in entry point
*/
/*
* send_record() - send an audit record to a host opening a connection,
* negotiate version and establish context if necessary.
*/
{
int conf_state;
int rc;
/*
* We need to grab the reset_lock here, to prevent eventual
* unsynchronized cleanup calls within the reset routine (reset caused
* by the receiving thread) and the initialization calls in the
* send_record() code path.
*/
(void) pthread_mutex_lock(&reset_lock);
/*
* Check whether the socket was closed by the recv thread prior to call
* send_record() and behave accordingly to the reason of the closure.
*/
if (recv_closure_rsn != RSN_UNDEFINED) {
if (recv_closure_rsn == RSN_GSS_CTX_EXP) {
} else {
}
(void) pthread_mutex_unlock(&reset_lock);
return (rc);
}
/*
* Send request to other then previously used host.
*/
if (sockfd != -1) {
(void) pthread_mutex_unlock(&reset_lock);
return (SEND_RECORD_RETRY);
}
}
/* initiate the receiving thread */
/* create and connect() socket, negotiate the protocol version */
if (sockfd == -1) {
/* socket operations */
/* we believe the err_rsn set by sock_prepare() */
(void) pthread_mutex_unlock(&reset_lock);
return (SEND_RECORD_NEXT);
}
/* protocol version negotiation */
if (prot_ver_negotiate() != 0) {
"Protocol version negotiation failed\n"));
(void) pthread_mutex_unlock(&reset_lock);
return (SEND_RECORD_NEXT);
}
/* let the socket be initiated for poll() */
}
if (!gss_ctx_initialized) {
if (establish_context() != 0) {
(void) pthread_mutex_unlock(&reset_lock);
return (SEND_RECORD_NEXT);
}
}
/* let the recv thread poll() on the sockfd */
if (init_sock_poll) {
*err_rsn = RSN_INIT_POLL;
(void) pthread_mutex_unlock(&reset_lock);
return (SEND_RECORD_RETRY);
}
}
(void) pthread_mutex_unlock(&reset_lock);
/* if not empty, retransmit contents of the transmission queue */
if (flush_transq) {
return (SEND_RECORD_RETRY);
} else if (rc == 1) {
*err_rsn = RSN_OTHER_ERR;
return (SEND_RECORD_NEXT);
}
}
/*
* Concatenate sequence number and the new record. Note, that the
* pointer to the chunk of memory allocated for the concatenated values
* is later passed to the transq_enqueu() function which stores the
* pointer in the transmission queue; subsequently called
* transq_dequeue() frees the allocated memory once the MIC is verified
* by the recv_record() function.
*
* If we return earlier than the transq_enqueue() is called, it's
* necessary to free the in_buf.value explicitly prior to return.
*
*/
return (SEND_RECORD_FAIL);
}
/* wrap sequence number and the new record to the per-message token */
(void) pthread_mutex_lock(&gss_ctx_lock);
(void) pthread_mutex_unlock(&gss_ctx_lock);
switch (maj_stat) {
case GSS_S_COMPLETE:
break;
case GSS_S_CONTEXT_EXPIRED:
return (SEND_RECORD_RETRY);
default:
min_stat);
*err_rsn = RSN_OTHER_ERR;
return (SEND_RECORD_NEXT);
}
} else { /* GSS context deleted by the recv thread */
(void) pthread_mutex_unlock(&gss_ctx_lock);
*err_rsn = RSN_OTHER_ERR;
return (SEND_RECORD_NEXT);
}
/* enqueue the to-be-sent token into transmission queue */
(void) pthread_mutex_lock(&transq_lock);
(void) pthread_mutex_unlock(&transq_lock);
*err_rsn = RSN_OTHER_ERR;
return (SEND_RECORD_RETRY);
}
(void) pthread_mutex_unlock(&transq_lock);
/* send token */
(void) pthread_mutex_lock(&transq_lock);
(void) pthread_mutex_unlock(&transq_lock);
*err_rsn = RSN_OTHER_ERR;
return (SEND_RECORD_NEXT);
}
return (SEND_RECORD_SUCCESS);
}
/*
* init_recv_record() - initialize the receiver thread
*/
static void
{
(void *)NULL);
}
/*
* recv_record() - the receiver thread routine
*/
static void
{
int fds_cnt;
int rc;
/*
* Fill in the information in the vector of file descriptors passed
* later on to the poll() function. In the initial state, there is only
* one struct pollfd in the vector which contains file descriptor of the
* notification pipe - notify_pipe[1]. There might be up to two file
* descriptors (struct pollfd) in the vector - notify_pipe[1] which
* resides in the vector during the entire life of the receiving thread,
* and the own file descriptor from which we read data sent by the
* remote server application.
*/
fds_cnt = 1;
/*
* In the endless loop, try to grab some data from the socket or
* notify_pipe[1].
*/
for (;;) {
/* block on poll, thus rc != 0 */
if (rc == -1) {
/* silently continue on EAGAIN || EINTR */
continue;
} else {
/* log the debug message in any other case */
continue;
}
}
/*
* Receive a message from the notification pipe. Information
* from the notification pipe takes precedence over the received
* data from the remote server application.
*
* Notification pipe message format - message accepted
* from the notify pipe comprises of two parts (int ||
* boolean_t), where if the first part (sizeof (int)) equals
* NP_CLOSE, then the second part (sizeof (boolean_t)) signals
* the necessity of broadcasting (DO_SYNC/DO_NOT_SYNC) the end
* of the reset routine.
*/
sizeof (np_data))) {
"failed"));
} else {
case NP_EXIT: /* exit receiving thread */
pthread_exit((void *)NULL);
break;
case NP_CLOSE: /* close and remove recv_fd */
continue;
default: /* add rc_pipe to the fds */
fds_cnt = 2;
continue;
}
}
}
/* Receive a token from the remote server application */
continue;
}
/* simple DOS prevention mechanism */
if (len > MAX_TOK_LEN) {
"length"));
MAX_TOK_LEN));
continue;
}
continue;
}
continue;
}
}
/*
* Extract the sequence number and the MIC from
* the per-message token
*/
/*
* be unique in the transmission queue. Any token in the
* transmission queue with the same seq_num as the acknowledge
* token received from the server is tested. This is due to the
* fact that the plugin cannot influence (in the current
* implementation) sequence numbers generated by the kernel (we
* are reusing record sequence numbers as a transmission queue
* sequence numbers). The probability of having two or more
* tokens in the transmission queue is low and at the same time
* the performance gain due to using sequence numbers is quite
* high.
*
* In case a harder condition with regard to duplicate sequence
* numbers in the transmission queue will be desired over time,
* the break_flag behavior used below should be
*/
(void) pthread_mutex_lock(&transq_lock);
continue;
}
(void) pthread_mutex_lock(&gss_ctx_lock);
&qop_state);
(void) pthread_mutex_unlock(&gss_ctx_lock);
switch (maj_stat) {
/*
* All the GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN,
* GSS_S_GAP_TOKEN are perceived as correct
* behavior of the server side. The plugin
* implementation is resistant to any of the
* above mention cases of returned status codes.
*/
/*FALLTHRU*/
case GSS_S_OLD_TOKEN:
case GSS_S_UNSEQ_TOKEN:
case GSS_S_GAP_TOKEN:
case GSS_S_COMPLETE:
/*
* the transmission queue
*/
"the token (transq len = %ld)\n",
transq_hdr.count));
break_flag = B_TRUE;
break;
/*
* Both the default case as well as
* GSS_S_DUPLICATE_TOKEN case should never
* occur. It's been left here for the sake of
* completeness.
* If any of the two cases occur, it is
* subsequently cought because we don't set
* the token_verified flag.
*/
/*FALLTHRU*/
case GSS_S_DUPLICATE_TOKEN:
default:
break_flag = B_TRUE;
break;
} /* switch (maj_stat) */
} else { /* the failure case */
gettext("signature verification of the "
"received token failed"),
switch (maj_stat) {
case GSS_S_CONTEXT_EXPIRED:
/* retransmission necessary */
break_flag = B_TRUE;
"the GSS context expiration\n"));
break;
case GSS_S_BAD_SIG:
"detected (seq_num = %lld)\n",
break;
default:
gettext("signature verification"),
break_flag = B_TRUE;
break;
}
}
} /* while */
(void) pthread_mutex_unlock(&transq_lock);
}
if (!token_verified) {
/*
* Received, but unverifiable token is perceived as
* the protocol flow corruption with the penalty of
*/
if (recv_closure_rsn == RSN_UNDEFINED) {
}
}
} /* for (;;) */
}
/*
* init_poll() - initiates the polling in the receiving thread via sending the
* appropriate message over the notify pipe. Message format = (int ||
* booleant_t), where the first part (sizeof (int)) contains the
* newly_opened/to_be_polled socket file descriptor. The contents of the second
* part (sizeof (boolean_t)) of the message works only as a padding here and no
* based on its value.
*/
static boolean_t
{
return (B_FALSE);
}
return (B_TRUE);
}
/*
* reset_transport() - locked by the reset_lock initiates the reset of socket,
* GSS security context and (possibly) flags the transq for retransmission; for
* more detailed information see do_reset(). The reset_transport() also allows
* the synchronization - waiting for the reset to be finished.
*
* do_close: DO_EXIT (DO_NOT_CLOSE), DO_CLOSE (DO_NOT_EXIT)
* sync_on_return: DO_SYNC, DO_NOT_SYNC
*
*/
void
{
/*
* Check if the reset routine is in progress or whether it was already
* executed by some other thread.
*/
(void) pthread_mutex_lock(&reset_lock);
if (!recv_thread_up || reset_in_progress) {
(void) pthread_mutex_unlock(&reset_lock);
return;
}
(void) pthread_mutex_unlock(&reset_lock);
return;
}
if (sync_on_return) {
while (reset_in_progress) {
}
}
(void) pthread_mutex_unlock(&reset_lock);
}
/*
* do_reset() - the own reseting routine called from the recv thread. If the
* synchronization was requested, signal the finish via conditional variable.
*/
static void
{
(void) pthread_mutex_lock(&reset_lock);
/* socket */
(void) pthread_mutex_lock(&sock_lock);
if (sockfd == -1) {
(void) pthread_mutex_unlock(&sock_lock);
goto out;
} else {
sockfd = -1;
(void) pthread_mutex_unlock(&sock_lock);
}
*fds_cnt = 1;
if (ver_str_concat != NULL) {
}
/* context */
if (gss_ctx_initialized) {
}
/* mark transq to be flushed */
(void) pthread_mutex_lock(&transq_lock);
if (transq_hdr.count > 0) {
}
(void) pthread_mutex_unlock(&transq_lock);
out:
if (do_signal) {
(void) pthread_cond_broadcast(&reset_cv);
}
(void) pthread_mutex_unlock(&reset_lock);
}
/*
* do_cleanup() - removes all the preallocated space by the plugin; prepares the
* plugin/application to be gracefully finished. Even thought the function
* allows execution without signalling the successful finish, it's recommended
* to use it (we usually want to wait for cleanup before exiting).
*/
static void
{
(void) pthread_mutex_lock(&reset_lock);
/*
* socket
* note: keeping locking for safety, thought it shouldn't be necessary
* in current implementation - we get here only in case the sending code
* path calls auditd_plugin_close() (thus no socket manipulation) and
* the recv thread is doing the own socket closure.
*/
(void) pthread_mutex_lock(&sock_lock);
if (sockfd != -1) {
sockfd = -1;
}
*fds_cnt = 1;
(void) pthread_mutex_unlock(&sock_lock);
if (ver_str_concat != NULL) {
}
/* context */
(void) pthread_mutex_lock(&gss_ctx_lock);
if (gss_ctx_initialized) {
}
(void) pthread_mutex_unlock(&gss_ctx_lock);
/* transmission queue */
(void) pthread_mutex_lock(&transq_lock);
if (transq_hdr.count > 0) {
while (transq_hdr.count > 0) {
}
}
(void) pthread_mutex_unlock(&transq_lock);
/* notification pipe */
if (notify_pipe_ready) {
(void) close(notify_pipe[0]);
}
if (do_signal) {
(void) pthread_cond_broadcast(&reset_cv);
}
(void) pthread_mutex_unlock(&reset_lock);
}
/*
* transq_dequeue() - dequeues given node pointed by the node_ptr from the
* transmission queue. Transmission queue should be locked prior to use of this
* function.
*/
static void
{
return;
}
}
}
/* update the transq_hdr */
}
}
transq_hdr.count--;
}
/*
* transq_enqueue() - creates new node in (at the end of) the transmission
* queue. in_ptoken_ptr is a pointer to the plain token in a form of
* gss_buffer_desc. Function returns 0 on success and updates the *node_ptr to
* point to a newly added transmission queue node. In case of any failure
* function returns 1 and sets the *node_ptr to NULL.
* Transmission queue should be locked prior to use of this function.
*/
static boolean_t
{
goto errout;
}
/* value of the seq_token.value = (sequence number || plain token) */
/* update the transq_hdr */
}
}
transq_hdr.count++;
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* transq_retransmit() - traverse the transmission queue and try to, 1 by 1,
* re-wrap the tokens with the recent context information and retransmit the
* tokens from the transmission queue.
* Function returns 2 on GSS context expiration, 1 on any other error, 0 on
* successfully resent transmission queue.
*/
static int
{
int conf_state;
(void) pthread_mutex_lock(&transq_lock);
(void) pthread_mutex_lock(&gss_ctx_lock);
(void) pthread_mutex_unlock(&gss_ctx_lock);
switch (maj_stat) {
case GSS_S_COMPLETE:
break;
case GSS_S_CONTEXT_EXPIRED:
min_stat);
(void) pthread_mutex_unlock(&transq_lock);
return (2);
default:
min_stat);
(void) pthread_mutex_unlock(&transq_lock);
return (1);
}
(void) pthread_mutex_unlock(&transq_lock);
return (1);
}
(void) pthread_mutex_unlock(&transq_lock);
} /* while */
return (0);
}