mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Due to simplistic quoting and confusion of database names with conninfo strings, roles with the CREATEDB or CREATEROLE option could escalate to superuser privileges when a superuser next ran certain maintenance commands. The new coding rule for PQconnectdbParams() calls, documented at conninfo_array_parse(), is to pass expand_dbname=true and wrap literal database names in a trivial connection string. Escape zero-length values in appendConnStrVal(). Back-patch to 9.1 (all supported versions). Nathan Bossart, Michael Paquier, and Noah Misch. Reviewed by Peter Eisentraut. Reported by Nathan Bossart. Security: CVE-2016-5424
		
			
				
	
	
		
			499 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *	util.c
 | 
						|
 *
 | 
						|
 *	utility functions
 | 
						|
 *
 | 
						|
 *	Copyright (c) 2010-2014, PostgreSQL Global Development Group
 | 
						|
 *	contrib/pg_upgrade/util.c
 | 
						|
 */
 | 
						|
 | 
						|
#include "postgres_fe.h"
 | 
						|
 | 
						|
#include "common/username.h"
 | 
						|
#include "pg_upgrade.h"
 | 
						|
 | 
						|
#include <signal.h>
 | 
						|
 | 
						|
 | 
						|
LogOpts		log_opts;
 | 
						|
 | 
						|
/*
 | 
						|
 * report_status()
 | 
						|
 *
 | 
						|
 *	Displays the result of an operation (ok, failed, error message,...)
 | 
						|
 */
 | 
						|
void
 | 
						|
report_status(eLogType type, const char *fmt,...)
 | 
						|
{
 | 
						|
	va_list		args;
 | 
						|
	char		message[MAX_STRING];
 | 
						|
 | 
						|
	va_start(args, fmt);
 | 
						|
	vsnprintf(message, sizeof(message), fmt, args);
 | 
						|
	va_end(args);
 | 
						|
 | 
						|
	pg_log(type, "%s\n", message);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* force blank output for progress display */
 | 
						|
void
 | 
						|
end_progress_output(void)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * In case nothing printed; pass a space so gcc doesn't complain about
 | 
						|
	 * empty format string.
 | 
						|
	 */
 | 
						|
	prep_status(" ");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * prep_status
 | 
						|
 *
 | 
						|
 *	Displays a message that describes an operation we are about to begin.
 | 
						|
 *	We pad the message out to MESSAGE_WIDTH characters so that all of the "ok" and
 | 
						|
 *	"failed" indicators line up nicely.
 | 
						|
 *
 | 
						|
 *	A typical sequence would look like this:
 | 
						|
 *		prep_status("about to flarb the next %d files", fileCount );
 | 
						|
 *
 | 
						|
 *		if(( message = flarbFiles(fileCount)) == NULL)
 | 
						|
 *		  report_status(PG_REPORT, "ok" );
 | 
						|
 *		else
 | 
						|
 *		  pg_log(PG_FATAL, "failed - %s\n", message );
 | 
						|
 */
 | 
						|
void
 | 
						|
prep_status(const char *fmt,...)
 | 
						|
{
 | 
						|
	va_list		args;
 | 
						|
	char		message[MAX_STRING];
 | 
						|
 | 
						|
	va_start(args, fmt);
 | 
						|
	vsnprintf(message, sizeof(message), fmt, args);
 | 
						|
	va_end(args);
 | 
						|
 | 
						|
	if (strlen(message) > 0 && message[strlen(message) - 1] == '\n')
 | 
						|
		pg_log(PG_REPORT, "%s", message);
 | 
						|
	else
 | 
						|
		/* trim strings that don't end in a newline */
 | 
						|
		pg_log(PG_REPORT, "%-*s", MESSAGE_WIDTH, message);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static
 | 
						|
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 0)))
 | 
						|
void
 | 
						|
