319 lines
9.8 KiB
C++
319 lines
9.8 KiB
C++
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright (c) 2000-2012, PostgreSQL Global Development Group
|
|
*
|
|
* src/bin/psql/input.c
|
|
*/
|
|
#include "settings.h"
|
|
#include "postgres_fe.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
|
|
#include "input.h"
|
|
#include "tab-complete.h"
|
|
#include "common.h"
|
|
|
|
/* Runtime options for turning off readline and history */
|
|
/* (of course there is no runtime command for doing that :) */
|
|
#ifdef USE_READLINE
|
|
bool useReadline = false;
|
|
|
|
#ifdef ENABLE_UT
|
|
#define static
|
|
#endif
|
|
|
|
/*
|
|
* Preserve newlines in saved queries by mapping '\n' to NL_IN_HISTORY
|
|
*
|
|
* It is assumed NL_IN_HISTORY will never be entered by the user
|
|
* nor appear inside a multi-byte string. 0x00 is not properly
|
|
* handled by the readline routines so it can not be used
|
|
* for this purpose.
|
|
*/
|
|
#define NL_IN_HISTORY 0x01
|
|
#endif
|
|
|
|
/*
|
|
* gets_interactive()
|
|
*
|
|
* Gets a line of interactive input, using readline if desired.
|
|
* The result is a malloc'd string.
|
|
*
|
|
* Caller *must* have set up sigint_interrupt_jmp before calling.
|
|
*/
|
|
char* gets_interactive(const char* prompt)
|
|
{
|
|
#ifdef USE_READLINE
|
|
if (useReadline) {
|
|
char* result = NULL;
|
|
|
|
/* Enable SIGINT to longjmp to sigint_interrupt_jmp */
|
|
sigint_interrupt_enabled = true;
|
|
|
|
/* On some platforms, readline is declared as readline(char *) */
|
|
result = readline((char*)prompt);
|
|
|
|
/* Disable SIGINT again */
|
|
sigint_interrupt_enabled = false;
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
fputs(prompt, stdout);
|
|
(void)fflush(stdout);
|
|
return gets_fromFile(stdin);
|
|
}
|
|
|
|
/*
|
|
* Append the line to the history buffer, making sure there is a trailing '\n'
|
|
*/
|
|
void pg_append_history(const char* s, PQExpBuffer history_buf)
|
|
{
|
|
#ifdef USE_READLINE
|
|
if (useReadline && s != NULL) {
|
|
appendPQExpBufferStr(history_buf, s);
|
|
if (!s[0] || s[strlen(s) - 1] != '\n')
|
|
appendPQExpBufferChar(history_buf, '\n');
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Emit accumulated history entry to readline's history mechanism,
|
|
* then reset the buffer to empty.
|
|
*
|
|
* Note: we write nothing if history_buf is empty, so extra calls to this
|
|
* function don't hurt. There must have been at least one line added by
|
|
* pg_append_history before we'll do anything.
|
|
*/
|
|
void pg_send_history(PQExpBuffer history_buf)
|
|
{
|
|
#ifdef USE_READLINE
|
|
static char* prev_hist = NULL;
|
|
|
|
char* s = history_buf->data;
|
|
int i;
|
|
|
|
/* Trim any trailing \n's (OK to scribble on history_buf) */
|
|
for (i = strlen(s) - 1; i >= 0 && s[i] == '\n'; i--)
|
|
;
|
|
s[(uint32)(i + 1)] = '\0';
|
|
|
|
if (useReadline && s[0]) {
|
|
if (((((unsigned int)pset.histcontrol) & hctl_ignorespace) && s[0] == ' ') ||
|
|
((((unsigned int)pset.histcontrol) & hctl_ignoredups) && (prev_hist != NULL) &&
|
|
strcmp(s, prev_hist) == 0)) {
|
|
/* Ignore this line as far as history is concerned */
|
|
} else {
|
|
/* Save each previous line for ignoredups processing */
|
|
if (prev_hist != NULL)
|
|
free(prev_hist);
|
|
prev_hist = pg_strdup(s);
|
|
/* And send it to readline */
|
|
if (!SensitiveStrCheck(s) && canAddHist) {
|
|
(void)add_history(s);
|
|
/* Count lines added to history for use later */
|
|
} else if (!canAddHist) {
|
|
canAddHist = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
resetPQExpBuffer(history_buf);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* gets_fromFile
|
|
*
|
|
* Gets a line of noninteractive input from a file (which could be stdin).
|
|
* The result is a malloc'd string, or NULL on EOF or input error.
|
|
*
|
|
* Caller *must* have set up sigint_interrupt_jmp before calling.
|
|
*
|
|
* Note: we re-use a static PQExpBuffer for each call. This is to avoid
|
|
* leaking memory if interrupted by SIGINT.
|
|
*/
|
|
char* gets_fromFile(FILE* source)
|
|
{
|
|
static PQExpBuffer buffer = NULL;
|
|
errno_t rc = 0;
|
|
|
|
if (buffer == NULL) /* first time through? */
|
|
buffer = createPQExpBuffer();
|
|
else
|
|
(void)resetPQExpBuffer(buffer);
|
|
|
|
for (;;) {
|
|
char* result = NULL;
|
|
|
|
/* Enable SIGINT to longjmp to sigint_interrupt_jmp */
|
|
sigint_interrupt_enabled = true;
|
|
|
|
/* Database Security: Data importing/dumping support AES128. */
|
|
/* Get some data */
|
|
if (pset.decryptInfo.encryptInclude == false) {
|
|
result = fgets(pset.decryptInfo.currLine, sizeof(pset.decryptInfo.currLine), source);
|
|
} else {
|
|
result = getLineFromAesEncryptFile(source, &pset.decryptInfo);
|
|
}
|
|
|
|
/* Disable SIGINT again */
|
|
sigint_interrupt_enabled = false;
|
|
|
|
/* EOF or error? */
|
|
if (result == NULL) {
|
|
if (ferror(source)) {
|
|
psql_error("could not read from input file: %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Data importing/dumping support AES128 through VPP SSL.
|
|
* when decrypt text is longer than MAX_DECRYPT_BUFF_LEN
|
|
* We use decryptBuff here.
|
|
*/
|
|
if (pset.decryptInfo.encryptInclude == true) {
|
|
appendPQExpBufferStr(buffer, (char*)pset.decryptInfo.decryptBuff);
|
|
free(pset.decryptInfo.decryptBuff);
|
|
pset.decryptInfo.decryptBuff = NULL;
|
|
} else {
|
|
appendPQExpBufferStr(buffer, pset.decryptInfo.currLine);
|
|
}
|
|
|
|
/* Clear password related memory to avoid leaks when core. */
|
|
if (pset.cur_cmd_interactive) {
|
|
rc = memset_s(
|
|
pset.decryptInfo.currLine, sizeof(pset.decryptInfo.currLine), 0, sizeof(pset.decryptInfo.currLine));
|
|
securec_check_c(rc, "\0", "\0");
|
|
}
|
|
|
|
if (PQExpBufferBroken(buffer)) {
|
|
psql_error("out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* EOL? */
|
|
if (buffer->len > 0 && buffer->data[buffer->len - 1] == '\n') {
|
|
char* line = NULL;
|
|
buffer->data[buffer->len - 1] = '\0';
|
|
line = pg_strdup(buffer->data);
|
|
|
|
/* Clear password related memory to avoid leaks when core. */
|
|
if (pset.cur_cmd_interactive) {
|
|
rc = memset_s(buffer->data, strlen(buffer->data), 0, strlen(buffer->data));
|
|
securec_check_c(rc, "\0", "\0");
|
|
}
|
|
return line;
|
|
}
|
|
}
|
|
|
|
if (buffer->len > 0) { /* EOF after reading some bufferload(s) */
|
|
char* line = NULL;
|
|
line = pg_strdup(buffer->data);
|
|
|
|
/* Clear password related memory to avoid leaks when core. */
|
|
if (pset.cur_cmd_interactive) {
|
|
rc = memset_s(buffer->data, strlen(buffer->data), 0, strlen(buffer->data));
|
|
securec_check_c(rc, "\0", "\0");
|
|
}
|
|
return line;
|
|
}
|
|
|
|
/* EOF, so return null */
|
|
return NULL;
|
|
}
|
|
|
|
bool SensitiveStrCheck(const char* target)
|
|
{
|
|
int j = 0;
|
|
int target_len = (int)strlen(target);
|
|
char* target_copy = (char*)pg_malloc(target_len + 1);
|
|
errno_t rc;
|
|
rc = strncpy_s(target_copy, target_len + 1, target, target_len);
|
|
securec_check_c(rc, "\0", "\0");
|
|
target_copy[target_len] = '\0';
|
|
for (j = 0; j < target_len; j++) {
|
|
target_copy[j] = toupper(target_copy[j]);
|
|
}
|
|
|
|
if (strstr(target_copy, "PASSWORD") != NULL || strstr(target_copy, "IDENTIFIED") != NULL ||
|
|
strstr(target_copy, "GS_ENCRYPT_AES128") != NULL || strstr(target_copy, "GS_DECRYPT_AES128") != NULL ||
|
|
strstr(target_copy, "GS_ENCRYPT") != NULL || strstr(target_copy, "GS_DECRYPT") != NULL ||
|
|
strstr(target_copy, "AES_ENCRYPT") != NULL || strstr(target_copy, "AES_DECRYPT") != NULL ||
|
|
strstr(target_copy, "PG_CREATE_PHYSICAL_REPLICATION_SLOT_EXTERN") != NULL ||
|
|
strstr(target_copy, "SECRET_ACCESS_KEY") != NULL ||
|
|
strstr(target_copy, "SECRETKEY") != NULL || strstr(target_copy, "CREATE_CREDENTIAL") != NULL ||
|
|
strstr(target_copy, "ACCESS_KEY") != NULL || strstr(target_copy, "SECRET_ACCESS_KEY") != NULL) {
|
|
free(target_copy);
|
|
return TRUE;
|
|
} else {
|
|
free(target_copy);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void setHistSize(const char* targetName, const char* targetValue, bool setToDefault)
|
|
{
|
|
#ifndef ENABLE_LLT
|
|
char* end = NULL;
|
|
long int result;
|
|
#define MAXHISTSIZE 500
|
|
#define DEFHISTSIZE 32
|
|
if (targetName == NULL) {
|
|
return;
|
|
}
|
|
if (strcmp(targetName, "HISTSIZE") == 0) {
|
|
if (!setToDefault) {
|
|
if (targetValue == NULL || strlen(targetValue) == 0) {
|
|
fprintf(stderr, "warning:\"HISTSIZE\" is not changed,because its value can not be null\n");
|
|
return;
|
|
} else {
|
|
errno = 0;
|
|
result = strtol(targetValue, &end, 0);
|
|
if ((errno == ERANGE && (result == LONG_MAX || result == LONG_MIN)) || (errno != 0 && result == 0)) {
|
|
fprintf(stderr, "warning:\"HISTSIZE\" is not changed,because its value overflows\n");
|
|
return;
|
|
}
|
|
if (*end || result < 0) {
|
|
fprintf(stderr, "warning:\"HISTSIZE\" is not changed,because its value must be positive integer\n");
|
|
return;
|
|
}
|
|
}
|
|
if (result > MAXHISTSIZE) {
|
|
fprintf(stderr, "warning:\"HISTSIZE\" is set to 500,because its value can not be greater than 500\n");
|
|
result = MAXHISTSIZE;
|
|
}
|
|
} else {
|
|
result = DEFHISTSIZE;
|
|
}
|
|
stifle_history((int)result);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Put any startup stuff related to input in here. It's good to maintain
|
|
* abstraction this way.
|
|
*
|
|
* The only "flag" right now is 1 for use readline & history.
|
|
*/
|
|
void initializeInput(int flags)
|
|
{
|
|
#ifdef USE_READLINE
|
|
flags &= useReadline;
|
|
|
|
if (flags & 1) {
|
|
/* these two things must be done in this order: */
|
|
initialize_readline();
|
|
rl_variable_bind ("enable-meta-key", "off");
|
|
rl_initialize();
|
|
}
|
|
#endif
|
|
}
|