--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/proftpd/mod_solaris_audit.c Wed Jun 15 01:09:08 2011 -0700
@@ -0,0 +1,1173 @@
+/*
+ * ProFTPD - FTP server daemon
+ * Copyright (c) 2011, Oracle and/or its affiliates. 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.
+ *
+ * As a special exemption, copyright holders give permission to link
+ * this program with OpenSSL, and distribute the resulting executable,
+ * without including the source code for OpenSSL in the source distribution.
+ *
+ */
+
+#include "conf.h"
+#include <bsm/adt.h>
+#include <bsm/adt_event.h>
+#include <security/pam_appl.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <ucred.h>
+
+#ifndef ADT_ftpd
+#define ADT_ftpd 152
+#endif
+
+#ifndef ADT_ftpd_logout
+#define ADT_ftpd_logout 153
+#endif
+
+module solaris_audit_module;
+
+static adt_session_data_t *asession = NULL;
+
+static int auth_retval = PAM_AUTH_ERR;
+
+static void audit_autherr_ev(const void *event_data, void *user_data) {
+
+ switch (*(int *)event_data) {
+ case PR_AUTH_NOPWD:
+ auth_retval = PAM_USER_UNKNOWN;
+ break;
+ case PR_AUTH_AGEPWD:
+ auth_retval = PAM_CRED_EXPIRED;
+ break;
+ case PR_AUTH_DISABLEDPWD:
+ auth_retval = PAM_ACCT_EXPIRED;
+ break;
+ case PR_AUTH_CRED_INSUFF:
+ auth_retval = PAM_CRED_INSUFFICIENT;
+ break;
+ case PR_AUTH_CRED_UNAVAIL:
+ auth_retval = PAM_CRED_UNAVAIL;
+ break;
+ case PR_AUTH_CRED_ERR:
+ auth_retval = PAM_CRED_ERR;
+ break;
+ case PR_AUTH_UNAVAIL:
+ auth_retval = PAM_AUTHINFO_UNAVAIL;
+ break;
+ case PR_AUTH_MAXTRIES:
+ auth_retval = PAM_MAXTRIES;
+ break;
+ case PR_AUTH_INIT_FAIL:
+ auth_retval = PAM_SESSION_ERR;
+ break;
+ case PR_AUTH_NEWTOK:
+ auth_retval = PAM_NEW_AUTHTOK_REQD;
+ break;
+ case PR_AUTH_OPEN_ERR:
+ auth_retval = PAM_OPEN_ERR;
+ break;
+ case PR_AUTH_SYMBOL_ERR:
+ auth_retval = PAM_SYMBOL_ERR;
+ break;
+ case PR_AUTH_SERVICE_ERR:
+ auth_retval = PAM_SERVICE_ERR;
+ break;
+ case PR_AUTH_SYSTEM_ERR:
+ auth_retval = PAM_SYSTEM_ERR;
+ break;
+ case PR_AUTH_BUF_ERR:
+ auth_retval = PAM_BUF_ERR;
+ break;
+ case PR_AUTH_CONV_ERR:
+ auth_retval = PAM_CONV_ERR;
+ break;
+ case PR_AUTH_PERM_DENIED:
+ auth_retval = PAM_PERM_DENIED;
+ break;
+ default: /* PR_AUTH_BADPWD */
+ auth_retval = PAM_AUTH_ERR;
+ break;
+ }
+
+}
+
+static void audit_failure(pool *p, char *authuser) {
+ adt_event_data_t *event = NULL;
+ const char *how;
+ int saved_errno = 0;
+ struct passwd pwd;
+ char *pwdbuf = NULL;
+ size_t pwdbuf_len;
+ long pwdbuf_len_max;
+ uid_t uid = ADT_NO_ATTRIB;
+ gid_t gid = ADT_NO_ATTRIB;
+
+ if ((pwdbuf_len_max = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) {
+ saved_errno = errno;
+ how = "couldn't determine maximum size of password buffer";
+ goto fail;
+ }
+
+ pwdbuf_len = (size_t)pwdbuf_len_max;
+ pwdbuf = pcalloc(p, pwdbuf_len);
+
+ if (adt_start_session(&asession, NULL, ADT_USE_PROC_DATA) != 0) {
+ saved_errno = errno;
+ how = "couldn't start adt session";
+ goto fail;
+ }
+
+ if ((authuser != NULL) && (authuser[0] != NULL) &&
+ (getpwnam_r(authuser, &pwd, pwdbuf, pwdbuf_len) != NULL)) {
+ uid = pwd.pw_uid;
+ gid = pwd.pw_gid;
+ }
+
+ if (adt_set_user(asession, uid, gid, uid, gid, NULL, ADT_NEW) != 0) {
+ saved_errno = errno;
+ how = "couldn't set adt user";
+ goto fail;
+ }
+
+ if ((event = adt_alloc_event(asession, ADT_ftpd)) == NULL) {
+ saved_errno = errno;
+ how = "couldn't allocate adt event";
+ goto fail;
+ }
+
+ if (adt_put_event(event, ADT_FAILURE, ADT_FAIL_PAM + auth_retval) != 0) {
+ saved_errno = errno;
+ how = "couldn't put adt event";
+ goto fail;
+ }
+
+ adt_free_event(event);
+ (void) adt_end_session(asession);
+ asession = NULL;
+ return;
+
+fail:
+ pr_log_pri(PR_LOG_ERR, "Auditing of login failed: %s (%s)", how,
+ strerror(saved_errno));
+
+ adt_free_event(event);
+ (void) adt_end_session(asession);
+ asession = NULL;
+}
+
+static void audit_success(void) {
+ adt_event_data_t *event = NULL;
+ const char *how;
+ int saved_errno = 0;
+
+ if (adt_start_session(&asession, NULL, ADT_USE_PROC_DATA) != 0) {
+ saved_errno = errno;
+ how = "couldn't start adt session";
+ goto fail;
+ }
+
+ if ((event = adt_alloc_event(asession, ADT_ftpd)) == NULL) {
+ saved_errno = errno;
+ how = "couldn't allocate adt event";
+ goto fail;
+ }
+
+ if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
+ saved_errno = errno;
+ how = "couldn't put adt event";
+ goto fail;
+ }
+
+ adt_free_event(event);
+
+ /* Don't end adt session - leave for when logging out. */
+ return;
+
+fail:
+ pr_log_pri(PR_LOG_ERR, "Auditing of login failed: %s (%s)", how,
+ strerror(saved_errno));
+
+ adt_free_event(event);
+
+ /* Don't end adt session - leave for when logging out. */
+
+}
+
+static void audit_logout(void) {
+ adt_event_data_t *event = NULL;
+ const char *how;
+ int saved_errno = 0;
+
+ /* If audit session was not created during login then leave */
+ if (asession == NULL)
+ return;
+
+ if ((event = adt_alloc_event(asession, ADT_ftpd_logout)) == NULL) {
+ saved_errno = errno;
+ how = "couldn't allocate adt event";
+ goto fail;
+ }
+
+ if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
+ saved_errno = errno;
+ how = "couldn't put adt event";
+ goto fail;
+ }
+
+ adt_free_event(event);
+ (void) adt_end_session(asession);
+ asession = NULL;
+ return;
+
+fail:
+ pr_log_pri(PR_LOG_ERR, "Auditing of logout failed: %s (%s)", how,
+ strerror(saved_errno));
+
+ adt_free_event(event);
+ (void) adt_end_session(asession);
+ asession = NULL;
+}
+
+/* Logout */
+static void audit_exit_ev(const void *event_data, void *user_data) {
+ audit_logout();
+}
+
+/* Login passed */
+MODRET solaris_audit_post_pass(cmd_rec *cmd) {
+
+ audit_success();
+
+ /* Set handler for logout/timeout */
+ pr_event_register(&solaris_audit_module, "core.exit", audit_exit_ev, NULL);
+
+ return PR_DECLINED(cmd);
+}
+
+/* Login failed */
+MODRET solaris_audit_post_fail(cmd_rec *cmd) {
+ char *login_user;
+
+ login_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
+
+ audit_failure(cmd->tmp_pool, login_user);
+ return PR_DECLINED(cmd);
+}
+
+static int audit_sess_init(void) {
+ adt_session_data_t *aht;
+ adt_termid_t *termid;
+ priv_set_t *privset;
+ int rval = -1;
+
+ /* add privs for audit init */
+ if ((privset = priv_allocset()) == NULL) {
+ pr_log_pri(PR_LOG_ERR, "Auditing privilege initialization failed");
+ return rval;
+ }
+ (void) getppriv(PRIV_EFFECTIVE, privset);
+ priv_addset(privset, PRIV_SYS_AUDIT);
+ (void) setppriv(PRIV_SET, PRIV_EFFECTIVE, privset);
+
+ /* basic terminal id setup */
+ if (adt_start_session(&aht, NULL, 0) != 0) {
+ pr_log_pri(PR_LOG_ERR, "pam adt_start_session: %s", strerror(errno));
+ goto out;
+ }
+ if (adt_load_termid(session.c->rfd, &termid) != 0) {
+ pr_log_pri(PR_LOG_ERR, "adt_load_termid: %s", strerror(errno));
+ (void) adt_end_session(aht);
+ goto out;
+ }
+
+ if (adt_set_user(aht, ADT_NO_AUDIT, ADT_NO_AUDIT, 0, ADT_NO_AUDIT, termid,
+ ADT_SETTID) != 0) {
+ pr_log_pri(PR_LOG_ERR, "adt_set_user: %", strerror(errno));
+ free(termid);
+ (void) adt_end_session(aht);
+ goto out;
+ }
+ free(termid);
+ if (adt_set_proc(aht) != 0) {
+ pr_log_pri(PR_LOG_ERR, "adt_set_proc: %", strerror(errno));
+ (void) adt_end_session(aht);
+ goto out;
+ }
+ (void) adt_end_session(aht);
+
+ /* Set handler for authentication error */
+ pr_event_register(&solaris_audit_module, "mod_auth.authentication-code",
+ audit_autherr_ev, NULL);
+
+ rval = 0;
+
+out:
+
+ /* remove unneeded privileges */
+ priv_delset(privset, PRIV_SYS_AUDIT);
+ (void) setppriv(PRIV_SET, PRIV_EFFECTIVE, privset);
+ (void) setpflags(PRIV_AWARE_RESET, 1);
+ priv_freeset(privset);
+
+ return rval;
+}
+
+#define EVENT_KEY "event"
+
+/* Helper functions and global variables
+ * for the file transfer command handlers.
+ * {
+ */
+
+static char src_realpath[PATH_MAX];
+static char dst_realpath[PATH_MAX];
+
+
+/*
+ * If an error occurs in any of the file transfer handlers,
+ * and the handler wants to return PR_ERROR(cmd), then it is necessary
+ * to send some FTP error message to user. This is in order to prevent
+ * a hang-up of the user's ftp client.
+ *
+ * This function sends the 451 error message to the user.
+ * It is only called in the "pre-" handlers. When a "pre-" handler
+ * returns PR_ERROR(cmd), then the corresponding "post_err-"
+ * handler is also called. Therefore it can happen that an error condition
+ * (such as no memory) can be logged (with the pr_log_pri() routine) twice.
+ * Once in the "pre-" handler, and once in the "post_err-" handler.
+ */
+static void error_451(void)
+{
+ pr_response_add_err(R_451,
+ "Requested action aborted: local error in processing.\n");
+}
+
+/*
+ * Allocate resources to process a command outcome.
+ *
+ * All file transfer command handlers need to allocate adt_event_data_t
+ * structure and also make a copy of the command argument.
+ * This function does both. If it can't, it logs an error and returns NULL.
+ * On success, it returns the pointer (event) to the allocated adt_event_data_t
+ * structure.
+ *
+ * If arg2 is not NULL, it makes a copy of the first (and only) command
+ * argument (using the memory pool "pool" from "cmd") and stores it to *arg2.
+ * There must be always exactly one command argument, otherwise it is an error.
+ *
+ * On success, the pointer to the created event structure is stored
+ * into cmd under "notes" variable, so that it is accessible
+ * by the subsequent corresponding "post-" or "post_err-" command handler.
+ */
+adt_event_data_t* __solaris_audit_pre_arg2(
+ cmd_rec *cmd, const char* description, int event_type, char **arg2) {
+
+ adt_event_data_t *event = NULL;
+ const char *how = "";
+ char *tmp = NULL;
+
+ /* The ftp server code will save errno into this variable
+ * in case an error happens, and there is a valid errno for it.
+ */
+ cmd->error_code = ADT_FAILURE;
+
+ if (cmd->argc != 2) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s failed: %s",
+ description, "bad arguments");
+ goto err;
+ }
+
+ if (arg2 != NULL) {
+ *arg2 = NULL;
+
+ if ((tmp = pstrdup(cmd->pool, cmd->argv[1])) == NULL) {
+ how = "no memory";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ description, cmd->argv[1], how);
+ goto err;
+ }
+ *arg2 = tmp;
+ }
+
+ if (cmd->notes == NULL ) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ description, cmd->argv[1], "API error, notes is NULL");
+ goto err;
+ }
+
+ if ((event = adt_alloc_event(asession, event_type)) == NULL) {
+ how = "couldn't allocate adt event";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s(%s)",
+ description, cmd->argv[1], how, strerror(errno));
+ goto err;
+ }
+
+ if (pr_table_add(cmd->notes, EVENT_KEY, event, sizeof(*event))==-1) {
+ how = "pr_table_add() failed";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ description, cmd->argv[1], how);
+ adt_free_event(event);
+ goto err;
+ }
+
+ return event;
+
+err:
+ return NULL;
+}
+
+/*
+ * This function implements logic that is common to most "post-"
+ * and "post_err-" file transfer command handlers.
+ *
+ * It retrieves the pointer (event) to the adt_event_data_t structure
+ * from "cmd->notes" and logs it. This structure has been created by the
+ * __solaris_audit_pre_arg2() function.
+ *
+ * Some audit event structures contain an optional *_stat member.
+ * If "fill_attr" is not NULL, it is called to fill in this member,
+ * before the audit event is logged.
+ *
+ * This function always returns PR_DECLINED, even if it failed
+ * to log the audit event. The reason is that it is called in the
+ * "post-" file transfer command handlers, which means that the command
+ * has been already successfully executed by the ftp server.
+ */
+MODRET __solaris_audit_post(cmd_rec *cmd,
+ const char* description, int exit_status, int __unused,
+ const char* (*fill_event)(cmd_rec *cmd, adt_event_data_t *event))
+{
+ adt_event_data_t *event = NULL;
+ const char* how = "";
+ const char* msg = NULL;
+ size_t size = 0;
+ int exit_error = cmd->error_code;
+
+ event = (adt_event_data_t*)pr_table_remove(cmd->notes, EVENT_KEY, &size);
+ if (event == NULL) {
+ how = "event is NULL";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s failed: %s", description, how);
+ goto out;
+ }
+
+ if (size != sizeof(*event)) {
+ how = "bad event size";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s failed: %s", description, how);
+ goto out;
+ }
+
+ if (fill_event != NULL) {
+ msg = fill_event(cmd, event);
+ if (msg != NULL) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s failed: %s", description, msg);
+ goto out;
+ }
+ }
+
+ /* It can happen, that the ftp command succeeds but only to some degree.
+ * In such case, the exit_error might contain the errno number
+ * of the failure.
+ */
+ if (exit_status == ADT_SUCCESS) {
+ if (exit_error == ADT_FAILURE)
+ exit_error = ADT_SUCCESS;
+ }
+
+ if (adt_put_event(event, exit_status, exit_error) != 0) {
+ how = "couldn't put adt event";
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s failed: %s (%s)",
+ description, how, strerror(errno));
+ }
+
+ adt_free_event(event);
+
+out:
+ return PR_DECLINED(cmd);
+}
+
+/*
+ * This is a generic function to fill in the given "stat" member
+ * of some audit event structure. The path and the member are specified
+ * by the caller. The pointer to cmd is supplied, because the stat64
+ * structure has to be allocated (the "stat" member is a pointer).
+ *
+ * The function returns NULL on success.
+ * In case of an error, it returns a descriptive message.
+ * This message is used by the caller to log an error.
+ *
+ * For some file transfer commands, the "stat" member is filled in
+ * the "pre-" handler (because the file is expected to exist prior
+ * to the execution of the command). For other file transfer commands,
+ * the "stat" member is filled in the "post-" handler (because
+ * the file is expected _not_ to exist prior to the execution of the command,
+ * but to exist after the command execution).
+ */
+static const char* __fill_attr
+(
+ cmd_rec *cmd, const char* path, adt_stat_t **ret)
+{
+ struct stat64 *ptr;
+ int err;
+
+ if (ret == NULL)
+ return "NULL pointer";
+
+ *ret = NULL;
+
+ ptr = palloc(cmd->pool, sizeof(*ptr));
+ if (ptr == NULL)
+ return "no memory";
+
+ err = stat64(path, ptr);
+ if (err == -1)
+ return "stat64() failed";
+
+ *ret = ptr;
+ return NULL;
+}
+/* } */
+
+
+/* Delete file. { */
+static const char* dele_fill_attr(cmd_rec *cmd, adt_event_data_t *event) {
+ return __fill_attr(
+ cmd, event->adt_ft_remove.f_path, &(event->adt_ft_remove.f_attr)
+ );
+}
+
+MODRET solaris_audit_pre_dele(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+ char* rp = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "remove", ADT_ft_remove, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ rp = realpath(ptr, src_realpath);
+ if (rp == NULL) {
+ if (errno != ENOENT) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "remove", ptr, "realpath() failed");
+ cmd->error_code = errno;
+ error_451();
+ return PR_ERROR(cmd);
+ }
+ /* If rp is NULL and errno is ENOENT, it means that
+ * the file to be deleted does not exist. In this case,
+ * the post_dele_err callback will be called to log this.
+ */
+ }
+
+ if (rp != NULL)
+ ptr = rp;
+
+ event->adt_ft_remove.f_path = ptr;
+ (void) dele_fill_attr(cmd, event);
+
+ return PR_DECLINED(cmd);
+}
+
+MODRET solaris_audit_post_dele(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "remove", ADT_SUCCESS, ADT_SUCCESS, NULL);
+}
+
+MODRET solaris_audit_post_dele_err(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "remove", ADT_FAILURE, ADT_FAILURE, NULL);
+}
+/* } */
+
+
+/* Make directory. { */
+MODRET solaris_audit_pre_mkd(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "mkdir", ADT_ft_mkdir, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ event->adt_ft_mkdir.d_path = ptr;
+ event->adt_ft_mkdir.d_attr = NULL;
+
+ /* Value 0777 is hardcoded in the ftp server. */
+ event->adt_ft_mkdir.arg = 0777;
+ event->adt_ft_mkdir.arg_id = 2;
+ event->adt_ft_mkdir.arg_desc = "mode";
+
+ return PR_DECLINED(cmd);
+}
+
+static const char* mkd_fill_event(cmd_rec *cmd, adt_event_data_t *event) {
+ char *rp = NULL;
+
+ rp = realpath(event->adt_ft_mkdir.d_path, src_realpath);
+ if (rp == NULL) {
+ cmd->error_code = errno;
+ return "realpath() failed";
+ }
+
+ event->adt_ft_mkdir.d_path = rp;
+ return __fill_attr(
+ cmd, event->adt_ft_mkdir.d_path, &(event->adt_ft_mkdir.d_attr)
+ );
+}
+
+static const char* mkd_fill_event_err(cmd_rec *cmd, adt_event_data_t *event) {
+ char *rp = NULL;
+
+ rp = realpath(event->adt_ft_mkdir.d_path, src_realpath);
+ if (rp != NULL) {
+ event->adt_ft_mkdir.d_path = rp;
+ (void) __fill_attr(
+ cmd, event->adt_ft_mkdir.d_path, &(event->adt_ft_mkdir.d_attr));
+ }
+
+ return NULL;
+}
+
+MODRET solaris_audit_post_mkd(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "mkdir", ADT_SUCCESS, ADT_SUCCESS, mkd_fill_event);
+}
+
+MODRET solaris_audit_post_mkd_err(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "mkdir", ADT_FAILURE, ADT_FAILURE, mkd_fill_event_err);
+}
+/* } */
+
+/* Remove directory. { */
+static const char* rmd_fill_attr(cmd_rec *cmd, adt_event_data_t *event) {
+ return __fill_attr(
+ cmd, event->adt_ft_rmdir.f_path, &(event->adt_ft_rmdir.f_attr)
+ );
+}
+
+MODRET solaris_audit_pre_rmd(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+ char* rp = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "rmdir", ADT_ft_rmdir, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ rp = realpath(ptr, src_realpath);
+ if (rp == NULL) {
+ if (errno != ENOENT) {
+ cmd->error_code = errno;
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "rmdir", ptr, "realpath() failed");
+ error_451();
+ return PR_ERROR(cmd);
+ }
+ }
+
+ if (rp != NULL)
+ ptr = rp;
+
+ event->adt_ft_rmdir.f_path = ptr;
+ (void) rmd_fill_attr(cmd, event);
+
+ return PR_DECLINED(cmd);
+}
+
+MODRET solaris_audit_post_rmd(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "rmdir", ADT_SUCCESS, ADT_SUCCESS, NULL);
+}
+
+MODRET solaris_audit_post_rmd_err(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "rmdir", ADT_FAILURE, ADT_FAILURE, NULL);
+}
+/* } */
+
+/* Get modification time and date. { */
+MODRET solaris_audit_pre_mdtm(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+ char* rp = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "utimes", ADT_ft_utimes, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ rp = realpath(ptr, src_realpath);
+ if (rp == NULL) {
+ if (errno != ENOENT) {
+ cmd->error_code = errno;
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "utimes", ptr, "realpath() failed");
+ error_451();
+ return PR_ERROR(cmd);
+ }
+ }
+
+ if (rp != NULL)
+ ptr = rp;
+
+ event->adt_ft_utimes.f_path = ptr;
+ event->adt_ft_utimes.f_attr = NULL;
+
+ return PR_DECLINED(cmd);
+}
+
+static const char* mdtm_fill_attr(cmd_rec *cmd, adt_event_data_t *event) {
+ return __fill_attr(
+ cmd, event->adt_ft_utimes.f_path, &(event->adt_ft_utimes.f_attr)
+ );
+}
+
+MODRET solaris_audit_post_mdtm(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "utimes", ADT_SUCCESS, ADT_SUCCESS, mdtm_fill_attr);
+}
+
+MODRET solaris_audit_post_mdtm_err(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "utimes", ADT_FAILURE, ADT_FAILURE, NULL);
+}
+/* } */
+
+/* Upload file. { */
+MODRET solaris_audit_pre_put(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "put", ADT_ft_put, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ event->adt_ft_put.f_path = ptr;
+ event->adt_ft_put.f_attr = NULL;
+
+ return PR_DECLINED(cmd);
+}
+
+static const char* put_fill_event(cmd_rec *cmd, adt_event_data_t *event) {
+ char *rp = NULL;
+
+ rp = realpath(event->adt_ft_put.f_path, src_realpath);
+ if (rp == NULL) {
+ cmd->error_code = errno;
+ return "realpath() failed";
+ }
+
+ event->adt_ft_put.f_path = rp;
+ return __fill_attr(
+ cmd, event->adt_ft_put.f_path, &(event->adt_ft_put.f_attr)
+ );
+}
+
+MODRET solaris_audit_post_put(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "put", ADT_SUCCESS, ADT_SUCCESS, put_fill_event);
+}
+
+MODRET solaris_audit_post_put_err(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "put", ADT_FAILURE, ADT_FAILURE, NULL);
+}
+/* } */
+
+/* Download file. { */
+MODRET solaris_audit_pre_get(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+ char* rp = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "get", ADT_ft_get, &ptr);
+ if (event == NULL) {
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ rp = realpath(ptr, src_realpath);
+ if (rp == NULL) {
+ if (errno != ENOENT) {
+ cmd->error_code = errno;
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "get", ptr, "realpath() failed");
+ error_451();
+ return PR_ERROR(cmd);
+ }
+ }
+
+ if (rp != NULL)
+ ptr = rp;
+
+ event->adt_ft_get.f_path = ptr;
+ event->adt_ft_get.f_attr = NULL;
+
+ return PR_DECLINED(cmd);
+}
+
+static const char* get_fill_attr(cmd_rec *cmd, adt_event_data_t *event) {
+ return __fill_attr(
+ cmd, event->adt_ft_get.f_path, &(event->adt_ft_get.f_attr)
+ );
+}
+
+MODRET solaris_audit_post_get(cmd_rec *cmd) {
+ return __solaris_audit_post(
+ cmd, "get", ADT_SUCCESS, ADT_SUCCESS, get_fill_attr);
+}
+
+MODRET solaris_audit_post_get_err(cmd_rec *cmd) {
+ return __solaris_audit_post(cmd, "get", ADT_FAILURE, ADT_FAILURE, NULL);
+}
+/* } */
+
+/* Rename file. { */
+/*
+ * The rename file implementation uses malloc()/free(),
+ * which the ProFTP module interface prohibits. I do not see another way.
+ *
+ * Any memory allocation method provided by the ProFTP API uses a memory pool.
+ * To avoid malloc()/free() a persistent memory pool is needed.
+ */
+
+/*
+ * To successfully log the rename audit event, a cooperation
+ * of RNFR and RNTO command handlers is necessary.
+ * The RNFR command specifies the source file name,
+ * and the RNTO command specifies the destination file name.
+ *
+ * The RNFR command handlers save the source file in the "src_path"
+ * variable, so that it is available to the RNTO command handler,
+ * which logs the audit event.
+ */
+static char* src_path = NULL;
+
+/* RNFR. { */
+static void __solaris_audit_rnfr_err(cmd_rec *cmd)
+{
+ adt_event_data_t *event = NULL;
+
+ if (src_path == NULL)
+ return;
+
+ event = __solaris_audit_pre_arg2(cmd, "RNFR", ADT_ft_rename, NULL);
+ if (event == NULL) {
+ error_451();
+ goto out;
+ }
+
+ event->adt_ft_rename.src_path = src_path;
+ event->adt_ft_rename.src_attr = NULL;
+ event->adt_ft_rename.dst_path = NULL;
+
+ (void) __solaris_audit_post(cmd, "RNFR", ADT_FAILURE, ADT_FAILURE, NULL);
+
+out:
+ free(src_path);
+ src_path = NULL;
+}
+
+MODRET solaris_audit_pre_rnfr(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ char* ptr = NULL;
+
+ /*
+ * If src_path is not NULL, it means that this RNFR command immediatelly
+ * follows a successfull RNFR command not terminated with a RNTO command.
+ * In such case, log an audit error for this unterminated RNFR command,
+ * and then continue normally.
+ *
+ * A correctly working ftp client can not cause this situation to happen.
+ * But this situation can be created, for instance, by manually sending
+ * commands to the ftp server with a telnet client.
+ */
+ if (src_path != NULL)
+ __solaris_audit_rnfr_err(cmd);
+
+ /*
+ * Prepare the audit event structure and remember the new src_path.
+ * This audit event structure will be used, if the RNFR command fails.
+ * It will be unused, if it succeeds.
+ */
+ event = __solaris_audit_pre_arg2(cmd, "get", ADT_ft_rename, &ptr);
+ if (event == NULL)
+ goto err;
+
+ event->adt_ft_rename.src_path = ptr;
+ event->adt_ft_rename.src_attr = NULL;
+ event->adt_ft_rename.dst_path = "";
+
+ src_path = strdup(cmd->argv[1]);
+ if (src_path == NULL) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "RNFR", ptr, "no memory");
+ goto err;
+ }
+
+ return PR_DECLINED(cmd);
+err:
+ return PR_ERROR(cmd);
+}
+
+/*
+ * On success, the RNFR command handlers do not log any audit event.
+ * A success means that a rename command is in progress and that
+ * the immediatelly following command is to be RNTO.
+ */
+MODRET solaris_audit_post_rnfr(cmd_rec *cmd) {
+
+ char *ptr;
+
+ ptr = realpath(src_path, src_realpath);
+ if (ptr == NULL) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s) failed: %s",
+ "RNFR", src_path, "realpath() failed");
+ error_451();
+ return PR_ERROR(cmd);
+ }
+
+ /*
+ * The argument to RNFR command is saved in src_path.
+ * It will be used in the subsequent RNTO command, or RNFR command.
+ */
+ return PR_DECLINED(cmd);
+}
+
+/* It can happen, that RNFR command fails, but the source path exists.
+ * Therefore make an attempt to resolve its realpath before doing
+ * the audit log.
+ *
+ * Even if the realpath() call fails, the src_path contents are still
+ * copied to src_realpath buffer. This makes them available to the RNTO
+ * command handlers.
+ */
+static const char* rnfr_err_fill_event(cmd_rec *cmd, adt_event_data_t *event) {
+ char *ptr = NULL;
+
+ if (src_path != NULL) {
+ ptr = realpath(src_path, src_realpath);
+ if (ptr != NULL)
+ event->adt_ft_rename.src_path = ptr;
+ }
+
+ return NULL;
+}
+
+/*
+ * On error, an audit event is logged, specifying that a rename
+ * command failed. The destination path in the audit event structure
+ * is empty, simply because the corresponding RNTO command did not yet
+ * happen, and it is not suppossed to happen.
+ */
+MODRET solaris_audit_post_rnfr_err(cmd_rec *cmd) {
+ MODRET ret;
+
+ ret = __solaris_audit_post(cmd, "RNFR", ADT_FAILURE, ADT_FAILURE,
+ rnfr_err_fill_event);
+
+ free(src_path);
+ src_path = NULL;
+
+ return ret;
+}
+/* } RNFR. */
+
+/* RNTO. { */
+static const char* rnto_fill_attr(cmd_rec *cmd, adt_event_data_t *event) {
+ return __fill_attr(
+ cmd, event->adt_ft_rename.src_path, &(event->adt_ft_rename.src_attr)
+ );
+}
+
+MODRET solaris_audit_pre_rnto(cmd_rec *cmd) {
+ adt_event_data_t *event = NULL;
+ const char* msg = NULL;
+ char* ptr = NULL;
+
+ event = __solaris_audit_pre_arg2(cmd, "get", ADT_ft_rename, &ptr);
+ if (event == NULL)
+ goto err;
+
+ /*
+ * If src_path is NULL, this means that there is no previous
+ * successfull RNFR command. The ftp server should know about this
+ * and terminate this RNTO command with an error (call the error callback).
+ */
+ event->adt_ft_rename.src_path = (src_path)?src_path:"";
+ event->adt_ft_rename.dst_path = ptr;
+
+ /*
+ * If the code executes here, it means that there is a successfully finished
+ * RNFR command immediatelly before us, which means that the src_path exists,
+ * and it should be therefore possible to get its status.
+ */
+ msg = rnto_fill_attr(cmd, event);
+ if (msg != NULL) {
+ pr_log_pri(PR_LOG_ERR, "Auditing of %s(%s,%s) failed: %s",
+ "RNTO", event->adt_ft_rename.src_path, ptr, msg);
+ goto err;
+ }
+
+ return PR_DECLINED(cmd);
+
+err:
+ error_451();
+ return PR_ERROR(cmd);
+}
+
+static const char* rnto_fill_event(cmd_rec *cmd, adt_event_data_t *event) {
+ char *ptr;
+
+ ptr = realpath(event->adt_ft_rename.dst_path, dst_realpath);
+ if (ptr == NULL) {
+ return "realpath() failed";
+ }
+
+ event->adt_ft_rename.src_path = src_realpath;
+ event->adt_ft_rename.dst_path = dst_realpath;
+
+ return NULL;
+}
+
+MODRET solaris_audit_post_rnto(cmd_rec *cmd) {
+ MODRET retval;
+
+ /* NULL means that there is no preceeding successfull RNFR command. */
+ if (src_path == NULL)
+ return PR_ERROR(cmd);
+
+ retval = __solaris_audit_post(cmd, "RNTO", ADT_SUCCESS, ADT_SUCCESS,
+ rnto_fill_event);
+
+ free(src_path);
+ src_path = NULL;
+
+ return retval;
+}
+
+/* It can happen, that RNTO command fails, but the destination path exists.
+ * Therefore make an attempt to resolve its realpath before doing
+ * the audit log.
+ */
+static const char* rnto_err_fill_event(cmd_rec *cmd, adt_event_data_t *event) {
+
+ (void) realpath(event->adt_ft_rename.dst_path, dst_realpath);
+ event->adt_ft_rename.src_path = src_realpath;
+ event->adt_ft_rename.dst_path = dst_realpath;
+
+ return NULL;
+}
+
+MODRET solaris_audit_post_rnto_err(cmd_rec *cmd) {
+ MODRET retval;
+ retval = __solaris_audit_post(cmd, "RNTO", ADT_FAILURE, ADT_FAILURE,
+ rnto_err_fill_event);
+ if (src_path != NULL) {
+ free(src_path);
+ src_path = NULL;
+ }
+ return retval;
+}
+/* } RNTO. */
+
+static cmdtable solaris_audit_commands[] = {
+ /* Login, logout. */
+ { POST_CMD, C_PASS, G_NONE, solaris_audit_post_pass, FALSE, FALSE },
+ { POST_CMD_ERR, C_PASS, G_NONE, solaris_audit_post_fail, FALSE, FALSE },
+
+ /* Delete file. */
+ { PRE_CMD, C_DELE, G_NONE, solaris_audit_pre_dele, FALSE, FALSE },
+ { POST_CMD, C_DELE, G_NONE, solaris_audit_post_dele, FALSE, FALSE },
+ { POST_CMD_ERR, C_DELE, G_NONE, solaris_audit_post_dele_err,
+ FALSE, FALSE },
+
+ /* Make directory. */
+ { PRE_CMD, C_MKD, G_NONE, solaris_audit_pre_mkd, FALSE, FALSE },
+ { POST_CMD, C_MKD, G_NONE, solaris_audit_post_mkd, FALSE, FALSE },
+ { POST_CMD_ERR, C_MKD, G_NONE, solaris_audit_post_mkd_err,
+ FALSE, FALSE },
+
+ /* Remove directory. */
+ { PRE_CMD, C_RMD, G_NONE, solaris_audit_pre_rmd, FALSE, FALSE },
+ { POST_CMD, C_RMD, G_NONE, solaris_audit_post_rmd, FALSE, FALSE },
+ { POST_CMD_ERR, C_RMD, G_NONE, solaris_audit_post_rmd_err,
+ FALSE, FALSE },
+
+ { PRE_CMD, C_XRMD, G_NONE, solaris_audit_pre_rmd, FALSE, FALSE },
+ { POST_CMD, C_XRMD, G_NONE, solaris_audit_post_rmd, FALSE, FALSE },
+ { POST_CMD_ERR, C_XRMD, G_NONE, solaris_audit_post_rmd_err,
+ FALSE, FALSE },
+
+ /* Get modification time. */
+ { PRE_CMD, C_MDTM, G_NONE, solaris_audit_pre_mdtm, FALSE, FALSE },
+ { POST_CMD, C_MDTM, G_NONE, solaris_audit_post_mdtm, FALSE, FALSE },
+ { POST_CMD_ERR, C_MDTM, G_NONE, solaris_audit_post_mdtm_err,
+ FALSE, FALSE },
+
+ /* Upload file. */
+ { PRE_CMD, C_STOR, G_WRITE, solaris_audit_pre_put, FALSE, FALSE },
+ { POST_CMD, C_STOR, G_WRITE, solaris_audit_post_put, FALSE, FALSE },
+ { POST_CMD_ERR, C_STOR, G_WRITE, solaris_audit_post_put_err,
+ FALSE, FALSE },
+
+ { PRE_CMD, C_STOU, G_WRITE, solaris_audit_pre_put, FALSE, FALSE },
+ { POST_CMD, C_STOU, G_WRITE, solaris_audit_post_put, FALSE, FALSE },
+ { POST_CMD_ERR, C_STOU, G_WRITE, solaris_audit_post_put_err,
+ FALSE, FALSE },
+
+ { PRE_CMD, C_APPE, G_WRITE, solaris_audit_pre_put, FALSE, FALSE },
+ { POST_CMD, C_APPE, G_WRITE, solaris_audit_post_put, FALSE, FALSE },
+ { POST_CMD_ERR, C_APPE, G_WRITE, solaris_audit_post_put_err,
+ FALSE, FALSE },
+
+ /* Download file. */
+ { PRE_CMD, C_RETR, G_READ, solaris_audit_pre_get, FALSE, FALSE },
+ { POST_CMD, C_RETR, G_READ, solaris_audit_post_get, FALSE, FALSE },
+ { POST_CMD_ERR, C_RETR, G_READ, solaris_audit_post_get_err,
+ FALSE, FALSE },
+
+ /* Rename file. */
+ { PRE_CMD, C_RNFR, G_NONE, solaris_audit_pre_rnfr, FALSE, FALSE },
+ { POST_CMD, C_RNFR, G_NONE, solaris_audit_post_rnfr, FALSE, FALSE },
+ { POST_CMD_ERR, C_RNFR, G_NONE, solaris_audit_post_rnfr_err,
+ FALSE, FALSE },
+
+ { PRE_CMD, C_RNTO, G_NONE, solaris_audit_pre_rnto, FALSE, FALSE },
+ { POST_CMD, C_RNTO, G_NONE, solaris_audit_post_rnto, FALSE, FALSE },
+ { POST_CMD_ERR, C_RNTO, G_NONE, solaris_audit_post_rnto_err,
+ FALSE, FALSE },
+
+ { 0, NULL }
+};
+
+module solaris_audit_module = {
+ NULL, NULL, /* Always NULL */
+ 0x20, /* API Version 2.0 */
+ "solaris_audit",
+ NULL, /* configuration table */
+ solaris_audit_commands, /* command table is for local use only */
+ NULL, /* No authentication handlers */
+ NULL, /* No initialization function */
+ audit_sess_init /* Post-fork "child mode" init */
+};