pg_log_v(eLogType type, const char *fmt, va_list ap)
 | 
						|
{
 | 
						|
	char		message[MAX_STRING];
 | 
						|
 | 
						|
	vsnprintf(message, sizeof(message), fmt, ap);
 | 
						|
 | 
						|
	/* PG_VERBOSE and PG_STATUS are only output in verbose mode */
 | 
						|
	/* fopen() on log_opts.internal might have failed, so check it */
 | 
						|
	if (((type != PG_VERBOSE && type != PG_STATUS) || log_opts.verbose) &&
 | 
						|
		log_opts.internal != NULL)
 | 
						|
	{
 | 
						|
		if (type == PG_STATUS)
 | 
						|
			/* status messages need two leading spaces and a newline */
 | 
						|
			fprintf(log_opts.internal, "  %s\n", message);
 | 
						|
		else
 | 
						|
			fprintf(log_opts.internal, "%s", message);
 | 
						|
		fflush(log_opts.internal);
 | 
						|
	}
 | 
						|
 | 
						|
	switch (type)
 | 
						|
	{
 | 
						|
		case PG_VERBOSE:
 | 
						|
			if (log_opts.verbose)
 | 
						|
				printf("%s", _(message));
 | 
						|
			break;
 | 
						|
 | 
						|
		case PG_STATUS:
 | 
						|
			/* for output to a display, do leading truncation and append \r */
 | 
						|
			if (isatty(fileno(stdout)))
 | 
						|
				/* -2 because we use a 2-space indent */
 | 
						|
				printf("  %s%-*.*s\r",
 | 
						|
				/* prefix with "..." if we do leading truncation */
 | 
						|
					   strlen(message) <= MESSAGE_WIDTH - 2 ? "" : "...",
 | 
						|
					   MESSAGE_WIDTH - 2, MESSAGE_WIDTH - 2,
 | 
						|
				/* optional leading truncation */
 | 
						|
					   strlen(message) <= MESSAGE_WIDTH - 2 ? message :
 | 
						|
					   message + strlen(message) - MESSAGE_WIDTH + 3 + 2);
 | 
						|
			else
 | 
						|
				printf("  %s\n", _(message));
 | 
						|
			break;
 | 
						|
 | 
						|
		case PG_REPORT:
 | 
						|
		case PG_WARNING:
 | 
						|
			printf("%s", _(message));
 | 
						|
			break;
 | 
						|
 | 
						|
		case PG_FATAL:
 | 
						|
			printf("\n%s", _(message));
 | 
						|
			printf("Failure, exiting\n");
 | 
						|
			exit(1);
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	fflush(stdout);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
pg_log(eLogType type, const char *fmt,...)
 | 
						|
{
 | 
						|
	va_list		args;
 | 
						|
 | 
						|
	va_start(args, fmt);
 | 
						|
	pg_log_v(type, fmt, args);
 | 
						|
	va_end(args);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
pg_fatal(const char *fmt,...)
 | 
						|
{
 | 
						|
	va_list		args;
 | 
						|
 | 
						|
	va_start(args, fmt);
 | 
						|
	pg_log_v(PG_FATAL, fmt, args);
 | 
						|
	va_end(args);
 | 
						|
	printf("Failure, exiting\n");
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
check_ok(void)
 | 
						|
{
 | 
						|
	/* all seems well */
 | 
						|
	report_status(PG_REPORT, "ok");
 | 
						|
	fflush(stdout);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * quote_identifier()
 | 
						|
 *		Properly double-quote a SQL identifier.
 | 
						|
 *
 | 
						|
 * The result should be pg_free'd, but most callers don't bother because
 | 
						|
 * memory leakage is not a big deal in this program.
 | 
						|
 */
 | 
						|
char *
 | 
						|
quote_identifier(const char *s)
 | 
						|
{
 | 
						|
	char	   *result = pg_malloc(strlen(s) * 2 + 3);
 | 
						|
	char	   *r = result;
 | 
						|
 | 
						|
	*r++ = '"';
 | 
						|
	while (*s)
 | 
						|
	{
 | 
						|
		if (*s == '"')
 | 
						|
			*r++ = *s;
 | 
						|
		*r++ = *s;
 | 
						|
		s++;
 | 
						|
	}
 | 
						|
	*r++ = '"';
 | 
						|
	*r++ = '\0';
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Append the given string to the shell command being built in the buffer,
 | 
						|
 * with suitable shell-style quoting to create exactly one argument.
 | 
						|
 *
 | 
						|
 * Forbid LF or CR characters, which have scant practical use beyond designing
 | 
						|
 * security breaches.  The Windows command shell is unusable as a conduit for
 | 
						|
 * arguments containing LF or CR characters.  A future major release should
 | 
						|
 * reject those characters in CREATE ROLE and CREATE DATABASE, because use
 | 
						|
 * there eventually leads to errors here.
 | 
						|
 */
 | 
						|
void
 | 
						|
appendShellString(PQExpBuffer buf, const char *str)
 | 
						|
{
 | 
						|
	const char *p;
 | 
						|
 | 
						|
#ifndef WIN32
 | 
						|
	appendPQExpBufferChar(buf, '\'');
 | 
						|
	for (p = str; *p; p++)
 | 
						|
	{
 | 
						|
		if (*p == '\n' || *p == '\r')
 | 
						|
		{
 | 
						|
			fprintf(stderr,
 | 
						|
					_("shell command argument contains a newline or carriage return: \"%s\"\n"),
 | 
						|
					str);
 | 
						|
			exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
 | 
						|
		if (*p == '\'')
 | 
						|
			appendPQExpBufferStr(buf, "'\"'\"'");
 | 
						|
		else
 | 
						|
			appendPQExpBufferChar(buf, *p);
 | 
						|
	}
 | 
						|
	appendPQExpBufferChar(buf, '\'');
 | 
						|
#else							/* WIN32 */
 | 
						|
	int			backslash_run_length = 0;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * A Windows system() argument experiences two layers of interpretation.
 | 
						|
	 * First, cmd.exe interprets the string.  Its behavior is undocumented,
 | 
						|
	 * but a caret escapes any byte except LF or CR that would otherwise have
 | 
						|
	 * special meaning.  Handling of a caret before LF or CR differs between
 | 
						|
	 * "cmd.exe /c" and other modes, and it is unusable here.
 | 
						|
	 *
 | 
						|
	 * Second, the new process parses its command line to construct argv (see
 | 
						|
	 * https://msdn.microsoft.com/en-us/library/17w5ykft.aspx).  This treats
 | 
						|
	 * backslash-double quote sequences specially.
 | 
						|
	 */
 | 
						|
	appendPQExpBufferStr(buf, "^\"");
 | 
						|
	for (p = str; *p; p++)
 | 
						|
	{
 | 
						|
		if (*p == '\n' || *p == '\r')
 | 
						|
		{
 | 
						|
			fprintf(stderr,
 | 
						|
					_("shell command argument contains a newline or carriage return: \"%s\"\n"),
 | 
						|
					str);
 | 
						|
			exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
 | 
						|
		/* Change N backslashes before a double quote to 2N+1 backslashes. */
 | 
						|
		if (*p == '"')
 | 
						|
		{
 | 
						|
			while (backslash_run_length)
 | 
						|
			{
 | 
						|
				appendPQExpBufferStr(buf, "^\\");
 | 
						|
				backslash_run_length--;
 | 
						|
			}
 | 
						|
			appendPQExpBufferStr(buf, "^\\");
 | 
						|
		}
 | 
						|
		else if (*p == '\\')
 | 
						|
			backslash_run_length++;
 | 
						|
		else
 | 
						|
			backslash_run_length = 0;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Decline to caret-escape the most mundane characters, to ease
 | 
						|
		 * debugging and lest we approach the command length limit.
 | 
						|
		 */
 | 
						|
		if (!((*p >= 'a' && *p <= 'z') ||
 | 
						|
			  (*p >= 'A' && *p <= 'Z') ||
 | 
						|
			  (*p >= '0' && *p <= '9')))
 | 
						|
			appendPQExpBufferChar(buf, '^');
 | 
						|
		appendPQExpBufferChar(buf, *p);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Change N backslashes at end of argument to 2N backslashes, because they
 | 
						|
	 * precede the double quote that terminates the argument.
 | 
						|
	 */
 | 
						|
	while (backslash_run_length)
 | 
						|
	{
 | 
						|
		appendPQExpBufferStr(buf, "^\\");
 | 
						|
		backslash_run_length--;
 | 
						|
	}
 | 
						|
	appendPQExpBufferStr(buf, "^\"");
 | 
						|
#endif   /* WIN32 */
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Append the given string to the buffer, with suitable quoting for passing
 | 
						|
 * the string as a value, in a keyword/pair value in a libpq connection
 | 
						|
 * string
 | 
						|
 */
 | 
						|
void
 | 
						|
appendConnStrVal(PQExpBuffer buf, const char *str)
 | 
						|
{
 | 
						|
	const char *s;
 | 
						|
	bool		needquotes;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the string is one or more plain ASCII characters, no need to quote
 | 
						|
	 * it. This is quite conservative, but better safe than sorry.
 | 
						|
	 */
 | 
						|
	needquotes = true;
 | 
						|
	for (s = str; *s; s++)
 | 
						|
	{
 | 
						|
		if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
 | 
						|
			  (*s >= '0' && *s <= '9') || *s == '_' || *s == '.'))
 | 
						|
		{
 | 
						|
			needquotes = true;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		needquotes = false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (needquotes)
 | 
						|
	{
 | 
						|
		appendPQExpBufferChar(buf, '\'');
 | 
						|
		while (*str)
 | 
						|
		{
 | 
						|
			/* ' and \ must be escaped by to \' and \\ */
 | 
						|
			if (*str == '\'' || *str == '\\')
 | 
						|
				appendPQExpBufferChar(buf, '\\');
 | 
						|
 | 
						|
			appendPQExpBufferChar(buf, *str);
 | 
						|
			str++;
 | 
						|
		}
 | 
						|
		appendPQExpBufferChar(buf, '\'');
 | 
						|
	}
 | 
						|
	else
 | 
						|
		appendPQExpBufferStr(buf, str);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Append a psql meta-command that connects to the given database with the
 | 
						|
 * then-current connection's user, host and port.
 | 
						|
 */
 | 
						|
void
 | 
						|
appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname)
 | 
						|
{
 | 
						|
	const char *s;
 | 
						|
	bool		complex;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the name is plain ASCII characters, emit a trivial "\connect "foo"".
 | 
						|
	 * For other names, even many not technically requiring it, skip to the
 | 
						|
	 * general case.  No database has a zero-length name.
 | 
						|
	 */
 | 
						|
	complex = false;
 | 
						|
	for (s = dbname; *s; s++)
 | 
						|
	{
 | 
						|
		if (*s == '\n' || *s == '\r')
 | 
						|
		{
 | 
						|
			fprintf(stderr,
 | 
						|
					_("database name contains a newline or carriage return: \"%s\"\n"),
 | 
						|
					dbname);
 | 
						|
			exit(EXIT_FAILURE);
 | 
						|
		}
 | 
						|
 | 
						|
		if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') ||
 | 
						|
			  (*s >= '0' && *s <= '9') || *s == '_' || *s == '.'))
 | 
						|
		{
 | 
						|
			complex = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	appendPQExpBufferStr(buf, "\\connect ");
 | 
						|
	if (complex)
 | 
						|
	{
 | 
						|
		PQExpBufferData connstr;
 | 
						|
 | 
						|
		initPQExpBuffer(&connstr);
 | 
						|
		appendPQExpBuffer(&connstr, "dbname=");
 | 
						|
		appendConnStrVal(&connstr, dbname);
 | 
						|
 | 
						|
		appendPQExpBuffer(buf, "-reuse-previous=on ");
 | 
						|
 | 
						|
		/*
 | 
						|
		 * As long as the name does not contain a newline, SQL identifier
 | 
						|
		 * quoting satisfies the psql meta-command parser.  Prefer not to
 | 
						|
		 * involve psql-interpreted single quotes, which behaved differently
 | 
						|
		 * before PostgreSQL 9.2.
 | 
						|
		 */
 | 
						|
		appendPQExpBufferStr(buf, quote_identifier(connstr.data));
 | 
						|
 | 
						|
		termPQExpBuffer(&connstr);
 | 
						|
	}
 | 
						|
	else
 | 
						|
		appendPQExpBufferStr(buf, quote_identifier(dbname));
 | 
						|
	appendPQExpBufferChar(buf, '\n');
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * get_user_info()
 | 
						|
 */
 | 
						|
int
 | 
						|
get_user_info(char **user_name_p)
 | 
						|
{
 | 
						|
	int			user_id;
 | 
						|
	const char *user_name;
 | 
						|
	char	   *errstr;
 | 
						|
 | 
						|
#ifndef WIN32
 | 
						|
	user_id = geteuid();
 | 
						|
#else
 | 
						|
	user_id = 1;
 | 
						|
#endif
 | 
						|
 | 
						|
	user_name = get_user_name(&errstr);
 | 
						|
	if (!user_name)
 | 
						|
		pg_fatal("%s\n", errstr);
 | 
						|
 | 
						|
	/* make a copy */
 | 
						|
	*user_name_p = pg_strdup(user_name);
 | 
						|
 | 
						|
	return user_id;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * getErrorText()
 | 
						|
 *
 | 
						|
 *	Returns the text of the most recent error
 | 
						|
 */
 | 
						|
const char *
 | 
						|
getErrorText(void)
 | 
						|
{
 | 
						|
#ifdef WIN32
 | 
						|
	_dosmaperr(GetLastError());
 | 
						|
#endif
 | 
						|
	return pg_strdup(strerror(errno));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 *	str2uint()
 | 
						|
 *
 | 
						|
 *	convert string to oid
 | 
						|
 */
 | 
						|
unsigned int
 | 
						|
str2uint(const char *str)
 | 
						|
{
 | 
						|
	return strtoul(str, NULL, 10);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 *	pg_putenv()
 | 
						|
 *
 | 
						|
 *	This is like putenv(), but takes two arguments.
 | 
						|
 *	It also does unsetenv() if val is NULL.
 | 
						|
 */
 | 
						|
void
 | 
						|
pg_putenv(const char *var, const char *val)
 | 
						|
{
 | 
						|
	if (val)
 | 
						|
	{
 | 
						|
#ifndef WIN32
 | 
						|
		char	   *envstr;
 | 
						|
 | 
						|
		envstr = psprintf("%s=%s", var, val);
 | 
						|
		putenv(envstr);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Do not free envstr because it becomes part of the environment on
 | 
						|
		 * some operating systems.  See port/unsetenv.c::unsetenv.
 | 
						|
		 */
 | 
						|
#else
 | 
						|
		SetEnvironmentVariableA(var, val);
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
#ifndef WIN32
 | 
						|
		unsetenv(var);
 | 
						|
#else
 | 
						|
		SetEnvironmentVariableA(var, "");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
}
 |