Commit 43a3543a authored by Bruce Momjian's avatar Bruce Momjian

Authentication improvements:

A new pg_hba.conf column, USER
Allow specifiction of lists of users separated by commas
Allow group names specified by +
Allow include files containing lists of users specified by @
Allow lists of databases, and database files
Allow samegroup in database column to match group name matching dbname
Removal of secondary password files
Remove pg_passwd utility
Lots of code cleanup in user.c and hba.c
New data/global/pg_pwd format
New data/global/pg_group file
parent af10378a
This diff is collapsed.
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.36 2002/03/19 02:18:12 momjian Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.37 2002/04/04 04:25:45 momjian Exp $
PostgreSQL documentation
Complete list of usable sgml source files in this directory.
-->
......@@ -125,7 +125,6 @@ Complete list of usable sgml source files in this directory.
<!entity pgCtl system "pg_ctl-ref.sgml">
<!entity pgDump system "pg_dump.sgml">
<!entity pgDumpall system "pg_dumpall.sgml">
<!entity pgPasswd system "pg_passwd.sgml">
<!entity pgRestore system "pg_restore.sgml">
<!entity pgTclSh system "pgtclsh.sgml">
<!entity pgTkSh system "pgtksh.sgml">
......
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/Attic/pg_passwd.sgml,v 1.10 2001/12/08 03:24:38 thomas Exp $
PostgreSQL documentation
-->
<refentry id="APP-PG-PASSWD">
<docinfo>
<date>2000-11-18</date>
</docinfo>
<refmeta>
<refentrytitle id="APP-PG-PASSWD-TITLE"><application>pg_passwd</application></refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo>Application</refmiscinfo>
</refmeta>
<refnamediv>
<refname>pg_passwd</refname>
<refpurpose>change a secondary <productname>PostgreSQL</> password file</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>pg_passwd</command>
<arg choice="plain"><replaceable>filename</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="app-pg-passwd-description">
<title>Description</title>
<para>
<application>pg_passwd</application> is a tool for manipulating flat
text password files. These files can control client authentication of
the <productname>PostgreSQL</productname> server. More information
about setting up this authentication mechanism can be found in the
<citetitle>Administrator's Guide</citetitle>.
</para>
<para>
The format of a text password file is one entry per line; the fields
of each entry are separated by colons. The first field is the user
name, the second field is the encrypted password. Other fields are
ignored (to allow password files to be shared between applications
that use similar formats). <application>pg_passwd</application>
enables users to interactively add entries to such a file, to alter
passwords of existing entries, and to encrypt such passwords.
</para>
<para>
Supply the name of the password file as argument to the
<application>pg_passwd</application> command. To be used by
PostgreSQL, the file needs to be located in the server's data
directory, and the base name of the file needs to be specified in the
<filename>pg_hba.conf</filename> access control file.
<screen>
<prompt>$</prompt> <userinput>pg_passwd /usr/local/pgsql/data/passwords</userinput>
<computeroutput>File "/usr/local/pgsql/data/passwords" does not exist. Create? (y/n):</computeroutput> <userinput>y</userinput>
<prompt>Username:</prompt> <userinput>guest</userinput>
<prompt>Password:</prompt>
<prompt>Re-enter password:</prompt>
</screen>
where the <literal>Password:</literal> and <literal>Re-enter
password:</literal> prompts require the same password input which
is not displayed on the terminal. Note that the password is limited
to eight useful characters by restrictions of the standard crypt(3)
library routine.
</para>
<para>
The original password file is renamed to
<filename>passwords.bk</filename>.
</para>
<para>
To make use of this password file, put a line like the following in
<filename>pg_hba.conf</filename>:
<programlisting>
host mydb 133.65.96.250 255.255.255.255 password passwords
</programlisting>
which would allow access to database mydb from host 133.65.96.250 using
the passwords listed in the <filename>passwords</filename> file (and
only to the users listed in that file).
</para>
<note>
<para>
It is also useful to have entries in a password file with empty
password fields. (This is different from an empty password.) Such
entries allow you to restrict users who can access the system. These
entries cannot be managed by <application>pg_passwd</application>,
but you can edit password files manually.
</para>
</note>
</refsect1>
<refsect1 id="app-pg-passwd-seealso">
<title>See also</title>
<para>
<citetitle>PostgreSQL Administrator's Guide</citetitle>
</para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:nil
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
sgml-parent-document:nil
sgml-default-dtd-file:"../reference.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:"/usr/lib/sgml/catalog"
sgml-local-ecat-files:nil
End:
-->
<!-- reference.sgml
$Header: /cvsroot/pgsql/doc/src/sgml/reference.sgml,v 1.24 2002/03/19 02:18:11 momjian Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/reference.sgml,v 1.25 2002/04/04 04:25:44 momjian Exp $
PostgreSQL Reference Manual
-->
......@@ -191,7 +191,6 @@ Disable this chapter until we have more functions documented.
&initlocation;
&ipcclean;
&pgCtl;
&pgPasswd;
&postgres;
&postmaster;
......
This diff is collapsed.
......@@ -4,7 +4,7 @@
# Makefile for libpq subsystem (backend half of libpq interface)
#
# IDENTIFICATION
# $Header: /cvsroot/pgsql/src/backend/libpq/Makefile,v 1.29 2002/03/04 01:46:02 tgl Exp $
# $Header: /cvsroot/pgsql/src/backend/libpq/Makefile,v 1.30 2002/04/04 04:25:46 momjian Exp $
#
#-------------------------------------------------------------------------
......@@ -14,9 +14,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = be-fsstubs.o \
auth.o crypt.o hba.o md5.o password.o \
pqcomm.o pqformat.o pqsignal.o
OBJS = be-fsstubs.o auth.o crypt.o hba.o md5.o pqcomm.o pqformat.o pqsignal.o
all: SUBSYS.o
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.79 2002/03/05 07:57:45 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.80 2002/04/04 04:25:47 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -34,7 +34,6 @@
#include "miscadmin.h"
static void sendAuthRequest(Port *port, AuthRequest areq);
static int checkPassword(Port *port, char *user, char *password);
static int old_be_recvauth(Port *port);
static int map_old_to_new(Port *port, UserAuth old, int status);
static void auth_failed(Port *port, int status);
......@@ -381,7 +380,7 @@ recv_and_check_passwordv0(Port *port)
saved = port->auth_method;
port->auth_method = uaPassword;
status = checkPassword(port, user, password);
status = md5_crypt_verify(port, user, password);
port->auth_method = saved;
......@@ -663,7 +662,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, struct pam_re
initStringInfo(&buf);
pq_getstr(&buf);
/* Do not echo failed password to logs, for security. */
elog(DEBUG5, "received PAM packet");
......@@ -810,26 +809,13 @@ recv_and_check_password_packet(Port *port)
/* Do not echo failed password to logs, for security. */
elog(DEBUG5, "received password packet");
result = checkPassword(port, port->user, buf.data);
result = md5_crypt_verify(port, port->user, buf.data);
pfree(buf.data);
return result;
}
/*
* Handle `password' and `crypt' records. If an auth argument was
* specified, use the respective file. Else use pg_shadow passwords.
*/
static int
checkPassword(Port *port, char *user, char *password)
{
if (port->auth_arg[0] != '\0')
return verify_password(port, user, password);
return md5_crypt_verify(port, user, password);
}
/*
* Server demux routine for incoming authentication information for protocol
* version 0.
......
......@@ -9,13 +9,12 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/libpq/crypt.c,v 1.44 2002/03/04 01:46:03 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/libpq/crypt.c,v 1.45 2002/04/04 04:25:47 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
......@@ -25,231 +24,10 @@
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "nodes/pg_list.h"
#include "utils/nabstime.h"
#define CRYPT_PWD_FILE "pg_pwd"
static char **pwd_cache = NULL;
static int pwd_cache_count = 0;
/*
* crypt_getpwdfilename --- get full pathname of password file
*
* Note that result string is palloc'd, and should be freed by the caller.
*/
char *
crypt_getpwdfilename(void)
{
int bufsize;
char *pfnam;
bufsize = strlen(DataDir) + 8 + strlen(CRYPT_PWD_FILE) + 1;
pfnam = (char *) palloc(bufsize);
snprintf(pfnam, bufsize, "%s/global/%s", DataDir, CRYPT_PWD_FILE);
return pfnam;
}
/*
* Open the password file if possible (return NULL if not)
*/
static FILE *
crypt_openpwdfile(void)
{
char *filename;
FILE *pwdfile;
filename = crypt_getpwdfilename();
pwdfile = AllocateFile(filename, "r");
if (pwdfile == NULL && errno != ENOENT)
elog(LOG, "could not open %s: %m", filename);
pfree(filename);
return pwdfile;
}
/*
* Compare two password-file lines on the basis of their usernames.
*
* Can also be used to compare just a username against a password-file
* line (for bsearch).
*/
static int
compar_user(const void *user_a, const void *user_b)
{
char *login_a;
char *login_b;
int len_a,
len_b,
result;
login_a = *((char **) user_a);
login_b = *((char **) user_b);
/*
* We only really want to compare the user logins which are first and
* are terminated by CRYPT_PWD_FILE_SEPSTR. (NB: this code
* effectively assumes that CRYPT_PWD_FILE_SEPSTR is just one char.)
*/
len_a = strcspn(login_a, CRYPT_PWD_FILE_SEPSTR);
len_b = strcspn(login_b, CRYPT_PWD_FILE_SEPSTR);
result = strncmp(login_a, login_b, Min(len_a, len_b));
if (result == 0) /* one could be a prefix of the other */
result = (len_a - len_b);
return result;
}
/*
* Load or reload the password-file cache
*/
void
load_password_cache(void)
{
FILE *pwd_file;
char buffer[1024];
/*
* If for some reason we fail to open the password file, preserve the
* old cache contents; this seems better than dropping the cache if,
* say, we are temporarily out of filetable slots.
*/
if (!(pwd_file = crypt_openpwdfile()))
return;
/* free any old data */
if (pwd_cache)
{
while (--pwd_cache_count >= 0)
pfree(pwd_cache[pwd_cache_count]);
pfree(pwd_cache);
pwd_cache = NULL;
pwd_cache_count = 0;
}
/*
* Read the file and store its lines in current memory context, which
* we expect will be PostmasterContext. That context will live as
* long as we need the cache to live, ie, until just after each
* postmaster child has completed client authentication.
*/
while (fgets(buffer, sizeof(buffer), pwd_file) != NULL)
{
int blen;
/*
* We must remove the return char at the end of the string, as
* this will affect the correct parsing of the password entry.
*/
if (buffer[(blen = strlen(buffer) - 1)] == '\n')
buffer[blen] = '\0';
if (pwd_cache == NULL)
pwd_cache = (char **)
palloc(sizeof(char *) * (pwd_cache_count + 1));
else
pwd_cache = (char **)
repalloc((void *) pwd_cache,
sizeof(char *) * (pwd_cache_count + 1));
pwd_cache[pwd_cache_count++] = pstrdup(buffer);
}
FreeFile(pwd_file);
/*
* Now sort the entries in the cache for faster searching later.
*/
qsort((void *) pwd_cache, pwd_cache_count, sizeof(char *), compar_user);
}
/*
* Parse a line of the password file to extract password and valid-until date.
*/
static bool
crypt_parsepwdentry(char *buffer, char **pwd, char **valdate)
{
char *parse = buffer;
int count,
i;
*pwd = NULL;
*valdate = NULL;
/*
* skip to the password field
*/
for (i = 0; i < 6; i++)
{
parse += strcspn(parse, CRYPT_PWD_FILE_SEPSTR);
if (*parse == '\0')
return false;
parse++;
}
/*
* store a copy of user password to return
*/
count = strcspn(parse, CRYPT_PWD_FILE_SEPSTR);
*pwd = (char *) palloc(count + 1);
memcpy(*pwd, parse, count);
(*pwd)[count] = '\0';
parse += count;
if (*parse == '\0')
{
pfree(*pwd);
*pwd = NULL;
return false;
}
parse++;
/*
* store a copy of the date login becomes invalid
*/
count = strcspn(parse, CRYPT_PWD_FILE_SEPSTR);
*valdate = (char *) palloc(count + 1);
memcpy(*valdate, parse, count);
(*valdate)[count] = '\0';
return true;
}
/*
* Lookup a username in the password-file cache,
* return his password and valid-until date.
*/
static bool
crypt_getloginfo(const char *user, char **passwd, char **valuntil)
{
*passwd = NULL;
*valuntil = NULL;
if (pwd_cache)
{
char **pwd_entry;
pwd_entry = (char **) bsearch((void *) &user,
(void *) pwd_cache,
pwd_cache_count,
sizeof(char *),
compar_user);
if (pwd_entry)
{
if (crypt_parsepwdentry(*pwd_entry, passwd, valuntil))
return true;
}
}
return false;
}
/*-------------------------------------------------------------------------*/
int
md5_crypt_verify(const Port *port, const char *user, const char *pgpass)
{
......@@ -257,10 +35,14 @@ md5_crypt_verify(const Port *port, const char *user, const char *pgpass)
*valuntil,
*crypt_pwd;
int retval = STATUS_ERROR;
List **line;
if (!crypt_getloginfo(user, &passwd, &valuntil))
if ((line = get_user_line(user)) == NULL)
return STATUS_ERROR;
passwd = lfirst(lnext(lnext(*line)));
valuntil = lfirst(lnext(lnext(lnext(*line))));
if (passwd == NULL || *passwd == '\0')
{
if (passwd)
......
This diff is collapsed.
/*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: password.c,v 1.41 2002/03/04 01:46:03 tgl Exp $
*
*/
#include <errno.h>
#include <unistd.h>
#include "postgres.h"
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#include "libpq/libpq.h"
#include "libpq/password.h"
#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/fd.h"
int
verify_password(const Port *port, const char *user, const char *password)
{
char *pw_file_fullname;
FILE *pw_file;
pw_file_fullname = (char *) palloc(strlen(DataDir) + strlen(port->auth_arg) + 2);
strcpy(pw_file_fullname, DataDir);
strcat(pw_file_fullname, "/");
strcat(pw_file_fullname, port->auth_arg);
pw_file = AllocateFile(pw_file_fullname, PG_BINARY_R);
if (!pw_file)
{
elog(LOG, "verify_password: Unable to open password file \"%s\": %m",
pw_file_fullname);
pfree(pw_file_fullname);
return STATUS_ERROR;
}
pfree(pw_file_fullname);
while (!feof(pw_file))
{
char pw_file_line[255],
*p,
*test_user,
*test_pw;
if (fgets(pw_file_line, sizeof(pw_file_line), pw_file) == NULL)
pw_file_line[0] = '\0';
/* kill the newline */
if (strlen(pw_file_line) > 0 &&
pw_file_line[strlen(pw_file_line) - 1] == '\n')
pw_file_line[strlen(pw_file_line) - 1] = '\0';
p = pw_file_line;
test_user = strtok(p, ":");
if (!test_user || test_user[0] == '\0')
continue;
test_pw = strtok(NULL, ":");
if (strcmp(user, test_user) == 0)
{
/* we're outta here one way or the other, so close file */
FreeFile(pw_file);
/*
* If the password is empty or "+" then we use the regular
* pg_shadow passwords. If we use crypt then we have to use
* pg_shadow passwords no matter what. This is because the
* current code needs non-encrypted passwords to encrypt with
* a random salt.
*/
if (port->auth_method == uaMD5 ||
port->auth_method == uaCrypt ||
test_pw == NULL ||
test_pw[0] == '\0' ||
strcmp(test_pw, "+") == 0)
return md5_crypt_verify(port, user, password);
/* external password file is crypt-only */
if (strcmp(crypt(password, test_pw), test_pw) == 0)
{
/* it matched. */
return STATUS_OK;
}
elog(LOG, "verify_password: password mismatch for '%s'",
user);
return STATUS_ERROR;
}
}
FreeFile(pw_file);
elog(LOG, "verify_password: user '%s' not found in password file",
user);
return STATUS_ERROR;
}
This diff is collapsed.
......@@ -37,7 +37,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.271 2002/03/15 19:20:35 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/postmaster/postmaster.c,v 1.272 2002/04/04 04:25:48 momjian Exp $
*
* NOTES
*
......@@ -748,8 +748,10 @@ PostmasterMain(int argc, char *argv[])
/*
* Load cached files for client authentication.
*/
load_hba_and_ident();
load_password_cache();
load_hba();
load_ident();
load_user();
load_group();
/*
* We're ready to rock and roll...
......@@ -1389,7 +1391,8 @@ SIGHUP_handler(SIGNAL_ARGS)
elog(LOG, "Received SIGHUP, reloading configuration files");
SignalChildren(SIGHUP);
ProcessConfigFile(PGC_SIGHUP);
load_hba_and_ident();
load_hba();
load_ident();
}
PG_SETMASK(&UnBlockSig);
......@@ -2288,9 +2291,10 @@ sigusr1_handler(SIGNAL_ARGS)
if (CheckPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE))
{
/*
* Password file has changed.
* Password or group file has changed.
*/
load_password_cache();
load_user();
load_group();
}
if (CheckPostmasterSignal(PMSIGNAL_WAKEN_CHILDREN))
......
......@@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/quote.c,v 1.6 2001/10/28 06:25:53 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/quote.c,v 1.7 2002/04/04 04:25:49 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -124,8 +124,6 @@ do_quote_ident(text *iptr)
{
if (*cp1 == '"')
*cp2++ = '"';
if (*cp1 == '\\')
*cp2++ = '\\';
*cp2++ = *cp1++;
}
*cp2++ = '"';
......@@ -234,8 +232,6 @@ do_quote_ident(text *iptr)
if (*cp1 == '"')
*cp2++ = '"';
if (*cp1 == '\\')
*cp2++ = '\\';
*cp2++ = *cp1++;
len--;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.85 2002/03/04 04:45:27 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.86 2002/04/04 04:25:49 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -236,85 +236,17 @@ pg_convert2(PG_FUNCTION_ARGS)
#ifdef CYR_RECODE
#define MAX_TOKEN 80
/*
* Some standard C libraries, including GNU, have an isblank() function.
* Others, including Solaris, do not. So we have our own.
*/
static bool
isblank(const char c)
{
return c == ' ' || c == '\t';
}
/*
* Grab one token out of fp. Tokens are strings of non-blank
* characters bounded by blank characters, beginning of line, and end
* of line. Blank means space or tab. Return the token as *buf.
* Leave file positioned to character immediately after the token or
* EOF, whichever comes first. If no more tokens on line, return null
* string as *buf and position file to beginning of next line or EOF,
* whichever comes first.
*/
static void
next_token(FILE *fp, char *buf, const int bufsz)
{
int c;
char *eb = buf + (bufsz - 1);
/* Move over initial token-delimiting blanks */
while ((c = getc(fp)) != EOF && isblank(c))
;
if (c != EOF && c != '\n')
{
/*
* build a token in buf of next characters up to EOF, eol, or
* blank. If the token gets too long, we still parse it
* correctly, but the excess characters are not stored into *buf.
*/
while (c != EOF && c != '\n' && !isblank(c))
{
if (buf < eb)
*buf++ = c;
c = getc(fp);
}
/*
* Put back the char right after the token (critical in case it is
* eol, since we need to detect end-of-line at next call).
*/
if (c != EOF)
ungetc(c, fp);
}
*buf = '\0';
}
static void
read_through_eol(FILE *file)
{
int c;
while ((c = getc(file)) != EOF && c != '\n')
;
}
void
SetCharSet(void)
{
FILE *file;
char *p;
char *filename;
char *map_file;
char buf[MAX_TOKEN];
int i,
c;
unsigned char FromChar,
ToChar;
char ChTable[80];
char ChTable[MAX_TOKEN];
for (i = 0; i < 128; i++)
{
......@@ -325,39 +257,40 @@ SetCharSet(void)
if (IsUnderPostmaster)
{
GetCharSetByHost(ChTable, MyProcPort->raddr.in.sin_addr.s_addr, DataDir);
p = ChTable;
filename = ChTable;
}
else
p = getenv("PG_RECODETABLE");
filename = getenv("PG_RECODETABLE");
if (p && *p != '\0')
if (filename && *filename != '\0')
{
map_file = palloc(strlen(DataDir) + strlen(p) + 2);
sprintf(map_file, "%s/%s", DataDir, p);
file = AllocateFile(map_file, PG_BINARY_R);
map_file = palloc(strlen(DataDir) + strlen(filename) + 2);
sprintf(map_file, "%s/%s", DataDir, filename);
file = AllocateFile(map_file, "r");
pfree(map_file);
if (file == NULL)
return;
while ((c = getc(file)) != EOF)
while (!feof(file))
{
if (c == '#')
read_through_eol(file);
else
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
/* Read the FromChar */
ungetc(c, file);
FromChar = strtoul(buf, 0, 0);
/* Read the ToChar */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
FromChar = strtoul(buf, 0, 0);
/* Read the ToChar */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
ToChar = strtoul(buf, 0, 0);
RecodeForwTable[FromChar - 128] = ToChar;
RecodeBackTable[ToChar - 128] = FromChar;
/* read to EOL */
while (!feof(file) && buf[0])
{
ToChar = strtoul(buf, 0, 0);
RecodeForwTable[FromChar - 128] = ToChar;
RecodeBackTable[ToChar - 128] = FromChar;
read_through_eol(file);
next_token(file, buf, sizeof(buf));
elog(LOG, "SetCharSet: unknown tag %s in file %s"
buf, filename);
}
}
}
......@@ -366,6 +299,7 @@ SetCharSet(void)
}
}
char *
convertstr(unsigned char *buff, int len, int dest)
{
......@@ -384,7 +318,206 @@ convertstr(unsigned char *buff, int len, int dest)
}
return ch;
}
#endif
#define CHARSET_FILE "charset.conf"
#define MAX_CHARSETS 10
#define KEY_HOST 1
#define KEY_BASE 2
#define KEY_TABLE 3
struct CharsetItem
{
char Orig[MAX_TOKEN];
char Dest[MAX_TOKEN];
char Table[MAX_TOKEN];
};
static bool
CharSetInRange(char *buf, int host)
{
int valid,
i,
FromAddr,
ToAddr,
tmp;
struct in_addr file_ip_addr;
char *p;
unsigned int one = 0x80000000,
NetMask = 0;
unsigned char mask;
p = strchr(buf, '/');
if (p)
{
*p++ = '\0';
valid = inet_aton(buf, &file_ip_addr);
if (valid)
{
mask = strtoul(p, 0, 0);
FromAddr = ntohl(file_ip_addr.s_addr);
ToAddr = ntohl(file_ip_addr.s_addr);
for (i = 0; i < mask; i++)
{
NetMask |= one;
one >>= 1;
}
FromAddr &= NetMask;
ToAddr = ToAddr | ~NetMask;
tmp = ntohl(host);
return ((unsigned) tmp >= (unsigned) FromAddr &&
(unsigned) tmp <= (unsigned) ToAddr);
}
}
else
{
p = strchr(buf, '-');
if (p)
{
*p++ = '\0';
valid = inet_aton(buf, &file_ip_addr);
if (valid)
{
FromAddr = ntohl(file_ip_addr.s_addr);
valid = inet_aton(p, &file_ip_addr);
if (valid)
{
ToAddr = ntohl(file_ip_addr.s_addr);
tmp = ntohl(host);
return ((unsigned) tmp >= (unsigned) FromAddr &&
(unsigned) tmp <= (unsigned) ToAddr);
}
}
}
else
{
valid = inet_aton(buf, &file_ip_addr);
if (valid)
{
FromAddr = file_ip_addr.s_addr;
return (unsigned) FromAddr == (unsigned) host;
}
}
}
return false;
}
static void
GetCharSetByHost(char *TableName, int host, const char *DataDir)
{
FILE *file;
char buf[MAX_TOKEN],
BaseCharset[MAX_TOKEN],
OrigCharset[MAX_TOKEN],
DestCharset[MAX_TOKEN],
HostCharset[MAX_TOKEN],
*map_file;
int key,
ChIndex = 0,
c,
i,
bufsize;
struct CharsetItem *ChArray[MAX_CHARSETS];
*TableName = '\0';
bufsize = (strlen(DataDir) + strlen(CHARSET_FILE) + 2) * sizeof(char);
map_file = (char *) palloc(bufsize);
snprintf(map_file, bufsize, "%s/%s", DataDir, CHARSET_FILE);
file = AllocateFile(map_file, "r");
pfree(map_file);
if (file == NULL)
{
/* XXX should we log a complaint? */
return;
}
while (!feof(file))
{
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
key = 0;
if (strcasecmp(buf, "HostCharset") == 0)
key = KEY_HOST;
else if (strcasecmp(buf, "BaseCharset") == 0)
key = KEY_BASE;
else if (strcasecmp(buf, "RecodeTable") == 0)
key = KEY_TABLE;
else
elog(LOG, "GetCharSetByHost: unknown tag %s in file %s"
buf, CHARSET_FILE);
switch (key)
{
case KEY_HOST:
/* Read the host */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
if (CharSetInRange(buf, host))
{
/* Read the charset */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
strcpy(HostCharset, buf);
}
}
break;
case KEY_BASE:
/* Read the base charset */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
strcpy(BaseCharset, buf);
break;
case KEY_TABLE:
/* Read the original charset */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
strcpy(OrigCharset, buf);
/* Read the destination charset */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
strcpy(DestCharset, buf);
/* Read the table filename */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
ChArray[ChIndex] =
(struct CharsetItem *) palloc(sizeof(struct CharsetItem));
strcpy(ChArray[ChIndex]->Orig, OrigCharset);
strcpy(ChArray[ChIndex]->Dest, DestCharset);
strcpy(ChArray[ChIndex]->Table, buf);
ChIndex++;
}
}
}
break;
}
/* read to EOL */
while (!feof(file) && buf[0])
{
next_token(file, buf, sizeof(buf));
elog(LOG, "GetCharSetByHost: unknown tag %s in file %s"
buf, CHARSET_FILE);
}
}
}
FreeFile(file);
for (i = 0; i < ChIndex; i++)
{
if (strcasecmp(BaseCharset, ChArray[i]->Orig) == 0 &&
strcasecmp(HostCharset, ChArray[i]->Dest) == 0)
strncpy(TableName, ChArray[i]->Table, 79);
pfree(ChArray[i]);
}
}
#endif /* CYR_RECODE */
......
......@@ -5,7 +5,7 @@
# Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# $Header: /cvsroot/pgsql/src/bin/Makefile,v 1.34 2001/02/18 18:33:59 momjian Exp $
# $Header: /cvsroot/pgsql/src/bin/Makefile,v 1.35 2002/04/04 04:25:50 momjian Exp $
#
#-------------------------------------------------------------------------
......@@ -14,7 +14,7 @@ top_builddir = ../..
include $(top_builddir)/src/Makefile.global
DIRS := initdb initlocation ipcclean pg_ctl pg_dump pg_id \
pg_passwd psql scripts pg_config
psql scripts pg_config
ifdef MULTIBYTE
DIRS += pg_encoding
......
......@@ -27,7 +27,7 @@
# Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.146 2002/04/03 05:39:32 petere Exp $
# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.147 2002/04/04 04:25:50 momjian Exp $
#
#-------------------------------------------------------------------------
......@@ -603,9 +603,11 @@ $ECHO_N "initializing pg_shadow... "$ECHO_C
"$PGPATH"/postgres $PGSQL_OPT template1 >/dev/null <<EOF
-- Create a trigger so that direct updates to pg_shadow will be written
-- to the flat password file pg_pwd
-- to the flat password/group files pg_pwd and pg_group
CREATE TRIGGER pg_sync_pg_pwd AFTER INSERT OR UPDATE OR DELETE ON pg_shadow \
FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd();
FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();
CREATE TRIGGER pg_sync_pg_group AFTER INSERT OR UPDATE OR DELETE ON pg_group \
FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();
-- needs to be done before alter user, because alter user checks that
-- pg_shadow is secure ...
REVOKE ALL on pg_shadow FROM public;
......@@ -643,6 +645,11 @@ EOF
echo "The password file wasn't generated. Please report this problem." 1>&2
exit_nicely
fi
if [ ! -f "$PGDATA"/global/pg_group ]; then
echo
echo "The group file wasn't generated. Please report this problem." 1>&2
exit_nicely
fi
echo "ok"
fi
......
# $Header: /cvsroot/pgsql/src/bin/pg_passwd/Attic/Makefile,v 1.14 2001/05/12 19:49:47 petere Exp $
subdir = src/bin/pg_passwd
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = pg_passwd.o
ifdef STRDUP
OBJS += $(top_builddir)/src/utils/strdup.o
endif
all: pg_passwd
pg_passwd: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
$(top_builddir)/src/utils/strdup.o:
$(MAKE) -C $(top_builddir)/src/utils strdup.o
install: all installdirs
$(INSTALL_PROGRAM) pg_passwd$(X) $(DESTDIR)$(bindir)/pg_passwd$(X)
installdirs:
$(mkinstalldirs) $(DESTDIR)$(bindir)
uninstall:
rm -f $(DESTDIR)$(bindir)/pg_passwd$(X)
depend dep:
$(CC) -MM $(CFLAGS) *.c >depend
clean distclean maintainer-clean:
rm -f pg_passwd$(X) pg_passwd.o
ifeq (depend,$(wildcard depend))
include depend
endif
/*
* @(#) pg_passwd.c 1.8 09:13:16 97/07/02 Y. Ichikawa
*/
#include "postgres_fe.h"
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#define issaltchar(c) (isalnum((unsigned char) (c)) || (c) == '.' || (c) == '/')
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#else
extern char *crypt(const char *, const char *);
#endif
/*
* We assume that the output of crypt(3) is always 13 characters,
* and that at most 8 characters can usefully be sent to it.
*
* Postgres usernames are assumed to be less than NAMEDATALEN chars long.
*/
#define CLEAR_PASSWD_LEN 8 /* not including null */
#define CRYPTED_PASSWD_LEN 13 /* not including null */
const char *progname;
static void usage(void);
static void read_pwd_file(char *filename);
static void write_pwd_file(char *filename, char *bkname);
static void encrypt_pwd(char key[CLEAR_PASSWD_LEN + 1],
char salt[3],
char passwd[CRYPTED_PASSWD_LEN + 1]);
static void prompt_for_username(char *username);
static void prompt_for_password(char *prompt, char *password);
static void
usage(void)
{
printf("%s manipulates flat text password files for PostgreSQL.\n\n", progname);
printf("Usage:\n %s PASSWORD-FILE\n\n", progname);
printf("Report bugs to <pgsql-bugs@postgresql.org>.\n");
}
typedef struct
{
char *uname;
char *pwd;
char *rest;
} pg_pwd;
#define MAXPWDS 1024
pg_pwd pwds[MAXPWDS];
int npwds = 0;
static void
read_pwd_file(char *filename)
{
FILE *fp;
static char line[512];
static char ans[128];
int i;
try_again:
fp = fopen(filename, PG_BINARY_R);
if (fp == NULL)
{
if (errno == ENOENT)
{
printf("File \"%s\" does not exist. Create? (y/n): ", filename);
fflush(stdout);
if (fgets(ans, sizeof(ans), stdin) == NULL)
exit(1);
switch (ans[0])
{
case 'y':
case 'Y':
fp = fopen(filename, PG_BINARY_W);
if (fp == NULL)
{
perror(filename);
exit(1);
}
fclose(fp);
goto try_again;
default:
/* cannot continue */
exit(1);
}
}
else
{
perror(filename);
exit(1);
}
}
/* read all the entries */
for (npwds = 0;
npwds < MAXPWDS && fgets(line, sizeof(line), fp) != NULL;
++npwds)
{
int l;
char *p,
*q;
l = strlen(line);
if (line[l - 1] == '\n')
line[l - 1] = '\0';
else
{
fprintf(stderr, "%s:%d: line too long\n",
filename, npwds + 1);
exit(1);
}
/* get user name */
p = line;
if ((q = strchr(p, ':')) != NULL)
*q = '\0';
if (strlen(p) == 0)
{
fprintf(stderr, "%s:%d: null user name\n",
filename, npwds + 1);
exit(1);
}
pwds[npwds].uname = strdup(p);
/* check for duplicate user name */
for (i = 0; i < npwds; ++i)
{
if (strcmp(pwds[i].uname, pwds[npwds].uname) == 0)
{
fprintf(stderr, "Duplicate username %s in entry %d\n",
pwds[npwds].uname, npwds + 1);
exit(1);
}
}
/* get password field */
if (q)
{
p = q + 1;
q = strchr(p, ':');
if (q != NULL)
*(q++) = '\0';
if (strlen(p) != CRYPTED_PASSWD_LEN && strcmp(p, "+") != 0)
{
fprintf(stderr, "%s:%d: warning: invalid password length\n",
filename, npwds + 1);
}
pwds[npwds].pwd = strdup(p);
}
else
pwds[npwds].pwd = NULL;
/* rest of the line is treated as is */
if (q == NULL)
pwds[npwds].rest = NULL;
else
pwds[npwds].rest = strdup(q);
}
fclose(fp);
}
static void
write_pwd_file(char *filename, char *bkname)
{
FILE *fp;
int i;
/* make the backup file */
link_again:
if (link(filename, bkname))
{
if (errno == EEXIST)
{
unlink(bkname);
goto link_again;
}
perror(bkname);
exit(1);
}
if (unlink(filename))
{
perror(filename);
exit(1);
}
/* open file */
if ((fp = fopen(filename, PG_BINARY_W)) == NULL)
{
perror(filename);
exit(1);
}
/* write file */
for (i = 0; i < npwds; ++i)
{
fprintf(fp, "%s", pwds[i].uname);
if (pwds[i].pwd)
fprintf(fp, ":%s", pwds[i].pwd);
if (pwds[i].rest)
fprintf(fp, ":%s", pwds[i].rest);
fprintf(fp, "\n");
}
fclose(fp);
}
static void
encrypt_pwd(char key[CLEAR_PASSWD_LEN + 1],
char salt[3],
char passwd[CRYPTED_PASSWD_LEN + 1])
{
int n;
/* select a salt, if not already given */
if (salt[0] == '\0')
{
srand(time(NULL));
do
{
n = rand() % 256;
} while (!issaltchar(n));
salt[0] = n;
do
{
n = rand() % 256;
} while (!issaltchar(n));
salt[1] = n;
salt[2] = '\0';
}
/* get encrypted password */
strcpy(passwd, crypt(key, salt));
#ifdef PG_PASSWD_DEBUG
/* show it */
fprintf(stderr, "key = %s, salt = %s, password = %s\n",
key, salt, passwd);
#endif
}
static void
prompt_for_username(char *username)
{
int length;
printf("Username: ");
fflush(stdout);
if (fgets(username, NAMEDATALEN, stdin) == NULL)
username[0] = '\0';
length = strlen(username);
if (length > 0 && username[length - 1] != '\n')
{
/* eat rest of the line */
char buf[128];
int buflen;
do
{
if (fgets(buf, sizeof(buf), stdin) == NULL)
break;
buflen = strlen(buf);
} while (buflen > 0 && buf[buflen - 1] != '\n');
}
if (length > 0 && username[length - 1] == '\n')
username[length - 1] = '\0';
}
static void
prompt_for_password(char *prompt, char *password)
{
int length;
#ifdef HAVE_TERMIOS_H
struct termios t_orig,
t;
#endif
#ifdef HAVE_TERMIOS_H
tcgetattr(0, &t);
t_orig = t;
t.c_lflag &= ~ECHO;
tcsetattr(0, TCSADRAIN, &t);
#endif
printf(prompt);
fflush(stdout);
if (fgets(password, CLEAR_PASSWD_LEN + 1, stdin) == NULL)
password[0] = '\0';
#ifdef HAVE_TERMIOS_H
tcsetattr(0, TCSADRAIN, &t_orig);
#endif
length = strlen(password);
if (length > 0 && password[length - 1] != '\n')
{
/* eat rest of the line */
char buf[128];
int buflen;
do
{
if (fgets(buf, sizeof(buf), stdin) == NULL)
break;
buflen = strlen(buf);
} while (buflen > 0 && buf[buflen - 1] != '\n');
}
if (length > 0 && password[length - 1] == '\n')
password[length - 1] = '\0';
printf("\n");
}
int
main(int argc, char *argv[])
{
char *filename;
char bkname[MAXPGPATH];
char username[NAMEDATALEN];
char salt[3];
char key[CLEAR_PASSWD_LEN + 1],
key2[CLEAR_PASSWD_LEN + 1];
char e_passwd[CRYPTED_PASSWD_LEN + 1];
int i;
progname = argv[0];
if (argc != 2)
{
fprintf(stderr, "%s: too %s arguments\nTry '%s --help' for more information.\n",
progname, argc > 2 ? "many" : "few", progname);
exit(1);
}
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
usage();
exit(0);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
puts("pg_passwd (PostgreSQL) " PG_VERSION);
exit(0);
}
if (argv[1][0] == '-')
{
fprintf(stderr, "%s: invalid option: %s\nTry '%s --help' for more information.\n",
progname, argv[1], progname);
exit(1);
}
filename = argv[1];
/* open file */
read_pwd_file(filename);
/* ask for the user name and the password */
prompt_for_username(username);
prompt_for_password("New password: ", key);
prompt_for_password("Re-enter new password: ", key2);
if (strcmp(key, key2) != 0)
{
fprintf(stderr, "Password mismatch\n");
exit(1);
}
salt[0] = '\0';
encrypt_pwd(key, salt, e_passwd);
/* check password entry */
for (i = 0; i < npwds; ++i)
{
if (strcmp(pwds[i].uname, username) == 0)
{ /* found */
pwds[i].pwd = strdup(e_passwd);
break;
}
}
if (i == npwds)
{ /* did not exist */
if (npwds == MAXPWDS)
{
fprintf(stderr, "Cannot handle so many entries\n");
exit(1);
}
pwds[npwds].uname = strdup(username);
pwds[npwds].pwd = strdup(e_passwd);
pwds[npwds].rest = NULL;
++npwds;
}
/* write back the file */
sprintf(bkname, "%s.bk", filename);
write_pwd_file(filename, bkname);
return 0;
}
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: pg_proc.h,v 1.224 2002/03/29 19:06:19 tgl Exp $
* $Id: pg_proc.h,v 1.225 2002/04/04 04:25:52 momjian Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
......@@ -2101,8 +2101,8 @@ DESCR("does not match LIKE expression, case-insensitive");
DATA(insert OID = 1637 ( like_escape PGUID 12 f t t t 2 f 25 "25 25" 100 0 0 100 like_escape - _null_ ));
DESCR("convert match pattern to use backslash escapes");
DATA(insert OID = 1689 ( update_pg_pwd PGUID 12 f t f t 0 f 0 "" 100 0 0 100 update_pg_pwd - _null_ ));
DESCR("update pg_pwd file");
DATA(insert OID = 1689 ( update_pg_pwd_and_pg_group PGUID 12 f t f t 0 f 0 "" 100 0 0 100 update_pg_pwd_and_pg_group - _null_ ));
DESCR("update pg_pwd and pg_group files");
/* Oracle Compatibility Related Functions - By Edmund Mergl <E.Mergl@bawue.de> */
DATA(insert OID = 868 ( strpos PGUID 12 f t t t 2 f 23 "25 25" 100 0 0 100 textpos - _null_ ));
......
......@@ -3,15 +3,23 @@
* user.h
*
*
* $Id: user.h,v 1.17 2002/03/01 22:45:17 petere Exp $
* $Id: user.h,v 1.18 2002/04/04 04:25:53 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef USER_H
#define USER_H
#include "fmgr.h"
#include "nodes/parsenodes.h"
#define PWD_FILE "pg_pwd"
#define USER_GROUP_FILE "pg_group"
extern char *group_getfilename(void);
extern char *user_getfilename(void);
extern void CreateUser(CreateUserStmt *stmt);
extern void AlterUser(AlterUserStmt *stmt);
extern void AlterUserSet(AlterUserSetStmt *stmt);
......@@ -21,6 +29,6 @@ extern void CreateGroup(CreateGroupStmt *stmt);
extern void AlterGroup(AlterGroupStmt *stmt, const char *tag);
extern void DropGroup(DropGroupStmt *stmt);
extern Datum update_pg_pwd(PG_FUNCTION_ARGS);
extern Datum update_pg_pwd_and_pg_group(PG_FUNCTION_ARGS);
#endif /* USER_H */
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: crypt.h,v 1.19 2001/11/12 01:52:46 momjian Exp $
* $Id: crypt.h,v 1.20 2002/04/04 04:25:53 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -15,8 +15,6 @@
#include "libpq/libpq-be.h"
#define CRYPT_PWD_FILE_SEPSTR "\t"
/* Also defined in interfaces/odbc/md5.h */
#define MD5_PASSWD_LEN 35
......@@ -24,9 +22,6 @@
strlen(passwd) == MD5_PASSWD_LEN)
extern char *crypt_getpwdfilename(void);
extern void load_password_cache(void);
extern int md5_crypt_verify(const Port *port, const char *user,
const char *pgpass);
extern bool md5_hash(const void *buff, size_t len, char *hexsum);
......
......@@ -4,7 +4,7 @@
* Interface to hba.c
*
*
* $Id: hba.h,v 1.31 2001/11/05 17:46:33 momjian Exp $
* $Id: hba.h,v 1.32 2002/04/04 04:25:54 momjian Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -15,15 +15,14 @@
#include <netinet/in.h>
#endif
#include "nodes/pg_list.h"
#define CONF_FILE "pg_hba.conf"
/* Name of the config file */
#define USERMAP_FILE "pg_ident.conf"
/* Name of the usermap file */
#define OLD_CONF_FILE "pg_hba"
/* Name of the config file in prior releases of Postgres. */
#define IDENT_PORT 113
/* Standard TCP port number for Ident service. Assigned by IANA */
......@@ -46,8 +45,15 @@ typedef enum UserAuth
typedef struct Port hbaPort;
#define MAX_TOKEN 256
extern void next_token(FILE *fp, char *buf, const int bufsz);
extern List **get_user_line(const char *user);
extern void load_hba(void);
extern void load_ident(void);
extern void load_user(void);
extern void load_group(void);
extern int hba_getauthmethod(hbaPort *port);
extern int authident(hbaPort *port);
extern void load_hba_and_ident(void);
#endif
......@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: miscadmin.h,v 1.101 2002/03/04 01:46:04 tgl Exp $
* $Id: miscadmin.h,v 1.102 2002/04/04 04:25:51 momjian Exp $
*
* NOTES
* some of the information in this file should be moved to
......@@ -219,7 +219,6 @@ extern int FindExec(char *full_path, const char *argv0,
extern int CheckPathAccess(char *path, char *name, int open_mode);
#ifdef CYR_RECODE
extern void GetCharSetByHost(char *TableName, int host, const char *DataDir);
extern void SetCharSet(void);
extern char *convertstr(unsigned char *buff, int len, int dest);
#endif
......
......@@ -30,7 +30,7 @@ WHERE (p1.prolang = 0 OR p1.prorettype = 0 OR
AND p1.proname !~ '^pl[^_]+_call_handler$'
AND p1.proname !~ '^RI_FKey_'
AND p1.proname !~ 'costestimate$'
AND p1.proname != 'update_pg_pwd';
AND p1.proname != 'update_pg_pwd_and_pg_group';
oid | proname
-----+---------
(0 rows)
......
......@@ -33,7 +33,7 @@ WHERE (p1.prolang = 0 OR p1.prorettype = 0 OR
AND p1.proname !~ '^pl[^_]+_call_handler$'
AND p1.proname !~ '^RI_FKey_'
AND p1.proname !~ 'costestimate$'
AND p1.proname != 'update_pg_pwd';
AND p1.proname != 'update_pg_pwd_and_pg_group';
-- Look for conflicting proc definitions (same names and input datatypes).
-- (This test should be dead code now that we have the unique index
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment