pax_global_header00006660000000000000000000000064146445111240014514gustar00rootroot0000000000000052 comment=b3c0644510bda421b5d1a7240b9bfccee8c71471 pgauditlogtofile-1.6.1/000077500000000000000000000000001464451112400150635ustar00rootroot00000000000000pgauditlogtofile-1.6.1/.gitignore000066400000000000000000000000521464451112400170500ustar00rootroot00000000000000/*.o /*.so /*.bc /.vagrant /test/.vagrant pgauditlogtofile-1.6.1/COPYING000066400000000000000000000016761464451112400161300ustar00rootroot00000000000000pglog - PostgreSQL extension Copyright (c) 2014, 2ndQuadrant Ltd. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL 2NDQUADRANT BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 2NDQUADRANT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2NDQUADRANT SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND 2NDQUADRANT HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgauditlogtofile-1.6.1/LICENSE000066400000000000000000000023761464451112400161000ustar00rootroot00000000000000Portions Copyright (c) 2021-2024 Francisco Miguel Biete Banon Portions Copyright (c) 1996-2021, The PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California This code is released under the PostgreSQL licence, as given at http://www.postgresql.org/about/licence/ Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN “AS IS” BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgauditlogtofile-1.6.1/Makefile000066400000000000000000000013571464451112400165310ustar00rootroot00000000000000# pgauditlogtofile/Makefile # EL9 #PG_CFLAGS = -fanalyzer -Wall -Wdiscarded-qualifiers # EL8 PG_CFLAGS = -Wall -Wdiscarded-qualifiers MODULE_big = pgauditlogtofile OBJS = pgauditlogtofile.o logtofile.o logtofile_bgw.o logtofile_connect.o logtofile_guc.o logtofile_log.o logtofile_shmem.o logtofile_autoclose.o logtofile_vars.o logtofile_filename.o EXTENSION = pgauditlogtofile DATA = pgauditlogtofile--1.0.sql pgauditlogtofile--1.0--1.2.sql pgauditlogtofile--1.2--1.3.sql pgauditlogtofile--1.3--1.4.sql pgauditlogtofile--1.4--1.5.sql pgauditlogtofile--1.5--1.6.sql PGFILEDESC = "pgAuditLogToFile - An addon for pgAudit logging extension for PostgreSQL" # PG_LDFLAGS = -lz PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) pgauditlogtofile-1.6.1/README.md000066400000000000000000000071421464451112400163460ustar00rootroot00000000000000# pgAudit Log to File [pgAudit Log to File](https://github.com/fmbiete/pgauditlogtofile) is an addon to [pgAudit](https://www.pgaudit.org/) than will redirect audit log lines to an independent file, instead of using PostgreSQL server logger. This will allow us to have an audit file that we can easily rotate without polluting server logs with those messages. Audit logs in heavily used systems can grow very fast. This extension allows to automatically rotate the files based in a number of minutes. ## Build ``` make install USE_PGXS=1 ``` ## Installation - Build the extension - Add pgauditlogtofile to "shared_preload_libraries" in postgresql.conf - Restart PostgreSQL to reload new shared library - Create extension in postgres database (like pgaudit we don't need to create it in all the databases) ``` postgres=# CREATE EXTENSION pgauditlogtofile; ``` ## Configuration ### pgaudit.log_directory Name of the directory where the audit file will be created. **Scope**: System **Default**: 'log' Empty or NULL will disable the extension and the audit logging will be done to PostgreSQL server logger. ### pgaudit.log_filename Name of the file where the audit will be written. Writing to an existing file will append the new entries. This variable can contain time patterns up to minute to allow automatic rotation. **Scope**: System **Default**: 'audit-%Y%m%d_%H%M.log' Empty or NULL will disable the extension and the audit logging will be done to PostgreSQL server logger. ### pgaudit.log_rotation_age Number of minutes after which the audit file will be rotated. **Scope**: System **Default**: 1440 minutes (1 day) **Performance Notes**: - If _log_rotation_age < 60_ the rotation background worker will wake up every 10 seconds. - If _log_rotation_age > 60_ the rotation background worker will wake up every 1 minute. ### pgaudit.log_connections Intercepts server log messages emited when log_connections is on **Scope**: System **Default**: off **Requires**: log_connections = on ### pgaudit.log_disconnections Intercepts server log messages emited when log_disconnections is on **Scope**: System **Default**: off **Requires**: log_disconnections = on ### pgaudit.log_autoclose_minutes **EXPERIMENTAL**: automatically closes the audit log file handler kept by a backend after N minutes of inactivity. _This features creates a background thread that will sleep in the background and close the file handler._ **Scope**: System **Default**: 0 ### pgAudit Log To File - Record format ``` CREATE FOREIGN TABLE pgauditlogtofile_extern ( ----fields from postgresql session---- log_time timestamptz(3) NULL, user_name text NULL, database_name text NULL, process_id int4 NULL, remote_client text NULL, remote_port text NULL, session_id text NULL, session_line_num int8 NULL, command_tag text NULL, session_start_time timestamptz NULL, virtual_transaction_id text NULL, transaction_id int8 NULL, sql_state_code text NULL, -----fields from pgaudit record------- audit_type text NULL, statement_id text NULL, substatement_id text NULL, "class" text NULL, command text NULL, object_type text NULL, object_name text NULL, "statement" text NULL, "parameter" text NULL, ----additional fields-------- detail text NULL, hint text NULL, internal_query text NULL, internal_query_pos int4 NULL, context text NULL, debug_query text NULL, cursor_pos int4 NULL, function_name text NULL, filename_linenum text NULL, application_name text NULL ) SERVER your_server OPTIONS (filename 'audit_log.csv', format 'csv'); ``` ### Test ``` cd test vagrant plugin install vagrant-vbguest vagrant up ``` pgauditlogtofile-1.6.1/logtofile.c000066400000000000000000000103001464451112400172050ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile.c * Main entry point for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile.h" #include "logtofile_bgw.h" #include "logtofile_connect.h" #include "logtofile_guc.h" #include "logtofile_log.h" #include "logtofile_shmem.h" #include "logtofile_vars.h" #include /* these are always necessary for a bgworker */ #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Main entry point for the extension * @param void * @return void */ void _PG_init(void) { BackgroundWorker worker; if (!process_shared_preload_libraries_in_progress) { ereport(ERROR, ( errmsg("pgauditlogtofile can only be loaded via shared_preload_libraries"), errhint("Add pgauditlogtofile to the shared_preload_libraries configuration variable in postgresql.conf."))); } /* guc variables */ DefineCustomStringVariable( "pgaudit.log_directory", "Directory where to spool log data", NULL, &guc_pgaudit_ltf_log_directory, "log", PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_SUPERUSER_ONLY, guc_check_directory, NULL, NULL); DefineCustomStringVariable( "pgaudit.log_filename", "Filename with time patterns (up to minutes) where to spool audit data", NULL, &guc_pgaudit_ltf_log_filename, "audit-%Y%m%d_%H%M.log", PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_SUPERUSER_ONLY, NULL, NULL, NULL); DefineCustomIntVariable( "pgaudit.log_rotation_age", "Automatic spool file rotation will occur after N minutes", NULL, &guc_pgaudit_ltf_log_rotation_age, HOURS_PER_DAY * MINS_PER_HOUR, 1, INT_MAX / SECS_PER_MINUTE, PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_UNIT_MIN | GUC_SUPERUSER_ONLY, NULL, NULL, NULL); DefineCustomBoolVariable( "pgaudit.log_connections", "Intercepts log_connections messages", NULL, &guc_pgaudit_ltf_log_connections, false, PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_SUPERUSER_ONLY, NULL, NULL, NULL); DefineCustomBoolVariable( "pgaudit.log_disconnections", "Intercepts log_disconnections messages", NULL, &guc_pgaudit_ltf_log_disconnections, false, PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_SUPERUSER_ONLY, NULL, NULL, NULL); DefineCustomIntVariable( "pgaudit.log_autoclose_minutes", "Automatic spool file closure by backend after N minutes of inactivity", NULL, &guc_pgaudit_ltf_auto_close_minutes, 0, 0, INT_MAX / MINS_PER_HOUR, PGC_SIGHUP, GUC_NOT_IN_SAMPLE | GUC_UNIT_MIN | GUC_SUPERUSER_ONLY, NULL, NULL, NULL); EmitWarningsOnPlaceholders("pgauditlogtofile"); /* background worker */ worker.bgw_flags = BGWORKER_SHMEM_ACCESS; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = 1; worker.bgw_main_arg = Int32GetDatum(0); worker.bgw_notify_pid = 0; sprintf(worker.bgw_library_name, "pgauditlogtofile"); sprintf(worker.bgw_function_name, "PgAuditLogToFileMain"); snprintf(worker.bgw_name, BGW_MAXLEN, "pgauditlogtofile launcher"); RegisterBackgroundWorker(&worker); /* backend hooks */ #if (PG_VERSION_NUM >= 150000) prev_shmem_request_hook = shmem_request_hook; shmem_request_hook = PgAuditLogToFile_shmem_request; #else RequestAddinShmemSpace(MAXALIGN(sizeof(PgAuditLogToFileShm))); RequestNamedLWLockTranche("pgauditlogtofile", 1); #endif prev_shmem_startup_hook = shmem_startup_hook; shmem_startup_hook = PgAuditLogToFile_shmem_startup; prev_emit_log_hook = emit_log_hook; emit_log_hook = PgAuditLogToFile_emit_log; } /** * @brief Extension finalization * @param void * @return void */ void _PG_fini(void) { emit_log_hook = prev_emit_log_hook; shmem_startup_hook = prev_shmem_startup_hook; } pgauditlogtofile-1.6.1/logtofile.h000066400000000000000000000007431464451112400172240ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile.h * Main entry point for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_H_ #define _LOGTOFILE_H_ void _PG_init(void); void _PG_fini(void); #endif pgauditlogtofile-1.6.1/logtofile_autoclose.c000066400000000000000000000032301464451112400212670ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_autoclose.c * Autoclose thread for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_autoclose.h" #include "logtofile_vars.h" #include #include #include #include #include /** * @brief Main thread function to close the audit log file after a certain time * @param arg: autoclose_thread_status_debug - used to debug the thread status * @return void */ void *PgAuditLogToFile_autoclose_run(void *arg) { int64 diff; TimestampTz ts_now; long secs; int microsecs; int *autoclose_thread_status_debug; // don't use ereport here, use this flag to identify the position autoclose_thread_status_debug = (int *)arg; while (1) { sleep(1 * SECS_PER_MINUTE); ts_now = GetCurrentTimestamp(); TimestampDifference(pgaudit_ltf_autoclose_active_ts, ts_now, &secs, µsecs); diff = secs / SECS_PER_MINUTE; if (diff >= guc_pgaudit_ltf_auto_close_minutes) { fclose(pgaudit_ltf_file_handler); pgaudit_ltf_file_handler = NULL; *autoclose_thread_status_debug = 3; // file closed break; } else { *autoclose_thread_status_debug = 2; // file recently used } } // clear the flag to allow another thread creation pg_atomic_clear_flag(&pgaudit_ltf_autoclose_flag_thread); pthread_exit(NULL); } pgauditlogtofile-1.6.1/logtofile_autoclose.h000066400000000000000000000010461464451112400212770ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_autoclose.h * Autoclose thread for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_AUTOCLOSE_H_ #define _LOGTOFILE_AUTOCLOSE_H_ #include extern void *PgAuditLogToFile_autoclose_run(void *arg); #endif pgauditlogtofile-1.6.1/logtofile_bgw.c000066400000000000000000000102261464451112400200530ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_bgw.c * Background worker for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_bgw.h" /* these are always necessary for a bgworker */ #include #include #include #include #include #include #include #include #include #if (PG_VERSION_NUM >= 140000) #include #include #else #include #endif #include #include #include #include "logtofile_filename.h" #include "logtofile_shmem.h" #include "logtofile_vars.h" /* global settings */ static bool PgAuditLogToFileReloadConfig = false; /* flags set by signal handlers */ static volatile sig_atomic_t got_sigterm = false; /* forward declaration private functions */ static void pgauditlogtofile_sighup(SIGNAL_ARGS); static void pgauditlogtofile_sigterm(SIGNAL_ARGS); /** * @brief Main entry point for the background worker * @param arg: unused * @return void */ void PgAuditLogToFileMain(Datum arg) { int sleep_ms = SECS_PER_MINUTE * 1000; MemoryContext PgAuditLogToFileContext = NULL; pqsignal(SIGHUP, pgauditlogtofile_sighup); pqsignal(SIGINT, SIG_IGN); pqsignal(SIGTERM, pgauditlogtofile_sigterm); BackgroundWorkerUnblockSignals(); pgstat_report_appname("pgauditlogtofile launcher"); PgAuditLogToFileContext = AllocSetContextCreate(CurrentMemoryContext, "pgauditlogtofile loop context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); ereport(LOG, (errmsg("pgauditlogtofile worker started"))); MemoryContextSwitchTo(PgAuditLogToFileContext); while (1) { int rc; CHECK_FOR_INTERRUPTS(); if (guc_pgaudit_ltf_log_rotation_age < MINS_PER_HOUR) { // very small rotation, wake up frequently - this has a performance impact, // but rotation every a few minutes should only be done for testing sleep_ms = 10000; } ereport(DEBUG5, (errmsg("pgauditlogtofile bgw loop"))); if (PgAuditLogToFileReloadConfig) { ereport(DEBUG3, (errmsg("pgauditlogtofile bgw loop reload cfg"))); ProcessConfigFile(PGC_SIGHUP); PgAuditLogToFile_calculate_current_filename(); PgAuditLogToFile_set_next_rotation_time(); ereport(DEBUG3, (errmsg("pgauditlogtofile bgw loop new filename %s", pgaudit_ltf_shm->filename))); PgAuditLogToFileReloadConfig = false; } else { if (PgAuditLogToFile_needs_rotate_file()) { ereport(DEBUG3, (errmsg("pgauditlogtofile bgw loop needs rotation %s", pgaudit_ltf_shm->filename))); PgAuditLogToFile_calculate_current_filename(); PgAuditLogToFile_set_next_rotation_time(); ereport(DEBUG3, (errmsg("pgauditlogtofile bgw loop new filename %s", pgaudit_ltf_shm->filename))); } } /* shutdown if requested */ if (got_sigterm) break; rc = WaitLatch(&MyProc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleep_ms, PG_WAIT_EXTENSION); if (rc & WL_POSTMASTER_DEATH) proc_exit(1); ResetLatch(&MyProc->procLatch); } MemoryContextReset(PgAuditLogToFileContext); ereport(LOG, (errmsg("pgauditlogtofile worker shutting down"))); proc_exit(0); } /* private functions */ /** * @brief Signal handler for SIGHUP * @param signal_arg: signal number * @return void */ static void pgauditlogtofile_sigterm(SIGNAL_ARGS) { got_sigterm = true; if (MyProc != NULL) { SetLatch(&MyProc->procLatch); } } /** * @brief Signal handler for SIGTERM * @param signal_arg: signal number * @return void */ static void pgauditlogtofile_sighup(SIGNAL_ARGS) { PgAuditLogToFileReloadConfig = true; if (MyProc != NULL) { SetLatch(&MyProc->procLatch); } } pgauditlogtofile-1.6.1/logtofile_bgw.h000066400000000000000000000010261464451112400200560ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_bgw.h * Background worker for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_BGW_H_ #define _LOGTOFILE_BGW_H_ #include extern PGDLLEXPORT void PgAuditLogToFileMain(Datum arg); #endif pgauditlogtofile-1.6.1/logtofile_connect.c000066400000000000000000000041351464451112400207270ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_connect.c * Functions to parse connect and disconnect messages * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_connect.h" /** * @brief From a list of messages, optionally translated, get the unique prefixes * @param messages: list of messages * @param num_messages: number of messages * @param num_unique: number of unique prefixes * @return char **: list of unique prefixes */ char ** PgAuditLogToFile_connect_UniquePrefixes(const char **messages, const size_t num_messages, size_t *num_unique) { bool is_unique; char **prefixes = NULL; char *prefix, *dup; #ifdef ENABLE_NLS char *message; #else const char *message; #endif size_t i, j; *num_unique = 0; prefixes = palloc(num_messages * sizeof(char *)); if (prefixes != NULL) { for (i = 0; i < num_messages; i++) { #ifdef ENABLE_NLS // Get translation - static copy message = gettext(messages[i]); #else // Pointer to original = static copy message = messages[i]; #endif // Get a copy that we can modify dup = pstrdup(message); if (dup != NULL) { prefix = strtok(dup, "%"); if (prefix != NULL) { // Search duplicated is_unique = true; for (j = 0; j < i; j++) { if (prefixes[j] != NULL) { if (strcmp(prefixes[j], prefix) == 0) { // Skip - prefix already present is_unique = false; } } } if (is_unique) { prefixes[i] = palloc((strlen(prefix) + 1) * sizeof(char)); if (prefixes[i] != NULL) { strcpy(prefixes[i], prefix); *num_unique += 1; } } else { prefixes[i] = NULL; } } pfree(dup); } } } return prefixes; } pgauditlogtofile-1.6.1/logtofile_connect.h000066400000000000000000000011721464451112400207320ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_connect.h * Functions to parse connect and disconnect messages * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_CONNECT_H_ #define _LOGTOFILE_CONNECT_H_ #include extern char ** PgAuditLogToFile_connect_UniquePrefixes(const char **messages, const size_t num_messages, size_t *num_unique); #endif pgauditlogtofile-1.6.1/logtofile_filename.c000066400000000000000000000050151464451112400210540ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_filename.c * Functions to calculate the filename of the log file * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_filename.h" #include #include #include #include "logtofile_vars.h" // Private functions char * pgauditlogtofile_tm2filename(const struct pg_tm *tm); /** * @brief Calculate the current filename of the log file * @param void * @return char * - the current filename */ char * PgAuditLogToFile_current_filename(void) { pg_time_t timet = timestamptz_to_time_t(GetCurrentTimestamp()); struct pg_tm *tm = pg_localtime(&timet, log_timezone); return pgauditlogtofile_tm2filename(tm); } /** * @brief Set the next rotation time * @param void * @return void * @note Copied from src/backend/postmaster/syslogger.c */ void PgAuditLogToFile_set_next_rotation_time(void) { pg_time_t now; struct pg_tm *tm; int rotinterval; /* nothing to do if time-based rotation is disabled */ if (guc_pgaudit_ltf_log_rotation_age < 1) return; /* * The requirements here are to choose the next time > now that is a * "multiple" of the log rotation interval. "Multiple" can be interpreted * fairly loosely. In this version we align to log_timezone rather than * GMT. */ rotinterval = guc_pgaudit_ltf_log_rotation_age * SECS_PER_MINUTE; /* convert to seconds */ now = (pg_time_t) time(NULL); tm = pg_localtime(&now, log_timezone); now += tm->tm_gmtoff; now -= now % rotinterval; now += rotinterval; now -= tm->tm_gmtoff; LWLockAcquire(pgaudit_ltf_shm->lock, LW_EXCLUSIVE); pgaudit_ltf_shm->next_rotation_time = now; LWLockRelease(pgaudit_ltf_shm->lock); } /** * @brief Convert a pg_tm structure to a filename * @param tm - the pg_tm structure * @return char * - the filename */ char * pgauditlogtofile_tm2filename(const struct pg_tm *tm) { char *filename = NULL; int len; filename = palloc(MAXPGPATH * sizeof(char *)); if (filename != NULL) { memset(filename, 0, sizeof(char) * MAXPGPATH); snprintf(filename, MAXPGPATH, "%s/", guc_pgaudit_ltf_log_directory); len = strlen(filename); pg_strftime(filename + len, MAXPGPATH - len, guc_pgaudit_ltf_log_filename, tm); } return filename; } pgauditlogtofile-1.6.1/logtofile_filename.h000066400000000000000000000012121464451112400210540ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_filename.c * Functions to calculate the filename of the log file * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_FILENAME_H_ #define _LOGTOFILE_FILENAME_H_ #include "postgres.h" extern char *PgAuditLogToFile_current_filename(void); extern void PgAuditLogToFile_set_next_rotation_time(void); #endif // _LOGTOFILE_FILENAME_H_pgauditlogtofile-1.6.1/logtofile_guc.c000066400000000000000000000016461464451112400200600ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_guc.c * GUC variables for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_guc.h" #include #include #include "logtofile_shmem.h" #include "logtofile_vars.h" /** * @brief GUC Callback pgaudit.log_directory check path * @param newval: new value * @param extra: extra * @param source: source * @return bool: true if path is valid */ bool guc_check_directory(char **newval, void **extra, GucSource source) { /* * Since canonicalize_path never enlarges the string, we can just modify * newval in-place. */ canonicalize_path(*newval); return true; } pgauditlogtofile-1.6.1/logtofile_guc.h000066400000000000000000000011001464451112400200460ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_guc.h * GUC variables for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_GUC_H_ #define _LOGTOFILE_GUC_H_ #include #include extern bool guc_check_directory(char **newval, void **extra, GucSource source); #endif pgauditlogtofile-1.6.1/logtofile_log.c000066400000000000000000000365741464451112400200730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_log.c * Functions to write audit logs to file * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_log.h" #include "logtofile_autoclose.h" #include "logtofile_guc.h" #include "logtofile_shmem.h" #include "logtofile_vars.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Defines */ #define PGAUDIT_PREFIX_LINE "AUDIT: " #define PGAUDIT_PREFIX_LINE_LENGTH sizeof(PGAUDIT_PREFIX_LINE) - 1 #define FORMATTED_TS_LEN 128 /* * We really want line-buffered mode for logfile output, but Windows does * not have it, and interprets _IOLBF as _IOFBF (bozos). So use _IONBF * instead on Windows. */ #ifdef WIN32 #define LBF_MODE _IONBF #else #define LBF_MODE _IOLBF #endif /* variables to use only in this unit */ static char formatted_log_time[FORMATTED_TS_LEN]; static char formatted_start_time[FORMATTED_TS_LEN]; static char filename_in_use[MAXPGPATH]; static int autoclose_thread_status_debug = 0; // 0: new proc, 1: th running, 2: th running sleep used, 3: th closed /* forward declaration private functions */ void pgauditlogtofile_close_file(void); void pgauditlogtofile_create_audit_line(StringInfo buf, const ErrorData *edata, int exclude_nchars); void pgauditlogtofile_format_log_time(void); void pgauditlogtofile_format_start_time(void); bool pgauditlogtofile_is_enabled(void); bool pgauditlogtofile_is_open_file(void); bool pgauditlogtofile_is_prefixed(const char *msg); bool pgauditlogtofile_open_file(void); bool pgauditlogtofile_record_audit(const ErrorData *edata, int exclude_nchars); bool pgauditlogtofile_write_audit(const ErrorData *edata, int exclude_nchars); /* public methods */ /** * @brief Hook to emit_log - write the record to the audit or send it to the default logger * @param edata: error data * @return void */ void PgAuditLogToFile_emit_log(ErrorData *edata) { int exclude_nchars = -1; if (pgauditlogtofile_is_enabled()) { // printf("ENABLE PRINTF\n"); if (pg_strncasecmp(edata->message, PGAUDIT_PREFIX_LINE, PGAUDIT_PREFIX_LINE_LENGTH) == 0) { exclude_nchars = PGAUDIT_PREFIX_LINE_LENGTH; edata->output_to_server = false; } else if (pgauditlogtofile_is_prefixed(edata->message)) { edata->output_to_server = false; exclude_nchars = 0; } // Scenarios not contemplated above will be ignored if (exclude_nchars >= 0) { if (!pgauditlogtofile_record_audit(edata, exclude_nchars)) { // ERROR: failed to record in audit, record in server log edata->output_to_server = true; } } } if (prev_emit_log_hook) prev_emit_log_hook(edata); } /** * @brief Checks if pgauditlogtofile is completely started and configured * @param void * @return bool - true if pgauditlogtofile is enabled */ bool pgauditlogtofile_is_enabled(void) { if (UsedShmemSegAddr == NULL) return false; if (!pgaudit_ltf_shm || !pg_atomic_unlocked_test_flag(&pgaudit_ltf_flag_shutdown) || guc_pgaudit_ltf_log_directory == NULL || guc_pgaudit_ltf_log_filename == NULL || strlen(guc_pgaudit_ltf_log_directory) == 0 || strlen(guc_pgaudit_ltf_log_filename) == 0) return false; return true; } /** * @brief Records an audit log * @param edata: error data * @param exclude_nchars: number of characters to exclude from the message * @return bool - true if the record was written */ bool pgauditlogtofile_record_audit(const ErrorData *edata, int exclude_nchars) { bool rc; ereport(DEBUG5, (errmsg("pgauditlogtofile record audit in %s (shm %s)", filename_in_use, pgaudit_ltf_shm->filename))); /* do we need to rotate? */ if (strcmp(filename_in_use, pgaudit_ltf_shm->filename) != 0) { ereport(DEBUG3, ( errmsg("pgauditlogtofile record audit file handler requires reopening - shm_filename %s filename_in_use %s", pgaudit_ltf_shm->filename, filename_in_use))); pgauditlogtofile_close_file(); } if (!pgauditlogtofile_is_open_file()) { if (!pgauditlogtofile_open_file()) { return false; } } rc = pgauditlogtofile_write_audit(edata, exclude_nchars); pgaudit_ltf_autoclose_active_ts = GetCurrentTimestamp(); if (guc_pgaudit_ltf_auto_close_minutes > 0) { // only 1 auto-close thread if (pg_atomic_test_set_flag(&pgaudit_ltf_autoclose_flag_thread)) { ereport(DEBUG3, (errmsg("pgauditlogtofile record_audit - create autoclose thread"))); autoclose_thread_status_debug = 1; pthread_attr_init(&pgaudit_ltf_autoclose_thread_attr); pthread_attr_setdetachstate(&pgaudit_ltf_autoclose_thread_attr, PTHREAD_CREATE_DETACHED); pthread_create(&pgaudit_ltf_autoclose_thread, &pgaudit_ltf_autoclose_thread_attr, PgAuditLogToFile_autoclose_run, &autoclose_thread_status_debug); } } return rc; } /** * @brief Close the audit log file * @param void * @return void */ void pgauditlogtofile_close_file(void) { if (pgaudit_ltf_file_handler) { fclose(pgaudit_ltf_file_handler); pgaudit_ltf_file_handler = NULL; } } /** * @brief Checks if the audit log file is open * @param void * @return bool - true if the file is open */ bool pgauditlogtofile_is_open_file(void) { if (pgaudit_ltf_file_handler) return true; else return false; } /** * @brief Checks if a message starts with one of our intercept prefixes * @param msg: message * @return bool - true if the message starts with a prefix */ bool pgauditlogtofile_is_prefixed(const char *msg) { bool found = false; size_t i; if (guc_pgaudit_ltf_log_connections) { for (i = 0; !found && i < pgaudit_ltf_shm->num_prefixes_connection; i++) { found = pg_strncasecmp(msg, pgaudit_ltf_shm->prefixes_connection[i]->prefix, pgaudit_ltf_shm->prefixes_connection[i]->length) == 0; } } if (!found && guc_pgaudit_ltf_log_disconnections) { for (i = 0; !found && i < pgaudit_ltf_shm->num_prefixes_disconnection; i++) { found = pg_strncasecmp(msg, pgaudit_ltf_shm->prefixes_disconnection[i]->prefix, pgaudit_ltf_shm->prefixes_disconnection[i]->length) == 0; } } return found; } /** * @brief Open the audit log file * @param void * @return bool - true if the file was opened */ bool pgauditlogtofile_open_file(void) { mode_t oumask; bool opened = true; /* Create spool directory if not present; ignore errors */ (void)MakePGDirectory(guc_pgaudit_ltf_log_directory); /* * Note we do not let Log_file_mode disable IWUSR, since we certainly want * to be able to write the files ourselves. */ oumask = umask( (mode_t)((~(Log_file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO))); pgaudit_ltf_file_handler = fopen(pgaudit_ltf_shm->filename, "a"); umask(oumask); if (pgaudit_ltf_file_handler) { /* 128K buffer and flush on demand or when full -> attempt to use only 1 IO operation per record */ setvbuf(pgaudit_ltf_file_handler, NULL, _IOFBF, 131072); #ifdef WIN32 /* use CRLF line endings on Windows */ _setmode(_fileno(file_handler), _O_TEXT); #endif // File open, we update the filename we are using strcpy(filename_in_use, pgaudit_ltf_shm->filename); } else { int save_errno = errno; opened = false; ereport(ERROR, (errcode_for_file_access(), errmsg("could not open log file \"%s\": %m", pgaudit_ltf_shm->filename))); errno = save_errno; } return opened; } /** * @brief Writes an audit record in the audit log file * @param edata: error data * @param exclude_nchars: number of characters to exclude from the message */ bool pgauditlogtofile_write_audit(const ErrorData *edata, int exclude_nchars) { StringInfoData buf; int rc = 0; initStringInfo(&buf); /* create the log line */ pgauditlogtofile_create_audit_line(&buf, edata, exclude_nchars); // auto-close maybe has closed the file if (!pgaudit_ltf_file_handler) pgauditlogtofile_open_file(); if (pgaudit_ltf_file_handler) { fseek(pgaudit_ltf_file_handler, 0L, SEEK_END); rc = fwrite(buf.data, 1, buf.len, pgaudit_ltf_file_handler); pfree(buf.data); fflush(pgaudit_ltf_file_handler); } if (rc != buf.len) { int save_errno = errno; ereport(ERROR, (errcode_for_file_access(), errmsg("could not write audit log file \"%s\": %m", filename_in_use))); pgauditlogtofile_close_file(); errno = save_errno; } return rc == buf.len; } /** * @brief Formats an audit log line * @param buf: buffer to write the formatted line * @param edata: error data * @param exclude_nchars: number of characters to exclude from the message * @return void */ void pgauditlogtofile_create_audit_line(StringInfo buf, const ErrorData *edata, int exclude_nchars) { bool print_stmt = false; /* static counter for line numbers */ static long log_line_number = 0; /* has counter been reset in current process? */ static int log_my_pid = 0; /* * This is one of the few places where we'd rather not inherit a static * variable's value from the postmaster. But since we will, reset it when * MyProcPid changes. */ if (log_my_pid != MyProcPid) { /* new session */ log_line_number = 0; log_my_pid = MyProcPid; /* start session timestamp */ pgauditlogtofile_format_start_time(); } log_line_number++; /* timestamp with milliseconds */ pgauditlogtofile_format_log_time(); appendStringInfoString(buf, formatted_log_time); appendStringInfoCharMacro(buf, ','); /* username */ if (MyProcPort && MyProcPort->user_name) appendStringInfoString(buf, MyProcPort->user_name); appendStringInfoCharMacro(buf, ','); /* database name */ if (MyProcPort && MyProcPort->database_name) appendStringInfoString(buf, MyProcPort->database_name); appendStringInfoCharMacro(buf, ','); /* Process id */ appendStringInfo(buf, "%d", log_my_pid); appendStringInfoCharMacro(buf, ','); /* Remote host and port */ if (MyProcPort && MyProcPort->remote_host) { appendStringInfoString(buf, MyProcPort->remote_host); if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') { appendStringInfoCharMacro(buf, ':'); appendStringInfoString(buf, MyProcPort->remote_port); } } appendStringInfoCharMacro(buf, ','); /* session id - hex representation of start time . session process id */ appendStringInfo(buf, "%lx.%x", (long)MyStartTime, log_my_pid); appendStringInfoCharMacro(buf, ','); /* Line number */ appendStringInfo(buf, "%ld", log_line_number); appendStringInfoCharMacro(buf, ','); /* PS display */ if (MyProcPort) { StringInfoData msgbuf; const char *psdisp; int displen; initStringInfo(&msgbuf); psdisp = get_ps_display(&displen); appendBinaryStringInfo(&msgbuf, psdisp, displen); appendStringInfoString(buf, msgbuf.data); pfree(msgbuf.data); } appendStringInfoCharMacro(buf, ','); /* session start timestamp */ appendStringInfoString(buf, formatted_start_time); appendStringInfoCharMacro(buf, ','); /* Virtual transaction id */ /* keep VXID format in sync with lockfuncs.c */ #if (PG_VERSION_NUM >= 170000) if (MyProc != NULL && MyProc->vxid.procNumber != INVALID_PROC_NUMBER) appendStringInfo(buf, "%d/%u", MyProc->vxid.procNumber, MyProc->vxid.lxid); #else if (MyProc != NULL && MyProc->backendId != InvalidBackendId) appendStringInfo(buf, "%d/%u", MyProc->backendId, MyProc->lxid); #endif appendStringInfoCharMacro(buf, ','); /* Transaction id */ appendStringInfo(buf, "%u", GetTopTransactionIdIfAny()); appendStringInfoCharMacro(buf, ','); /* SQL state code */ appendStringInfoString(buf, unpack_sql_state(edata->sqlerrcode)); appendStringInfoCharMacro(buf, ','); /* errmessage - PGAUDIT formatted text, +7 exclude "AUDIT: " prefix */ appendStringInfoString(buf, edata->message + exclude_nchars); appendStringInfoCharMacro(buf, ','); /* errdetail or errdetail_log */ if (edata->detail_log) appendStringInfoString(buf, edata->detail_log); else if (edata->detail) appendStringInfoString(buf, edata->detail); appendStringInfoCharMacro(buf, ','); /* errhint */ if (edata->hint) appendStringInfoString(buf, edata->hint); appendStringInfoCharMacro(buf, ','); /* internal query */ if (edata->internalquery) appendStringInfoString(buf, edata->internalquery); appendStringInfoCharMacro(buf, ','); /* if printed internal query, print internal pos too */ if (edata->internalpos > 0 && edata->internalquery != NULL) appendStringInfo(buf, "%d", edata->internalpos); appendStringInfoCharMacro(buf, ','); /* errcontext */ if (edata->context) appendStringInfoString(buf, edata->context); appendStringInfoCharMacro(buf, ','); /* user query --- only reported if not disabled by the caller */ if (debug_query_string != NULL && !edata->hide_stmt) print_stmt = true; if (print_stmt) appendStringInfoString(buf, debug_query_string); appendStringInfoCharMacro(buf, ','); if (print_stmt && edata->cursorpos > 0) appendStringInfo(buf, "%d", edata->cursorpos); appendStringInfoCharMacro(buf, ','); /* file error location */ if (Log_error_verbosity >= PGERROR_VERBOSE) { StringInfoData msgbuf; initStringInfo(&msgbuf); if (edata->funcname && edata->filename) appendStringInfo(&msgbuf, "%s, %s:%d", edata->funcname, edata->filename, edata->lineno); else if (edata->filename) appendStringInfo(&msgbuf, "%s:%d", edata->filename, edata->lineno); appendStringInfoString(buf, msgbuf.data); pfree(msgbuf.data); } appendStringInfoCharMacro(buf, ','); /* application name */ if (application_name) appendStringInfoString(buf, application_name); appendStringInfoCharMacro(buf, '\n'); } /** * @brief Formats the session start time * @param void * @return void */ void pgauditlogtofile_format_start_time(void) { /* * Note: we expect that guc.c will ensure that log_timezone is set up (at * least with a minimal GMT value) before Log_line_prefix can become * nonempty or CSV mode can be selected. */ pg_strftime(formatted_start_time, FORMATTED_TS_LEN, "%Y-%m-%d %H:%M:%S %Z", pg_localtime((pg_time_t *)&MyStartTime, log_timezone)); } /** * @brief Formats the record time * @param void * @return void */ void pgauditlogtofile_format_log_time(void) { struct timeval tv; char msbuf[5]; gettimeofday(&tv, NULL); /* * Note: we expect that guc.c will ensure that log_timezone is set up (at * least with a minimal GMT value) before Log_line_prefix can become * nonempty or CSV mode can be selected. */ pg_strftime(formatted_log_time, FORMATTED_TS_LEN, /* leave room for milliseconds... */ "%Y-%m-%d %H:%M:%S %Z", pg_localtime((pg_time_t *)&(tv.tv_sec), log_timezone)); /* 'paste' milliseconds into place... */ sprintf(msbuf, ".%03d", (int)(tv.tv_usec / 1000)); memcpy(formatted_log_time + 19, msbuf, 4); } pgauditlogtofile-1.6.1/logtofile_log.h000066400000000000000000000010611464451112400200570ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_log.h * Functions to write audit logs to file * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_LOG_H_ #define _LOGTOFILE_LOG_H_ #include /* Hook functions */ extern void PgAuditLogToFile_emit_log(ErrorData *edata); #endif pgauditlogtofile-1.6.1/logtofile_shmem.c000066400000000000000000000164321464451112400204120ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_shmem.c * Functions to manage shared memory * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_shmem.h" #include #include #include #include #include #include "logtofile_autoclose.h" #include "logtofile_connect.h" #include "logtofile_filename.h" #include "logtofile_guc.h" #include "logtofile_vars.h" /* Extracted from src/backend/po */ const char *postgresConnMsg[] = { "connection received: host=%s port=%s", "connection received: host=%s", "connection authorized: user=%s", "connection authenticated: identity=\"%s\" method=%s (%s:%d)", "replication connection authorized: user=%s", "replication connection authorized: user=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)", "replication connection authorized: user=%s application_name=%s", "replication connection authorized: user=%s application_name=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)", "password authentication failed for user \"%s\"", "authentication failed for user \"%s\": host rejected", "\"trust\" authentication failed for user \"%s\"", "Ident authentication failed for user \"%s\"", "Peer authentication failed for user \"%s\"", "password authentication failed for user \"%s\"", "SSPI authentication failed for user \"%s\"", "PAM authentication failed for user \"%s\"", "BSD authentication failed for user \"%s\"", "LDAP authentication failed for user \"%s\"", "certificate authentication failed for user \"%s\"", "RADIUS authentication failed for user \"%s\"", "authentication failed for user \"%s\": invalid authentication method", "connection authorized: user=%s database=%s", "connection authorized: user=%s database=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)", "connection authorized: user=%s database=%s application_name=%s", "connection authorized: user=%s database=%s application_name=%s SSL enabled (protocol=%s, cipher=%s, bits=%d, compression=%s)", }; /* Extracted from src/backend/po */ const char *postgresDisconnMsg[] = { "disconnection: session time: %d:%02d:%02d.%03d user=%s database=%s host=%s%s%s"}; // Private functions Timestamp pgauditlogtofile_truncate_timestamp(Timestamp t); #if (PG_VERSION_NUM >= 150000) /** * @brief Request shared memory space * @param void * @return void */ void PgAuditLogToFile_shmem_request(void) { if (prev_shmem_request_hook) prev_shmem_request_hook(); RequestAddinShmemSpace(MAXALIGN(sizeof(PgAuditLogToFileShm))); RequestNamedLWLockTranche("pgauditlogtofile", 1); } #endif /** * @brief SHMEM startup hook - Initialize SHMEM structure * @param void * @return void */ void PgAuditLogToFile_shmem_startup(void) { bool found; size_t num_messages, i, j; char **prefixes = NULL; // Execute other hooks if (prev_shmem_startup_hook) prev_shmem_startup_hook(); /* reset in case this is a restart within the postmaster */ pgaudit_ltf_shm = NULL; LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); pgaudit_ltf_shm = ShmemInitStruct("pgauditlogtofile", sizeof(PgAuditLogToFileShm), &found); if (!found) { pg_atomic_init_flag(&pgaudit_ltf_flag_shutdown); // Get unique prefixes and copy them to SHMEM num_messages = sizeof(postgresConnMsg) / sizeof(char *); prefixes = PgAuditLogToFile_connect_UniquePrefixes(postgresConnMsg, num_messages, &pgaudit_ltf_shm->num_prefixes_connection); pgaudit_ltf_shm->prefixes_connection = ShmemAlloc(pgaudit_ltf_shm->num_prefixes_connection * sizeof(PgAuditLogToFilePrefix *)); for (i = 0, j = 0; i < num_messages; i++) { if (prefixes != NULL && prefixes[i] != NULL) { pgaudit_ltf_shm->prefixes_connection[j] = ShmemAlloc(sizeof(PgAuditLogToFilePrefix)); pgaudit_ltf_shm->prefixes_connection[j]->length = strlen(prefixes[i]); pgaudit_ltf_shm->prefixes_connection[j]->prefix = ShmemAlloc((pgaudit_ltf_shm->prefixes_connection[j]->length + 1) * sizeof(char)); strcpy(pgaudit_ltf_shm->prefixes_connection[j]->prefix, prefixes[i]); pfree(prefixes[i]); j++; } } pfree(prefixes); num_messages = sizeof(postgresDisconnMsg) / sizeof(char *); prefixes = PgAuditLogToFile_connect_UniquePrefixes(postgresDisconnMsg, num_messages, &pgaudit_ltf_shm->num_prefixes_disconnection); pgaudit_ltf_shm->prefixes_disconnection = ShmemAlloc(pgaudit_ltf_shm->num_prefixes_disconnection * sizeof(PgAuditLogToFilePrefix *)); for (i = 0, j = 0; i < num_messages; i++) { if (prefixes != NULL && prefixes[i] != NULL) { pgaudit_ltf_shm->prefixes_disconnection[j] = ShmemAlloc(sizeof(PgAuditLogToFilePrefix)); pgaudit_ltf_shm->prefixes_disconnection[j]->length = strlen(prefixes[i]); pgaudit_ltf_shm->prefixes_disconnection[j]->prefix = ShmemAlloc((pgaudit_ltf_shm->prefixes_disconnection[j]->length + 1) * sizeof(char)); strcpy(pgaudit_ltf_shm->prefixes_disconnection[j]->prefix, prefixes[i]); pfree(prefixes[i]); j++; } } pfree(prefixes); pgaudit_ltf_shm->lock = &(GetNamedLWLockTranche("pgauditlogtofile"))->lock; PgAuditLogToFile_calculate_current_filename(); PgAuditLogToFile_set_next_rotation_time(); } LWLockRelease(AddinShmemInitLock); if (IsUnderPostmaster) { // Backend pg_atomic_init_flag(&pgaudit_ltf_autoclose_flag_thread); } else { // Postmaster on_shmem_exit(PgAuditLogToFile_shmem_shutdown, (Datum)0); } if (!found) ereport(LOG, (errmsg("pgauditlogtofile extension initialized"))); } /** * @brief SHMEM shutdown hook * @param code: code * @param arg: arg * @return void */ void PgAuditLogToFile_shmem_shutdown(int code, Datum arg) { pg_atomic_test_set_flag(&pgaudit_ltf_flag_shutdown); } /** * @brief Generates the name for the audit log file * @param void * @return void */ void PgAuditLogToFile_calculate_current_filename(void) { char *filename = NULL; if (UsedShmemSegAddr == NULL || pgaudit_ltf_shm == NULL) return; filename = PgAuditLogToFile_current_filename(); if (filename == NULL) { ereport(WARNING, (errmsg("pgauditlogtofile failed to calculate filename"))); return; } LWLockAcquire(pgaudit_ltf_shm->lock, LW_EXCLUSIVE); memset(pgaudit_ltf_shm->filename, 0, sizeof(pgaudit_ltf_shm->filename)); strcpy(pgaudit_ltf_shm->filename, filename); LWLockRelease(pgaudit_ltf_shm->lock); pfree(filename); } /* * @brief Checks if the audit log file needs to be rotated before we use it * @param void * @return bool: true if the file needs to be rotated */ bool PgAuditLogToFile_needs_rotate_file(void) { pg_time_t now; if (UsedShmemSegAddr == NULL || pgaudit_ltf_shm == NULL) return false; if (guc_pgaudit_ltf_log_rotation_age < 1) return false; now = (pg_time_t) time(NULL); if (now >= pgaudit_ltf_shm->next_rotation_time) { ereport(DEBUG3, (errmsg("pgauditlogtofile needs to rotate file %s", pgaudit_ltf_shm->filename))); return true; } return false; }pgauditlogtofile-1.6.1/logtofile_shmem.h000066400000000000000000000014741464451112400204170ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_shmem.h * Functions to manage shared memory * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_SHMEM_H_ #define _LOGTOFILE_SHMEM_H_ #include /* Hook functions */ extern void PgAuditLogToFile_shmem_startup(void); extern void PgAuditLogToFile_shmem_shutdown(int code, Datum arg); #if (PG_VERSION_NUM >= 150000) extern void PgAuditLogToFile_shmem_request(void); #endif extern void PgAuditLogToFile_calculate_current_filename(void); extern bool PgAuditLogToFile_needs_rotate_file(void); #endif pgauditlogtofile-1.6.1/logtofile_vars.c000066400000000000000000000027751464451112400202610ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_vars.c * Global variables for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "logtofile_vars.h" // Guc char *guc_pgaudit_ltf_log_directory = NULL; char *guc_pgaudit_ltf_log_filename = NULL; char *guc_pgaudit_log_last_rotation = NULL; int guc_pgaudit_ltf_log_rotation_age = HOURS_PER_DAY * MINS_PER_HOUR; // Default: 1 day bool guc_pgaudit_ltf_log_connections = false; // Default: off bool guc_pgaudit_ltf_log_disconnections = false; // Default: off int guc_pgaudit_ltf_auto_close_minutes = 0; // Default: off // Audit log file handler FILE *pgaudit_ltf_file_handler = NULL; // Background auto-close file handler pg_atomic_flag pgaudit_ltf_autoclose_flag_thread; pthread_t pgaudit_ltf_autoclose_thread; pthread_attr_t pgaudit_ltf_autoclose_thread_attr; TimestampTz pgaudit_ltf_autoclose_active_ts; // Hook log emit_log_hook_type prev_emit_log_hook = NULL; // Shared memory PgAuditLogToFileShm *pgaudit_ltf_shm = NULL; pg_atomic_flag pgaudit_ltf_flag_shutdown; // Shared memory hook shmem_startup_hook_type prev_shmem_startup_hook = NULL; #if (PG_VERSION_NUM >= 150000) shmem_request_hook_type prev_shmem_request_hook = NULL; #endif pgauditlogtofile-1.6.1/logtofile_vars.h000066400000000000000000000037471464451112400202660ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * logtofile_vars.h * Global variables for logtofile * * Copyright (c) 2020-2024, Francisco Miguel Biete Banon * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #ifndef _LOGTOFILE_VARS_H_ #define _LOGTOFILE_VARS_H_ #include #include #include #include #include #include #include #include // Guc extern char *guc_pgaudit_ltf_log_directory; extern char *guc_pgaudit_ltf_log_filename; extern int guc_pgaudit_ltf_log_rotation_age; extern bool guc_pgaudit_ltf_log_connections; extern bool guc_pgaudit_ltf_log_disconnections; extern int guc_pgaudit_ltf_auto_close_minutes; // Audit log file handler extern FILE *pgaudit_ltf_file_handler; // Background auto-close file handler extern pg_atomic_flag pgaudit_ltf_autoclose_flag_thread; extern pthread_t pgaudit_ltf_autoclose_thread; extern pthread_attr_t pgaudit_ltf_autoclose_thread_attr; extern Timestamp pgaudit_ltf_autoclose_active_ts; // Hook log extern emit_log_hook_type prev_emit_log_hook; // Shared Memory types typedef struct PgAuditLogToFilePrefix { char *prefix; int length; } PgAuditLogToFilePrefix; typedef struct pgAuditLogToFileShm { LWLock *lock; PgAuditLogToFilePrefix **prefixes_connection; size_t num_prefixes_connection; PgAuditLogToFilePrefix **prefixes_disconnection; size_t num_prefixes_disconnection; char filename[MAXPGPATH]; pg_time_t next_rotation_time; } PgAuditLogToFileShm; // Shared Memory extern PgAuditLogToFileShm *pgaudit_ltf_shm; extern pg_atomic_flag pgaudit_ltf_flag_shutdown; // Shared Memory - Hook extern shmem_startup_hook_type prev_shmem_startup_hook; #if (PG_VERSION_NUM >= 150000) extern shmem_request_hook_type prev_shmem_request_hook; #endif #endif pgauditlogtofile-1.6.1/pgauditlogtofile--1.0--1.2.sql000066400000000000000000000003301464451112400220660ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.0--1.2.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgauditlogtofile UPDATE TO '1.2'" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile--1.0.sql000066400000000000000000000003041464451112400215140ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.0.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgauditlogtofile" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile--1.2--1.3.sql000066400000000000000000000003271464451112400220770ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.2--1.3.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgauditlogtofile UPDATE TO '1.3'" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile--1.3--1.4.sql000066400000000000000000000003271464451112400221010ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.3--1.4.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgauditlogtofile UPDATE TO '1.4'" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile--1.4--1.5.sql000066400000000000000000000003271464451112400221030ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.4--1.5.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgauditlogtofile UPDATE TO '1.5'" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile--1.5--1.6.sql000066400000000000000000000003271464451112400221050ustar00rootroot00000000000000/* pgauditlogtofile/pgauditlogtofile--1.5--1.6.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgauditlogtofile UPDATE TO '1.6'" to load this file. \quit pgauditlogtofile-1.6.1/pgauditlogtofile.c000066400000000000000000000010541464451112400205710ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * pgauditlogtofile.c * pgaudit addon to redirect audit log lines to an independent file * * Copyright (c) 2020-2023, Francisco Miguel Biete Banon * Copyright (c) 2014, 2ndQuadrant Ltd. * * This code is released under the PostgreSQL licence, as given at * http://www.postgresql.org/about/licence/ *------------------------------------------------------------------------- */ #include "postgres.h" #include "utils/guc.h" PG_MODULE_MAGIC; #include "logtofile.h" pgauditlogtofile-1.6.1/pgauditlogtofile.control000066400000000000000000000003001464451112400220200ustar00rootroot00000000000000# pgauditlogtofile extension comment = 'pgAudit addon to redirect audit entries to an independent file' default_version = '1.6' module_pathname = '$libdir/pgauditlogtofile' relocatable = true pgauditlogtofile-1.6.1/test/000077500000000000000000000000001464451112400160425ustar00rootroot00000000000000pgauditlogtofile-1.6.1/test/Vagrantfile000066400000000000000000000047151464451112400202360ustar00rootroot00000000000000Vagrant.configure(2) do |config| config.vm.box = "centos/7" #config.vm.box = "geerlingguy/centos7" config.vm.provider :virtualbox do |vb| vb.name = "pgauditlogtofile-centos7-test" end # Provision the VM config.vm.provision "shell", inline: <<-SHELL # Setup environment echo 'export PG_VERSION=12' >> /etc/bashrc echo 'export PATH=$PATH:/usr/pgsql-${PG_VERSION?}/bin' >> /etc/bashrc source /etc/bashrc # Install PostgreSQL rpm -ivh https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postgresql${PG_VERSION}-server # Install SCL llvm toolset 7 and enable it by default yum install -y centos-release-scl-rh epel-release yum install -y postgresql${PG_VERSION}-devel make openssl-devel llvm-toolset-7-clang llvm5.0 echo 'source scl_source enable devtoolset-7' >> /etc/bashrc source /etc/bashrc # Compile & install pgaudit mkdir /pgaudit curl -sSL https://github.com/pgaudit/pgaudit/archive/1.4.0.tar.gz | tar xzf - --strip-components=1 -C /pgaudit make -C /pgaudit install USE_PGXS=1 # Compile & install pgauditlogtofile make -C /pgauditlogtofile install USE_PGXS=1 # Create PostgreSQL cluster /usr/bin/sudo -u postgres /usr/pgsql-${PG_VERSION}/bin/initdb -A trust -k /var/lib/pgsql/${PG_VERSION}/data echo "shared_preload_libraries = 'pgaudit,pgauditlogtofile'" >> /var/lib/pgsql/${PG_VERSION}/data/postgresql.conf /usr/bin/systemctl start postgresql-${PG_VERSION} /usr/bin/sudo -u postgres psql -Xc 'create user vagrant superuser' postgres # Configure pgaudit /usr/bin/sudo -u postgres psql -Xc 'alter system set pgaudit.log = "all"' postgres /usr/bin/sudo -u postgres psql -Xc 'alter system set pgaudit.log_parameter = on' postgres /usr/bin/sudo -u postgres psql -Xc 'select pg_reload_conf()' postgres # Enable pgauditlogtofile /usr/bin/sudo -u postgres psql -Xc 'create extension pgauditlogtofile' postgres /usr/bin/sudo -u postgres psql -Xc "select name, setting, unit from pg_settings where name like 'pgaudit%' order by name" postgres SHELL # Don't share the default vagrant folder config.vm.synced_folder ".", "/vagrant", disabled: true # Mount project path for testing config.vm.synced_folder "..", "/pgauditlogtofile", type: "virtualbox" end