--- a/contrib/auth_delay/Makefile +++ b/contrib/auth_delay/Makefile @@ -3,6 +3,9 @@ MODULES = auth_delay PGFILEDESC = "auth_delay - delay authentication failure reports" +EXTENSION = auth_delay +DATA = auth_delay--1.0.sql + ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/contrib/auth_delay/auth_delay--1.0.sql b/contrib/auth_delay/auth_delay--1.0.sql new file mode 100644 --- /dev/null +++ b/contrib/auth_delay/auth_delay--1.0.sql @@ -0,0 +1,44 @@ +CREATE OR REPLACE FUNCTION auth_delay_conn(OUT id int, OUT remote_host text, OUT delay double precision) + RETURNS SETOF record + AS 'MODULE_PATHNAME', 'auth_delay_conn' + LANGUAGE C IMMUTABLE; + +CREATE VIEW auth_delay_conn AS SELECT * FROM auth_delay_conn(); + +create or replace function release_auth_delay_conn_by_id(id int) + returns void + AS 'MODULE_PATHNAME', 'release_auth_delay_conn_by_id' + LANGUAGE C IMMUTABLE; + +create or replace function release_auth_delay_conn_by_name(name cstring) + returns void + AS 'MODULE_PATHNAME', 'release_auth_delay_conn_by_name' + LANGUAGE C IMMUTABLE; + +CREATE OR REPLACE FUNCTION auth_delay_user(OUT id int, OUT user_name text, OUT total_attempts int) + RETURNS SETOF record + AS 'MODULE_PATHNAME', 'auth_delay_user' + LANGUAGE C IMMUTABLE; + +CREATE VIEW auth_delay_user AS SELECT * FROM auth_delay_user(); + +create or replace function release_auth_delay_user_by_id(id int) + returns void + AS 'MODULE_PATHNAME', 'release_auth_delay_user_by_id' + LANGUAGE C IMMUTABLE; + +create or replace function release_auth_delay_user_by_name(name cstring) + returns void + AS 'MODULE_PATHNAME', 'release_auth_delay_user_by_name' + LANGUAGE C IMMUTABLE; + +alter view auth_delay_conn owner to sys; +alter view auth_delay_user owner to sys; +revoke execute on function release_auth_delay_conn_by_id(int) from public; +revoke execute on function release_auth_delay_conn_by_name(cstring) from public; +revoke execute on function release_auth_delay_user_by_id(int) from public; +revoke execute on function release_auth_delay_user_by_name(cstring) from public; +grant execute on function release_auth_delay_conn_by_id(int) to sys; +grant execute on function release_auth_delay_conn_by_name(cstring) to sys; +grant execute on function release_auth_delay_user_by_id(int) to sys; +grant execute on function release_auth_delay_user_by_name(cstring) to sys; diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -2,7 +2,7 @@ * * auth_delay.c * - * Copyright (c) 2010-2019, PostgreSQL Global Development Group * * IDENTIFICATION * contrib/auth_delay/auth_delay.c @@ -18,35 +18,711 @@ #include "utils/guc.h" #include "utils/timestamp.h" +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "lib/ilist.h" +#include "storage/ipc.h" +#include "storage/shmem.h" +#include "utils/builtins.h" +#include "miscadmin.h" +#include "pg_config_manual.h" +#include "funcapi.h" + +#include "catalog/pg_authid.h" +#include "utils/syscache.h" + +#include + PG_MODULE_MAGIC; void _PG_init(void); +#define MAX_CONN_RECORDS 50 +#define MAX_USER_RECORDS 10 +#define SECONDS_IN_HOUR 3600 +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + /* GUC Variables */ static int auth_delay_milliseconds; +static int auth_delay_max_seconds; +static double auth_delay_ratio; +static int auth_delay_max_attempts; +static int auth_delay_time_range; +static int auth_delay_time_interval; /* Original Hook */ static ClientAuthentication_hook_type original_client_auth_hook = NULL; +typedef struct AuthConnRecord +{ + char remote_host[NAMEDATALEN]; /* how to decide the length? */ + bool used; + double sleep_time; /* in milliseconds */ +} AuthConnRecord; + +typedef struct AuthUserRecord +{ + char user_name[NAMEDATALEN]; + bool used; + time_t last_record_time; + int last_record_index; + int fail_slots[FLEXIBLE_ARRAY_MEMBER]; +} AuthUserRecord; + +typedef enum AuthStatus +{ + AUTH_PASS, + AUTH_EQUAL, + AUTH_FAIL +} AuthStatus; + +static shmem_startup_hook_type shmem_startup_next = NULL; +static AuthUserRecord *aur_array = NULL; +static int *count; +static AuthConnRecord *acr_array = NULL; + +//static bool check_denied_day(Port *port); + +static AuthStatus check_auth_rights(Port *port); +static AuthUserRecord * find_user_record(char *user_name, int *free_index); +static AuthConnRecord * find_conn_record(char *remote_host, int *free_index); +static void cleanup_expired_slots(AuthUserRecord *aur); + +static double record_failed_conn_auth(Port *port); +static void record_conn_failure(AuthConnRecord *acr); +static void record_failed_user_auth(Port *port); +static void record_user_failure(AuthUserRecord *aur); +static int get_failure_count(AuthUserRecord *aur); +static time_t truncated_min_and_sec(const time_t clock); + +static void cleanup_conn_record(Port *port); +static void cleanup_user_record(Port *port); + +Datum auth_delay_conn(PG_FUNCTION_ARGS); +Datum release_auth_delay_conn_by_id(PG_FUNCTION_ARGS); +Datum release_auth_delay_conn_by_name(PG_FUNCTION_ARGS); +Datum auth_delay_user(PG_FUNCTION_ARGS); +Datum release_auth_delay_user_by_id(PG_FUNCTION_ARGS); +Datum release_auth_delay_user_by_name(PG_FUNCTION_ARGS); + /* * Check authentication */ static void auth_delay_checks(Port *port, int status) { +// bool denied; + AuthStatus auth_status; + elog(DEBUG1, "status: %d", status); + /* * Any other plugins which use ClientAuthentication_hook. */ if (original_client_auth_hook) original_client_auth_hook(port, status); +// denied = check_denied_day(port); + auth_status = check_auth_rights(port); + /* * Inject a short delay if authentication failed. */ if (status != STATUS_OK) { - pg_usleep(1000L * auth_delay_milliseconds); + double delay; /* in milliseconds */ + + /* ClientAuthentication() is called twice, ignore the first one + * TODO: why? */ + if (status == STATUS_EOF) + return; + + record_failed_user_auth(port); + delay = record_failed_conn_auth(port); + delay = MIN(delay, 1000L * auth_delay_max_seconds); + // pg_usleep(1000L * auth_delay_milliseconds); + elog(LOG, "Authentication delayed for %f seconds", delay / 1000.0); + pg_usleep(1000L * (long) delay); + +// if (denied) +// elog(ERROR, "user \"%s\" is not allowed to login today", port->user_name); + + /* There are two kinks of failure: 1. wrong password 2. current user + * is locked. We should not reveal the information that the password + * is wrong in the first place. */ + if (auth_status == AUTH_EQUAL || auth_status == AUTH_FAIL) + elog(ERROR, "Too many failed login attempts in the recent"); + } + else if (auth_status == AUTH_FAIL) + { + /* authentication passed but current user is locked */ + elog(ERROR, "Too many failed login attempts in the recent"); + } + else + { + cleanup_conn_record(port); + cleanup_user_record(port); + } + // TODO: success, clean up +} + +/* +static int +get_dayofweek() +{ + time_t cur_time; + struct tm *p; + + time(&cur_time); + p = localtime(&cur_time); + + elog(DEBUG1, "day of week: %d", p->tm_wday); + return p->tm_wday; +} +*/ + +/* + * Check whether a user is denied to login on current day of week. + * Return: + * true: login is denied + * false: login is allowed + */ +/* +static bool +check_denied_day(Port *port) +{ + HeapTuple tuple; + Datum datum; + bool isnull; + char *denied_day; + int i; + int dayofweek; + bool result; + + tuple = SearchSysCache1(AUTHNAME, + PointerGetDatum(port->user_name)); + if (!HeapTupleIsValid(tuple)) + { + elog(ERROR, "Role \"%s\" does not exist.", port->user_name); } + + datum = SysCacheGetAttr(AUTHNAME, tuple, Anum_pg_authid_roldeniedday, &isnull); + if (isnull) + { + result = false; + } + else + { + denied_day = text_to_cstring(DatumGetTextP(datum)); + + dayofweek = get_dayofweek(); + + result = false; + for (i = 0; i < strlen(denied_day); i++) + { + if (denied_day[i] == ',') + continue; + if (denied_day[i] == ('0' + dayofweek)) + result = true; + } + } + + ReleaseSysCache(tuple); + + return result; +} +*/ + +/* + * Get AuthUserRecord pointer according to given index + * Since the length of AuthUserRecord is not fixed, we have to calculate + * the offset. + */ +static AuthUserRecord * +get_aur(int index) +{ + int length; + + Assert(index < MAX_USER_RECORDS); + length = sizeof(AuthUserRecord) + sizeof(int) * (auth_delay_time_range / + auth_delay_time_interval - 1); + + return (AuthUserRecord *) ((char *)aur_array + length * index); +} + +/* + * Check whether current user has the right to login + */ +static AuthStatus +check_auth_rights(Port *port) +{ + int free_index; + AuthUserRecord *aur = NULL; + + aur = find_user_record(port->user_name, &free_index); + if (aur == NULL) + return AUTH_PASS; + + cleanup_expired_slots(aur); + if (get_failure_count(aur) > auth_delay_max_attempts) + return AUTH_FAIL; + else if (get_failure_count(aur) == auth_delay_max_attempts) + return AUTH_EQUAL; + else + return AUTH_PASS; +} + +static AuthUserRecord * +find_user_record(char *user_name, int *free_index) +{ + int i; + + *free_index = -1; + for (i = 0; i < MAX_USER_RECORDS; i++) + { + if (!get_aur(i)->used) + { + if (*free_index == -1) + *free_index = i;// record unused element + continue; + } + if (strcmp(get_aur(i)->user_name, user_name) == 0) + return get_aur(i); + } + + return NULL; +} + +static AuthConnRecord * +find_conn_record(char *remote_host, int *free_index) +{ + int i; + + *free_index = -1; + for (i = 0; i < MAX_CONN_RECORDS; i++) + { + if (!acr_array[i].used) + { + if (*free_index == -1) + *free_index = i;// record unused element + continue; + } + if (strcmp(acr_array[i].remote_host, remote_host) == 0) + return &acr_array[i]; + } + + return NULL; +} + +static void +cleanup_expired_slots(AuthUserRecord *aur) +{ + time_t cur_time; + int i; + int index; + int interval; + int num_slots; + + time(&cur_time); + cur_time = truncated_min_and_sec(cur_time); + + interval = (cur_time - aur->last_record_time) / SECONDS_IN_HOUR; + interval = MIN(interval, auth_delay_time_range) / auth_delay_time_interval; + elog(DEBUG1, "interval: %d", interval); + num_slots = auth_delay_time_range / auth_delay_time_interval; + + index = aur->last_record_index;// in case interval = 0 + for (i = 1; i <= interval; i++) + { + /* These slots are out of time range */ + index = (aur->last_record_index + i) % num_slots; + aur->fail_slots[index] = 0; + } + aur->last_record_index = index; + // aur->last_record_time = cur_time; +} + +static double +record_failed_conn_auth(Port *port) +{ + AuthConnRecord *acr = NULL; + int j = -1; + + acr = find_conn_record(port->remote_host, &j); + + if (!acr) + { + Assert(j != -1);// XXX: no free space, MAX_CONN_RECORDS too small + acr = &acr_array[j]; + strcpy(acr->remote_host, port->remote_host); + acr->used = true; + elog(DEBUG1, "new connection: %s, index: %d", acr->remote_host, j); + } + + record_conn_failure(acr); + return acr->sleep_time; +} + +static void +record_conn_failure(AuthConnRecord *acr) +{ + if (acr->sleep_time == 0) + acr->sleep_time = (double) auth_delay_milliseconds; + else + acr->sleep_time *= auth_delay_ratio; + elog(DEBUG1, "connection %s will sleep for %f seconds", + acr->remote_host, acr->sleep_time / 1000.0); +} + +static void +record_failed_user_auth(Port *port) +{ + AuthUserRecord *aur = NULL; + int j = -1; + + (*count)++; + elog(DEBUG1, "count: %d", *count); + + aur = find_user_record(port->user_name, &j); + elog(DEBUG1, "record_failed_user_auth: %d, %d", get_aur(0)->fail_slots[0], + get_aur(0)->fail_slots[1]); + + if (!aur) + { + Assert(j != -1);// XXX: no free space, MAX_USER_RECORDS too small + aur = get_aur(j); + strcpy(aur->user_name, port->user_name); + aur->used = true; + elog(DEBUG1, "new user: %s, index: %d", aur->user_name, j); + } + + record_user_failure(aur); +} + +static time_t +truncated_min_and_sec(const time_t clock) +{ + struct tm *p = localtime(&clock); + + p->tm_min = 0; + p->tm_sec = 0; + + return mktime(p); +} + +static void +record_user_failure(AuthUserRecord *aur) +{ + int index = -1; + time_t cur_time; + + if (aur->last_record_time == 0) + { + /* New record */ + aur->fail_slots[0] = 1; + aur->last_record_index = 0; + } + else + { + index = aur->last_record_index; + aur->fail_slots[index]++; + } + + time(&cur_time); + cur_time = truncated_min_and_sec(cur_time); + aur->last_record_time = cur_time; + elog(DEBUG1, "user: %s, failure count: %d, index: %d", + aur->user_name, get_failure_count(aur), index); + elog(DEBUG1, "record_user_failure: %d, %d", get_aur(0)->fail_slots[0], + get_aur(0)->fail_slots[1]); +} + +static int +get_failure_count(AuthUserRecord *aur) +{ + int i; + int count = 0; + for (i = 0; i < auth_delay_time_range / auth_delay_time_interval; i++) + count += aur->fail_slots[i]; + + elog(DEBUG1, "total count: %d", count); + return count; +} + +static void +cleanup_conn_record(Port *port) +{ + int free_index; + AuthConnRecord *acr = NULL; + + acr = find_conn_record(port->remote_host, &free_index); + if (acr == NULL) + return; + + acr->used = false; + acr->sleep_time = 0.0; +} + +static void +cleanup_user_record(Port *port) +{ + int free_index; + AuthUserRecord *aur = NULL; + + aur = find_user_record(port->user_name, &free_index); + if (aur == NULL) + return; + + aur->used = false; + aur->last_record_time = 0; + aur->last_record_index = 0; + memset(aur->fail_slots, 0, auth_delay_time_range/auth_delay_time_interval); +} + +Datum +auth_delay_conn(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + Datum values[3]; + bool nulls[3]; + + elog(DEBUG1, "auth_delay_conn() is called"); + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + +/* + * Modified: CreateTemplateTupleDesc(int, bool) -> CreateTemplateTupleDesc(int) + */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "id", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "remote_host", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "delay", + FLOAT8OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + funcctx->max_calls = MAX_CONN_RECORDS; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + while (funcctx->call_cntr < funcctx->max_calls) + { + HeapTuple tuple; + Datum result; + AuthConnRecord *acr; + + acr = &acr_array[funcctx->call_cntr]; + elog(DEBUG1, "cntr: %ld, remote host: %s, used: %d", funcctx->call_cntr, acr->remote_host, acr->used); + if (!acr->used) + { + funcctx->call_cntr++; + continue; + } + + elog(DEBUG1, "remote host: %s", acr->remote_host); + memset(nulls, 0, sizeof(nulls)); + values[0] = Int32GetDatum(funcctx->call_cntr); + values[1] = CStringGetTextDatum(acr->remote_host); + values[2] = Float8GetDatum(acr->sleep_time / 1000.0); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + SRF_RETURN_DONE(funcctx); +} +PG_FUNCTION_INFO_V1(auth_delay_conn); + +Datum +release_auth_delay_conn_by_id(PG_FUNCTION_ARGS) +{ + int id = PG_GETARG_DATUM(0); + + if (id >= MAX_CONN_RECORDS || !acr_array[id].used) + elog(ERROR, "Invalid id"); + else + { + acr_array[id].used = false; + acr_array[id].sleep_time = 0.0; + } + PG_RETURN_VOID(); +} +PG_FUNCTION_INFO_V1(release_auth_delay_conn_by_id); + +Datum +release_auth_delay_conn_by_name(PG_FUNCTION_ARGS) +{ + int j; + char *remote_host = PG_GETARG_CSTRING(0); + AuthConnRecord *acr = find_conn_record(remote_host, &j); + + if (!acr) + elog(ERROR, "Invalid remote host"); + else + { + acr->used = false; + acr->sleep_time = 0.0; + } + + PG_RETURN_VOID(); +} +PG_FUNCTION_INFO_V1(release_auth_delay_conn_by_name); + +Datum +auth_delay_user(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + Datum values[3]; + bool nulls[3]; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + +/* + * Modified: CreateTemplateTupleDesc(int, bool) -> CreateTemplateTupleDesc(int) + */ + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "id", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "user_name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "total_attempts", + INT4OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + funcctx->max_calls = MAX_USER_RECORDS; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + while (funcctx->call_cntr < funcctx->max_calls) + { + HeapTuple tuple; + Datum result; + AuthUserRecord *aur; + + aur = get_aur(funcctx->call_cntr); + if (!aur->used) + { + funcctx->call_cntr++; + continue; + } + + elog(DEBUG1, "fail_slots: %d, %d", aur->fail_slots[0], aur->fail_slots[1]); + memset(nulls, 0, sizeof(nulls)); + values[0] = Int32GetDatum(funcctx->call_cntr); + values[1] = CStringGetTextDatum(aur->user_name); + values[2] = Int32GetDatum(get_failure_count(aur)); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + SRF_RETURN_DONE(funcctx); +} +PG_FUNCTION_INFO_V1(auth_delay_user); + +Datum +release_auth_delay_user_by_id(PG_FUNCTION_ARGS) +{ + int id = PG_GETARG_DATUM(0); + AuthUserRecord *aur; + + if (id >= MAX_USER_RECORDS || !get_aur(id)->used) + elog(ERROR, "Invalid id"); + else + { + aur = get_aur(id); + aur->used = false; + aur->last_record_time = 0; + aur->last_record_index = 0; + memset(aur->fail_slots, 0, auth_delay_time_range/auth_delay_time_interval); + } + PG_RETURN_VOID(); +} +PG_FUNCTION_INFO_V1(release_auth_delay_user_by_id); + +Datum +release_auth_delay_user_by_name(PG_FUNCTION_ARGS) +{ + int j; + char *user_name = PG_GETARG_CSTRING(0); + AuthUserRecord *aur = find_user_record(user_name, &j); + + if (!aur) + elog(ERROR, "Invalid user name"); + else + { + aur->used = false; + aur->last_record_time = 0; + aur->last_record_index = 0; + memset(aur->fail_slots, 0, auth_delay_time_range/auth_delay_time_interval); + } + PG_RETURN_VOID(); +} +PG_FUNCTION_INFO_V1(release_auth_delay_user_by_name); + +/* + * Set up shared memory + */ +static void +auth_delay_shmem_startup(void) +{ + Size required; + bool found; + + if (shmem_startup_next) + shmem_startup_next(); + + count = ShmemInitStruct("Count of failure", sizeof(int), &found); + if (found) + elog(DEBUG1, "variable count already exists: %d", *count); + *count = 0; + + required = (sizeof(AuthUserRecord) + sizeof(int) * (auth_delay_time_range / + auth_delay_time_interval - 1)) * MAX_USER_RECORDS; + aur_array = ShmemInitStruct("Array of AuthUserRecord", required, &found); + if (found)// this should not happen? + elog(DEBUG1, "variable aur_array already exists"); + /* all fileds are set to 0 */ + memset(aur_array, 0, required); + + required = sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + acr_array = ShmemInitStruct("Array of AuthConnRecord", required, &found); + if (found)// this should not happen? + elog(DEBUG1, "variable acr_array already exists"); + /* all fileds are set to 0 */ + memset(acr_array, 0, required); +} + +static bool +guc_check_time_interval(int *newval, void **extra, GucSource source) +{ + if (*newval > auth_delay_time_range) + elog(ERROR, "auth_delay.time_interval should not be greater than " + "auth_delay.time_range"); + if (auth_delay_time_range % *newval != 0) + elog(ERROR, "auth_delay.time_range should be integer multiple of " + "auth_delay.time_interval"); + + return true; } /* @@ -55,6 +731,13 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { + Size required; + + if (!process_shared_preload_libraries_in_progress) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("auth_delay must be loaded via shared_preload_libraries"))); + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", @@ -64,10 +747,64 @@ _PG_init(void) 0, INT_MAX / 1000, PGC_SIGHUP, GUC_UNIT_MS, + NULL, NULL, NULL); + DefineCustomIntVariable("auth_delay.max_seconds", + "Maximum seconds to wait if fail to login", NULL, + &auth_delay_max_seconds, + 100, + 0, INT_MAX, + PGC_POSTMASTER, + GUC_UNIT_S, + NULL, NULL, NULL); + DefineCustomRealVariable("auth_delay.ratio", + "Incremental ratio of waiting time", + NULL, + &auth_delay_ratio, + 1.0, + 1.0, 1000.0, + PGC_POSTMASTER, + GUC_UNIT_S, + NULL, NULL, NULL); + DefineCustomIntVariable("auth_delay.max_attempts", + "Maximum number of attempts to login of a user", + NULL, + &auth_delay_max_attempts, + 2, + 0, 1000, + PGC_POSTMASTER, + 0, + NULL, NULL, NULL); + DefineCustomIntVariable("auth_delay.time_range", + "Time range during which failure login attempts are counted", NULL, - NULL); + &auth_delay_time_range, + 1, + 1, 100, + PGC_POSTMASTER, + 0, + NULL, NULL, NULL); + DefineCustomIntVariable("auth_delay.time_interval", + "Time interval during which failure login attempts are counted", + NULL, + &auth_delay_time_interval, + 1, + 1, 100, + PGC_POSTMASTER, + 0, + guc_check_time_interval, + NULL, NULL); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = auth_delay_checks; + + /* Set up shared memory */ + shmem_startup_next = shmem_startup_hook; + shmem_startup_hook = auth_delay_shmem_startup; + + required = (sizeof(AuthUserRecord) + sizeof(int) * (auth_delay_time_range / + auth_delay_time_interval - 1)) * MAX_USER_RECORDS; + required += sizeof(AuthConnRecord) * MAX_CONN_RECORDS; + required += sizeof(int); + RequestAddinShmemSpace(required); } diff --git a/contrib/auth_delay/auth_delay.control b/contrib/auth_delay/auth_delay.control new file mode 100644 --- /dev/null +++ b/contrib/auth_delay/auth_delay.control @@ -0,0 +1,5 @@ +# auth_delay extension +comment = 'Extented authentication delay for users and connections' +default_version = '1.0' +module_pathname = '$libdir/auth_delay' +relocatable = false