blob: aeb69cad8037ef5a86268cb9dc34f23273affa50 [file] [log] [blame]
/*-------------------------------------------------------------------------
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* loginmonitor.c
* PostgreSQL Integrated Login Monitor Daemon
*
* IDENTIFICATION
* src/backend/postmaster/loginmonitor.c
*
* Like autovacuum, the login monitor is structured in two different
* kinds of processes: the login monitor launcher and the login monitor
* worker. The launcher is an always-running process, started by the
* postmaster. It is mainly used to process user's failed login
* authentication. It will always running in loop waiting for failed
* login signal. The launcher will signal postmaster to fork the worker
* process when it receives failed authentication signal from the postgres
* process. The worker process is the process which doing the actual
* working; as the worker process is only forked when authenticate failed,
* one worker process is enough to finish that. It will be forked from
* the postmaster as needed. Like normal postgres process, login monitor
* worker is equipped with locks, transactions, read/write catalog table
* and other functionalities.
*
* The login monitor launcher cannot start the worker process by itself,
* as doing so would cause robustness issues (namely, failure to shut
* them down on exceptional conditions, and also, since the launcher is
* connected to shared memory and is thus subject to corruption there,
* it is not as robust as the postmaster). So it leaves that task to the
* postmaster.
*
* There is a login monitor shared memory area, where the launcher
* stores information about its pid and latch. What's more, the worker
* stores the current login user's name and it's latch to shared memory.
* There is also a flag between launcher and worker to indicate whether
* the failed login authentication signal has been processed.
*
* When there is a failed login authentication, the postgres process will
* send the signal to the postmaster and wait the shared memory latch.
* The launcher received signal from postgres process, it will set the
* flag to true to indicate the signal is processing. And it will resend
* a signal to postmaster and wait the latch of lm_latch in shared memory.
* After receiving the signal from the launcher, the postmaster will fork
* the worker to do the actual working. After the worker finishes work,
* it will notify postgres process and the launcher by setting the latch
* and the lm_latch in shared memory. Moreover, it will reset the flag to
* false to indicate the work is finished.
*
* Only when the user is able to use profile, process will send signal to
* login monitor and wait for the completion of worker process. Otherwise,
* the failed login authentication of the user will be ignored and doesn't
* need to send signal to the launcher.
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/reloptions.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_profile.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/loginmonitor.h"
#include "postmaster/postmaster.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/ps_status.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/timeout.h"
#include "cdb/cdbvars.h"
/* Memory context for login monitor rewrites catalogs */
static MemoryContext LoginMonitorMemCxt;
static pid_t LoginMonitorPID;
/*
* The main Login Monitor shmem struct. On shared memory we store it,
* which will be set by failed proc and reset by login monitor
* post-processing. This struct keeps:
*
* lm_pid Login Monitor Launcher Process pid
* lm_latch Login Monitor Launcher Latch pointer
* curr_user_name current failed user name
* latch pointer of current failed proc's latch
* login_failed_requested whether current is handling a failed login
*
* curr_user_name, latch and login_failed_requested are protected by LWLock LoginFailedSharedMemoryLock
*/
typedef struct {
pid_t lm_pid;
Latch *lm_latch;
char curr_user_name[NAMEDATALEN];
Latch *latch;
sig_atomic_t login_failed_requested;
} LoginMonitorShmemStruct;
static LoginMonitorShmemStruct *LoginMonitorShmem;
/* Flags to tell if we are in an login monitor process */
static bool am_login_monitor_launcher = false;
static bool am_login_monitor_worker = false;
static void LoginMonitorShutdown(void);
static void HandleLoginMonitorInterrupts(void);
static void LoginMonitorShmemReset(void);
#ifdef EXEC_BACKEND
static pid_t lmlauncher_forkexec(void);
static pid_t lmworker_forkexec(void);
#endif
NON_EXEC_STATIC void LoginMonitorLauncherMain(int argc, char *argv[]);
NON_EXEC_STATIC void LoginMonitorWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();
static void record_failed_login(void);
/********************************************************************
* LOGIN MONITOR CODE
********************************************************************/
#ifdef EXEC_BACKEND
/*
* forkexec routine for the login monitor launcher process.
*
* Format up the arglist, then fork and exec.
*/
static pid_t
lmlauncher_forkexec(void)
{
char *lm[10];
int lc = 0;
lm[lc++] = "postgres";
lm[lc++] = "--forkloginmonitor";
lm[lc++] = NULL;
lm[lc] = NULL;
Assert(lc < lengthof(lm));
return postmaster_forkexec(lc, lm);
}
#endif
/*
* Main entry point for login monitor launcher process, to be called from
* the postmaster.
*/
int
StartLoginMonitorLauncher(void) {
#ifdef EXEC_BACKEND
switch ((LoginMonitorPID = lmlauncher_forkexec()))
#else
switch ((LoginMonitorPID = fork_process()))
#endif
{
case -1:
ereport(LOG,
(errmsg("could not fork login monitor process: %m")));
return 0;
#ifndef EXEC_BACKEND
case 0:
/* in postgresmaster child ... */
InitPostmasterChild();
/* Close the postmaster's sockets */
ClosePostmasterPorts(false);
LoginMonitorLauncherMain(0, NULL);
break;
#endif
default:
return (int) LoginMonitorPID;
}
/* shouldn't get here */
return 0;
}
/*
* Main loop for the login monitor process.
*/
NON_EXEC_STATIC void
LoginMonitorLauncherMain(int argc, char *argv[]) {
sigjmp_buf local_sigjmp_buf;
am_login_monitor_launcher = true;
MyBackendType = B_LOGIN_MONITOR;
init_ps_display(NULL);
ereport(DEBUG1,
(errmsg_internal("login monitor launcher started")));
SetProcessingMode(InitProcessing);
/*
* Set up signal handler. We operate on databases much like a regular
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, StatementCancelHandler);
pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
/* SIGQUIT handler was already set up by InitPostmasterChild */
InitializeTimeouts(); /* established SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
pqsignal(SIGFPE, FloatExceptionHandler);
pqsignal(SIGCHLD, SIG_DFL);
/* Early initialization */
BaseInit();
/*
* Create a per-backend PGPROC struct in shared memory, except in the
* EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
* this before we can use LWLocks (and in the EXEC_BACKEND case we already
* had to do some stuff with LWLocks).
*/
#ifndef EXEC_BACKEND
InitProcess();
#endif
InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL, false);
SetProcessingMode(NormalProcessing);
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
* possible memory leaks.
*/
LoginMonitorMemCxt = AllocSetContextCreate(TopMemoryContext,
"Login Monitor",
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(LoginMonitorMemCxt);
/*
* If an exception is encountered, processing resumes here.
*
* This code is a stripped down version of PostgresMain error recovery.
*
* Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
* (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
* signals other than SIGQUIT will be blocked until we complete error
* recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
* call redundant, but it is not since InterruptPending might be set
* already.
*/
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
/* since not using PG_TRY, must reset error stack by hand */
error_context_stack = NULL;
/* Prevents interrupts while cleaning up */
HOLD_INTERRUPTS();
/* Forget any pending QueryCancel or timeout request */
disable_all_timeouts(false);
QueryCancelPending = false; /* second to avoid race condition */
/* Report the error to the server log */
EmitErrorReport();
/* Abort the current transaction in order to recover */
AbortCurrentTransaction();
/*
* Release any other resources, for the case where we were not in a
* transaction.
*/
LWLockReleaseAll();
AbortBufferIO();
UnlockBuffers();
/* this is probably dead code, but let's be safe: */
if (AuxProcessResourceOwner)
ReleaseAuxProcessResources(false);
AtEOXact_Buffers(false);
AtEOXact_SMgr();
AtEOXact_Files(false);
AtEOXact_HashTables(false);
/*
* Now return to normal top-level context and clear ErrorContext for
* next time.
*/
MemoryContextSwitchTo(LoginMonitorMemCxt);
FlushErrorState();
/* Flush any leaked data in the top-level context */
MemoryContextResetAndDeleteChildren(LoginMonitorMemCxt);
/* Now we can allow interrupts again */
RESUME_INTERRUPTS();
/* reset and notify process by latch */
if (LoginMonitorShmem->latch)
SetLatch(LoginMonitorShmem->latch);
ResetLatch(LoginMonitorShmem->lm_latch);
LoginMonitorShmemReset();
/* if in shutdown mode, no need for anything further; just go away */
if (ShutdownRequestPending)
LoginMonitorShutdown();
/*
* Sleep at least 1 milli second after any error. We don't want to be
* filling the error logs as fast as we can.
*/
pg_usleep(1000L);
}
/* We can now handle ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
/* must unblock signals before calling rebuild */
PG_SETMASK(&UnBlockSig);
/*
* Set always-secure search path.
*/
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force zero_damaged_pages OFF in the login monitor process, even if it is set`
* in postgresql.conf. We don't really want such a dangerous option being applied
* non-interactively.
*/
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force settable timeouts off to avoid letting these settings prevent
* regular maintenance from being executed.
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("idle_in_transaction_session_timeout", "0",
PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force default_transaction_isolation to READ COMMITTED. We don't want
* to pay the overhead of serializable mode, nor add any risk of causing
* deadlocks or delaying other transactions.
*/
SetConfigOption("default_transaction_isolation", "read committed",
PGC_SUSET, PGC_S_OVERRIDE);
LoginMonitorShmem->lm_pid = MyProcPid;
LoginMonitorShmem->lm_latch = MyLatch;
/*
* Main loop until shutdown request
*/
while (!ShutdownRequestPending) {
/*
* Wait until naptime expires or we get some type of signal (all the
* signal handlers will wake us by calling SetLatch).
*/
(void) WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
5 * 1000L,
WAIT_EVENT_LOGIN_MONITOR_LAUNCHER_MAIN);
/* Clear any already-pending wakeups */
ResetLatch(MyLatch);
HandleLoginMonitorInterrupts();
/*
* As only one worker can run at any time, before start a worker,
* we should check whether there is already one worker running.
*/
if (LoginMonitorShmem->login_failed_requested)
{
SendPostmasterSignal(PMSIGNAL_START_LOGIN_MONITOR_WORKER);
elog(DEBUG1, "Login monitor launcher is processing uesr %s and has sent starting"
"worker signal to postmaster.", LoginMonitorShmem->curr_user_name);
WaitLatch(LoginMonitorShmem->lm_latch,
WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
WAIT_EVENT_LOGINMONITOR_FINISH);
/* Clear any already-pending wakeups */
ResetLatch(LoginMonitorShmem->lm_latch);
}
}
LoginMonitorShutdown();
}
/********************************************************************
* LOGIN MONITOR WORKER CODE
********************************************************************/
/*
* Main entry point for login monitor process.
*
* This code is heavily based on pgarch.c, q.v.
*/
int
StartLoginMonitorWorker(void) {
pid_t worker_pid;
#ifdef EXEC_BACKEND
switch ((worker_pid = lmworker_forkexec()))
#else
switch ((worker_pid = fork_process()))
#endif
{
case -1:
ereport(LOG,
(errmsg("could not fork login monitor worker process: %m")));
return 0;
#ifndef EXEC_BACKEND
case 0:
/* in postmaster child ... */
InitPostmasterChild();
/* Close the postmaster's sockets */
ClosePostmasterPorts(false);
LoginMonitorWorkerMain(0, NULL);
break;
#endif
default:
return (int) worker_pid;
}
/* shouldn't get here */
return 0;
}
/*
* LoginMonitorWorkerMain
*/
NON_EXEC_STATIC void
LoginMonitorWorkerMain(int argc, char *argv[]) {
sigjmp_buf local_sigjmp_buf;
am_login_monitor_worker = true;
/* MPP-4990: LoginMonitor always runs as utility-mode */
if (IS_QUERY_DISPATCHER())
Gp_role = GP_ROLE_DISPATCH;
else
Gp_role = GP_ROLE_UTILITY;
MyBackendType = B_LOGIN_MONITOR_WORKER;
init_ps_display(NULL);
SetProcessingMode(InitProcessing);
/*
* Set up signal handlers. We operate on databases much like a regular
* backend, so we use the same signal handling. See equivalent code in
* tcop/postgres.c.
*/
pqsignal(SIGHUP, SignalHandlerForConfigReload);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, die);
/* SIGQUIT handler was already set up by InitPostmasterChild */
InitializeTimeouts(); /* establishes SIGALRM handler */
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, procsignal_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
pqsignal(SIGFPE, FloatExceptionHandler);
pqsignal(SIGCHLD, SIG_DFL);
/* Early initialization */
BaseInit();
/*
* Create a per-backend PGPROC struct in shared memory, except in the
* EXEC_BACKEND case where this was done in SubPostmasterMain. We must do
* this before we can use LWLocks (and in the EXEC_BACKEND case we already
* had to do some stuff with LWLocks).
*/
#ifndef EXEC_BACKEND
InitProcess();
#endif
/*
* If an exception is encountered, processing resumes here.
*
* This code is a stripped down version of PostgresMain error recovery.
*
* Note that we use sigsetjmp(..., 1), so that the prevailing signal mask
* (to wit, BlockSig) will be restored when longjmp'ing to here. Thus,
* signals other than SIGQUIT will be blocked until we complete error
* recovery. It might seem that this policy makes the HOLD_INTERRUPTS()
* call redundant, but it is not since InterruptPending might be set
* already.
*/
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
/* since not using PG_TRY, must reset error stack by hand */
error_context_stack = NULL;
/* Prevents interrupts while cleaning up */
HOLD_INTERRUPTS();
/* Report the error to the server log */
EmitErrorReport();
SetLatch(LoginMonitorShmem->lm_latch);
/*
* We can now go away. Note that because we called InitProcess, a
* callback was registered to do ProcKill, which will clean up
* necessary state.
*/
proc_exit(0);
}
/* We can now handle ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
PG_SETMASK(&UnBlockSig);
/*
* Set always-secure search path.
*/
SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force zero_damaged_pages OFF in the login monitor process, even if it is set`
* in postgresql.conf. We don't really want such a dangerous option being applied
* non-interactively.
*/
SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force settable timeouts off to avoid letting these settings prevent
* regular maintenance from being executed.
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("idle_in_transaction_session_timeout", "0",
PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force default_transaction_isolation to READ COMMITTED. We don't want
* to pay the overhead of serializable mode, nor add any risk of causing
* deadlocks or delaying other transactions.
*/
SetConfigOption("default_transaction_isolation", "read committed",
PGC_SUSET, PGC_S_OVERRIDE);
/*
* Force synchronous replication off to allow regular maintenance even if
* we are waiting for standbys to connect. This is important to ensure we
* aren't blocked from performing anti-wraparound tasks.
*/
if (synchronous_commit > SYNCHRONOUS_COMMIT_LOCAL_FLUSH)
SetConfigOption("synchronous_commit", "local",
PGC_SUSET, PGC_S_OVERRIDE);
if (LoginMonitorShmem->curr_user_name[0] != '\0')
{
InitPostgres(DB_FOR_COMMON_ACCESS, InvalidOid, NULL, InvalidOid, NULL, false);
SetProcessingMode(NormalProcessing);
set_ps_display(LoginMonitorShmem->curr_user_name);
ereport(DEBUG1,
(errmsg_internal("login monitor: processing user \"%s\" failed",
LoginMonitorShmem->curr_user_name)));
record_failed_login();
}
proc_exit(0);
}
/*
* Process any new interrupts.
*/
static void
HandleLoginMonitorInterrupts(void) {
/* the normal shutdown case */
if (ShutdownRequestPending)
LoginMonitorShutdown();
if (ConfigReloadPending) {
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}
/* Process barrier events */
if (ProcSignalBarrierPending)
ProcessProcSignalBarrier();
/* Perform logging of memory contexts of this process */
if (LogMemoryContextPending)
ProcessLogMemoryContextInterrupt();
/* Process sinval catchup interrupts that happened while sleeping */
ProcessCatchupInterrupt();
}
/*
* Process a failed login authentication
*
* As this will only record the current failed login, we don't need
* CHECK_FOR_INTERRUPTS during process.
*/
static void
record_failed_login(void) {
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
bool new_record_repl[Natts_pg_authid];
Relation pg_authid_rel;
Relation pg_profile_rel;
TupleDesc pg_authid_dsc;
HeapTuple profile_tuple;
HeapTuple auth_tuple;
HeapTuple new_tuple;
Form_pg_profile profileform;
int32 failed_login_attempts;
int32 profile_failed_login_attempts;
bool isnull;
int32 profileid;
TimestampTz now;
/* Start a transaction so our commands have one to play into. */
StartTransactionCommand();
/* Acquire LWLock */
LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
elog(DEBUG1, "Login monitor worker has acquired LoginFailedSharedMemoryLock for process user %s.",
LoginMonitorShmem->curr_user_name);
/*
* Update related catalog and check whether the account need to be locked
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
pg_profile_rel = table_open(ProfileRelationId, AccessShareLock);
auth_tuple = SearchSysCache1(AUTHNAME,
CStringGetDatum(LoginMonitorShmem->curr_user_name));
Assert(HeapTupleIsValid(auth_tuple));
/* get current failed_login_attempts */
failed_login_attempts = SysCacheGetAttr(AUTHNAME, auth_tuple,
Anum_pg_authid_rolfailedlogins, &isnull);
Assert(!isnull);
/* increase failed_login_attempts by one */
failed_login_attempts++;
elog(DEBUG1, "User %s FAILED LOGIN ATTEMPTS is %d in Login Monitor worker",
LoginMonitorShmem->curr_user_name, failed_login_attempts);
/*
* Build an updated tuple, perusing the information just obtained
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, true, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
new_record[Anum_pg_authid_rolfailedlogins - 1] =
Int32GetDatum(failed_login_attempts);
new_record_nulls[Anum_pg_authid_rolfailedlogins - 1] =
false;
new_record_repl[Anum_pg_authid_rolfailedlogins - 1] =
true;
/* get the user's current profile oid */
profileid = SysCacheGetAttr(AUTHNAME, auth_tuple,
Anum_pg_authid_rolprofile, &isnull);
Assert(!isnull);
/* get user's current profile tuple */
profile_tuple = SearchSysCache1(PROFILEID, ObjectIdGetDatum(profileid));
Assert(HeapTupleIsValid(profile_tuple));
profileform = (Form_pg_profile) GETSTRUCT(profile_tuple);
/*
* Transform failed_login_attempts to normal value if it's
* PROFILE_DEFAULT or PROFILE_UNLIMITED.
*/
profile_failed_login_attempts =
tranformProfileValueToNormal(profileform->prffailedloginattempts,
Anum_pg_profile_prffailedloginattempts);
/*
* If user's failed_login_attempts is bigger equal than current
* profile's failed_login_attempts, update account status to
* locked and lockdate to now.
*/
if (failed_login_attempts >= profile_failed_login_attempts) {
new_record[Anum_pg_authid_rolaccountstatus - 1] =
Int32GetDatum(ROLE_ACCOUNT_STATUS_LOCKED_TIMED);
new_record_nulls[Anum_pg_authid_rolaccountstatus - 1] =
false;
new_record_repl[Anum_pg_authid_rolaccountstatus - 1] =
true;
now = GetCurrentTimestamp();
new_record[Anum_pg_authid_rollockdate - 1] =
Int64GetDatum(now);
new_record_nulls[Anum_pg_authid_rollockdate - 1] =
false;
new_record_repl[Anum_pg_authid_rollockdate - 1] =
true;
}
new_tuple = heap_modify_tuple(auth_tuple, pg_authid_dsc,
new_record, new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &auth_tuple->t_self, new_tuple);
InvokeObjectPostAlterHook(AuthIdRelationId, profileid, 0);
ReleaseSysCache(auth_tuple);
ReleaseSysCache(profile_tuple);
table_close(pg_profile_rel, NoLock);
table_close(pg_authid_rel, NoLock);
/* reset login_failed_requested to false */
LoginMonitorShmem->login_failed_requested = false;
/* notify process by setting latch */
SetLatch(LoginMonitorShmem->lm_latch);
SetLatch(LoginMonitorShmem->latch);
LWLockRelease(LoginFailedSharedMemoryLock);
elog(DEBUG1, "Login monitor worker has released LoginFailedSharedMemoryLock for process user %s.",
LoginMonitorShmem->curr_user_name);
CommitTransactionCommand();
}
/*
* Notify Login Monitor Launcher by LoginMonitorShmem
*/
void
LoginMonitorWorkerFailed(void) {
SetLatch(LoginMonitorShmem->lm_latch);
}
/*
* Perform a normal exit from the login monitor.
*/
static void
LoginMonitorShutdown(void) {
ereport(DEBUG1,
(errmsg_internal("login monitor shutting down")));
LoginMonitorShmem->lm_pid = 0;
LoginMonitorShmem->lm_latch = NULL;
proc_exit(0);
}
/*
* SIGUSR1: a login password authentication failed
*/
void
HandleLoginFailed(void) {
int save_errno = errno;
LoginMonitorShmem->login_failed_requested = true;
SetLatch(MyLatch);
errno = save_errno;
}
/*
* IsLoginMonitorLauncher functions
* Return whether this is a login monitor launcher process.
*/
bool
IsLoginMonitorLauncherProcess(void) {
return am_login_monitor_launcher;
}
/*
* IsLoginMonitorWorker functions
* Return whether this is a login monitor worker process.
*/
bool
IsLoginMonitorWorkerProcess(void) {
return am_login_monitor_worker;
}
/*
* LoginMonitorShmemSize
* Compute space needed for login monitor-related shared memory
*/
Size
LoginMonitorShmemSize(void) {
Size size;
size = sizeof(LoginMonitorShmemStruct);
return size;
}
/*
* LoginMonitorShmemInit
* Allocate and initialize login monitor-related shared memory
*/
void
LoginMonitorShmemInit(void) {
bool found;
LoginMonitorShmem = (LoginMonitorShmemStruct *)
ShmemInitStruct("Login Monitor Data",
LoginMonitorShmemSize(),
&found);
if (!IsUnderPostmaster) {
Assert(!found);
LoginMonitorShmem->lm_pid = 0;
LoginMonitorShmem->lm_latch = NULL;
memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
LoginMonitorShmem->latch = NULL;
LoginMonitorShmem->login_failed_requested = false;
} else
Assert(found);
}
/*
* SendLoginFailedSignal - signal the postmaster after failed authentication.
*/
void
SendLoginFailedSignal(const char *curr_user_name) {
int rc;
/* If called in a standalone backend or Login Monitor PID is 0, do nothing */
if (!IsUnderPostmaster || !LoginMonitorPID)
return;
/*
* Before sending signal, we need to acquire lock in shared memory and set
* current user oid. Only when login monitor backend process has solved the
* current signal, the lock will be released. By this way, the login monitor
* solve the postgres signal serially which will avoid dead lock.
*/
LWLockAcquire(LoginFailedControlLock, LW_EXCLUSIVE);
LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
elog(DEBUG1, "User %s has acquire LoginFailedControlLock and LoginFailedSharedMemoryLock", curr_user_name);
/* Reset login monitor shmem user name */
memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
/* Set current user name */
strcpy(LoginMonitorShmem->curr_user_name, curr_user_name);
/* Set latch to pointer to MyLatch */
LoginMonitorShmem->latch = &MyProc->procLatch;
ResetLatch(LoginMonitorShmem->latch);
/* Send signal to PostMaster */
SendPostmasterSignal(PMSIGNAL_FAILED_LOGIN);
elog(DEBUG1, "User %s has sent failed login signal to postmaster", curr_user_name);
LWLockRelease(LoginFailedSharedMemoryLock);
elog(DEBUG1, "User %s has released LoginFailedSharedMemoryLock and wait latch", curr_user_name);
rc = WaitLatch(LoginMonitorShmem->latch,
WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
WAIT_EVENT_LOGINMONITOR_FINISH);
LWLockRelease(LoginFailedControlLock);
elog(DEBUG1, "User %s has release LoginFailedControlLock", curr_user_name);
}
static void
LoginMonitorShmemReset(void) {
Assert(LoginMonitorShmem);
LWLockAcquire(LoginFailedSharedMemoryLock, LW_EXCLUSIVE);
memset(LoginMonitorShmem->curr_user_name, 0, NAMEDATALEN);
LoginMonitorShmem->latch = NULL;
LoginMonitorShmem->login_failed_requested = false;
LWLockRelease(LoginFailedSharedMemoryLock);
return;
}