Directory/File Access Permissions for COPY and Generic File Access Functions
All,
The attached patch for review implements a directory permission system that
allows for providing a directory read/write capability to directories for
COPY TO/FROM and Generic File Access Functions to non-superusers. This is
not a complete solution as it does not currently contain documentation or
regression tests. Though it is my hopes to get some feedback as I am sure
there are some aspects of it that need to be reworked. So I thought I'd
put it out there for comments/review.
The approach taken is to create "directory aliases" that have a unique name
and path, as well as an associated ACL list. A superuser can create a new
alias to any directory on the system and then provide READ or WRITE
permissions to any non-superuser. When a non-superuser then attempts to
execute a COPY TO/FROM or any one of the generic file access functions, a
permission check is performed against the aliases for the user and target
directory. Superusers are allowed to bypass all of these checks. All
alias paths must be an absolute path in order to avoid potential risks.
However, in the generic file access functions superusers are still allowed
to execute the functions with a relative path where non-superusers are
required to provide an absolute path.
- Implementation Details -
System Catalog:
pg_diralias
- dirname - the name of the directory alias
- dirpath - the directory path - must be absolute
- diracl - the ACL for the directory
Syntax:
CREATE DIRALIAS <name> AS '<path>'
ALTER DIRALIAS <name> AS '<path>'
ALTER DIRALIAS <name> RENAME TO <new_name>
DROP DIRALIAS <name>
This is probably the area that I would really appreciate your thoughts and
recommendations. To GRANT permissions to a directory alias, I had to create
a special variant of GRANT since READ and WRITE are not reserved keywords
and causes grammar issues. Therefore, I chose to start with the following
syntax:
GRANT ON DIRALIAS <name> <permissions> TO <roles>
where <permissions> is either READ, WRITE or ALL.
Any comments, suggestions or feedback would be greatly appreciated.
Thanks,
Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
Attachments:
directory-alias-v1.patchtext/x-patch; charset=US-ASCII; name=directory-alias-v1.patchDownload
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index b257b02..8cdc5cb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
- toasting.h indexing.h \
+ pg_diralias.h toasting.h indexing.h \
)
# location of Catalog.pm
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d30612c..3717bf5 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -30,6 +30,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
@@ -48,6 +49,7 @@
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "commands/dbcommands.h"
+#include "commands/diralias.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "foreign/foreign.h"
@@ -3183,6 +3185,190 @@ ExecGrant_Type(InternalGrant *istmt)
heap_close(relation, RowExclusiveLock);
}
+/*
+ * ExecuteGrantDirAliasStmt
+ * handles the execution of the GRANT/REVOKE ON DIRALIAS command.
+ *
+ * stmt - the GrantDirAliasStmt that describes the directory aliases and
+ * permissions to be granted/revoked.
+ */
+void
+ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt)
+{
+ Relation pg_diralias_rel;
+ Oid grantor;
+ List *grantee_ids = NIL;
+ AclMode permissions;
+ ListCell *item;
+
+ /* Must be superuser to grant directory alias permissions */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to grant directory alias permissions")));
+
+ /*
+ * Grantor is optional. If it is not provided then set it to the current
+ * user.
+ */
+ if (stmt->grantor)
+ grantor = get_role_oid(stmt->grantor, false);
+ else
+ grantor = GetUserId();
+
+ /* Convert grantee names to oids */
+ foreach(item, stmt->grantees)
+ {
+ PrivGrantee *grantee = (PrivGrantee *) lfirst(item);
+
+ if (grantee->rolname == NULL)
+ grantee_ids = lappend_oid(grantee_ids, ACL_ID_PUBLIC);
+ else
+ {
+ Oid roleid = get_role_oid(grantee->rolname, false);
+ grantee_ids = lappend_oid(grantee_ids, roleid);
+ }
+ }
+
+ permissions = ACL_NO_RIGHTS;
+
+ /* If ALL was provided then set permissions to ACL_ALL_RIGHTS_DIRALIAS */
+ if (stmt->permissions == NIL)
+ permissions = ACL_ALL_RIGHTS_DIRALIAS;
+ else
+ {
+ /* Condense all permissions */
+ foreach(item, stmt->permissions)
+ {
+ AccessPriv *priv = (AccessPriv *) lfirst(item);
+ permissions |= string_to_privilege(priv->priv_name);
+ }
+ }
+
+
+ /*
+ * Though it shouldn't be possible to provide permissions other than READ
+ * and WRITE, check to make sure no others have been set. If they have,
+ * then warn the user and correct the permissions.
+ */
+ if (permissions & !((AclMode) ACL_ALL_RIGHTS_DIRALIAS))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("directory aliases only support READ and WRITE permissions")));
+
+ permissions &= ACL_ALL_RIGHTS_DIRALIAS;
+ }
+
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /* Grant/Revoke permissions on directory aliases */
+ foreach(item, stmt->directories)
+ {
+ Datum values[Natts_pg_diralias];
+ bool replaces[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ HeapTuple new_tuple;
+ Datum datum;
+ Oid owner_id;
+ Acl *dir_acl;
+ Acl *new_acl;
+ bool is_null;
+ int num_old_members;
+ int num_new_members;
+ Oid *old_members;
+ Oid *new_members;
+ Oid diralias_id;
+ char *name;
+
+ name = strVal(lfirst(item));
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(name));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for directory alias \"%s\"", name);
+
+ /*
+ * Get directory alias owner id. Since all superusers are considered
+ * to be owners of a directory alias, it is safe to assume that the
+ * current user is an owner, given the superuser check above.
+ */
+ owner_id = GetUserId();
+
+ /* Get directory alias ACL */
+ datum = heap_getattr(tuple, Anum_pg_diralias_diracl,
+ RelationGetDescr(pg_diralias_rel), &is_null);
+
+ /* Get the directory alias oid */
+ diralias_id = HeapTupleGetOid(tuple);
+
+ /*
+ * If there are currently no permissions granted on the directory alias,
+ * then add default permissions, which should include the permssions
+ * granted to the owner of the table. Directory aliases are owned by
+ * all superusers.
+ */
+ if (is_null)
+ {
+ dir_acl = acldefault(ACL_OBJECT_DIRALIAS, owner_id);
+ num_old_members = 0;
+ old_members = NULL;
+ }
+ else
+ {
+ dir_acl = DatumGetAclPCopy(datum);
+
+ /* Get the roles in the current ACL */
+ num_old_members = aclmembers(dir_acl, &old_members);
+ }
+
+ /* Merge new ACL with current ACL */
+ new_acl = merge_acl_with_grant(dir_acl, stmt->is_grant, false,
+ DROP_CASCADE, grantee_ids, permissions,
+ grantor, owner_id);
+
+ num_new_members = aclmembers(new_acl, &new_members);
+
+ /* Insert new ACL value */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(replaces, 0, sizeof(replaces));
+
+ values[Anum_pg_diralias_diracl - 1] = PointerGetDatum(new_acl);
+ replaces[Anum_pg_diralias_diracl - 1] = true;
+
+ new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel),
+ values, nulls, replaces);
+
+ simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, new_tuple);
+
+ /* Update shared dependency ACL information */
+ updateAclDependencies(DirAliasRelationId, diralias_id, 0,
+ owner_id,
+ num_old_members, old_members,
+ num_new_members, new_members);
+
+ /* Clean Up */
+ pfree(new_acl);
+ heap_endscan(scandesc);
+ }
+
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+}
+
static AclMode
string_to_privilege(const char *privname)
@@ -3307,6 +3493,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
gettext_noop("permission denied for event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("permission denied for extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("permission denied for directory alias %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
@@ -3353,6 +3541,8 @@ static const char *const not_owner_msg[MAX_ACL_KIND] =
gettext_noop("must be owner of event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("must be owner of extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("must be owner of directory alias %s"),
};
@@ -4194,6 +4384,62 @@ pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
}
/*
+ * Exported routine for examining a user's permissions for a directory alias.
+ */
+AclMode
+pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+ AclMode result;
+ HeapTuple tuple;
+ Datum aclDatum;
+ bool isNull;
+ Acl *acl;
+
+ /* Bypass permission checks for superusers */
+ if (superuser_arg(roleid))
+ return mask;
+
+ /* Must get the directory alias's tuple from pg_diralias */
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(dir_oid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias with OID %u does not exist", dir_oid)));
+
+ aclDatum = SysCacheGetAttr(DIRALIASOID, tuple,
+ Anum_pg_diralias_diracl, &isNull);
+ if (isNull)
+ {
+ /* No ACL, so build default ACL */
+ acl = acldefault(ACL_OBJECT_DIRALIAS, roleid);
+ aclDatum = (Datum) 0;
+ }
+ else
+ {
+ /* detoast rel's ACL if necessary */
+ acl = DatumGetAclP(aclDatum);
+ }
+
+ /*
+ * We use InvalidOid as the ownerid for determining the aclmask. This is
+ * because directory aliases belong to all superusers. aclmask() uses the
+ * ownerid to determine grant options by implying that owners always have
+ * all grant options. If roleid, is not a superuser and therefore an owner
+ * (which it couldn't be at this point), then this check in aclmask() must
+ * be false. Therefore, by using InvalidOid we are guaranteed this behavior.
+ */
+ result = aclmask(acl, roleid, InvalidOid, mask, how);
+
+ /* if we have a detoasted copy, free it */
+ if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+ pfree(acl);
+
+ ReleaseSysCache(tuple);
+
+ return result;
+}
+
+/*
* Exported routine for examining a user's privileges for a type.
*/
AclMode
@@ -4513,6 +4759,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
}
/*
+ * Exported routine for checking a user's access permissions to a directory alias
+ */
+AclResult
+pg_diralias_aclcheck(Oid dir_oid, Oid roleid, AclMode mode)
+{
+ if (pg_diralias_aclmask(dir_oid, roleid, mode, ACLMASK_ANY) != 0)
+ return ACLCHECK_OK;
+ else
+ return ACLCHECK_NO_PRIV;
+}
+
+/*
* Ownership check for a relation (specified by OID).
*/
bool
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 256486c..b056559 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -33,6 +33,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
@@ -56,6 +57,7 @@
#include "catalog/pg_user_mapping.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -1255,6 +1257,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_DIRALIAS:
+ RemoveDirAliasById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2325,6 +2331,9 @@ getObjectClass(const ObjectAddress *object)
case RowSecurityRelationId:
return OCLASS_ROWSECURITY;
+
+ case DirAliasRelationId:
+ return OCLASS_DIRALIAS;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..872d233 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -26,6 +26,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_default_acl.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -54,6 +55,7 @@
#include "catalog/pg_user_mapping.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -358,6 +360,18 @@ static const ObjectPropertyType ObjectProperty[] =
false
},
{
+ DirAliasRelationId,
+ DirAliasOidIndexId,
+ DIRALIASOID,
+ -1,
+ Anum_pg_diralias_dirname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ -1,
+ false
+ },
+ {
EventTriggerRelationId,
EventTriggerOidIndexId,
EVENTTRIGGEROID,
@@ -536,6 +550,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
&relation, missing_ok);
break;
case OBJECT_DATABASE:
+ case OBJECT_DIRALIAS:
case OBJECT_EXTENSION:
case OBJECT_TABLESPACE:
case OBJECT_ROLE:
@@ -746,6 +761,9 @@ get_object_address_unqualified(ObjectType objtype,
case OBJECT_DATABASE:
msg = gettext_noop("database name cannot be qualified");
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias cannot be qualified");
+ break;
case OBJECT_EXTENSION:
msg = gettext_noop("extension name cannot be qualified");
break;
@@ -790,6 +808,11 @@ get_object_address_unqualified(ObjectType objtype,
address.objectId = get_database_oid(name, missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_DIRALIAS:
+ address.classId = DirAliasRelationId;
+ address.objectId = get_diralias_oid(name, missing_ok);
+ address.objectSubId = 0;
+ break;
case OBJECT_EXTENSION:
address.classId = ExtensionRelationId;
address.objectId = get_extension_oid(name, missing_ok);
@@ -1318,6 +1341,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_DIRALIAS:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
@@ -2224,6 +2248,18 @@ getObjectDescription(const ObjectAddress *object)
break;
}
+ case OCLASS_DIRALIAS:
+ {
+ char *diralias_name;
+
+ diralias_name = get_diralias_name(object->objectId);
+ if (!diralias_name)
+ elog(ERROR, "cache lookup failed for directory alias %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("directory alias %s"), diralias_name);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index b1ac704..36a897c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
- dbcommands.o define.o discard.o dropcmds.o \
+ dbcommands.o define.o diralias.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
policy.o portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c9a9baf..47f8d49 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -41,6 +41,7 @@
#include "commands/conversioncmds.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -349,6 +350,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_EVENT_TRIGGER:
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b83576..3a9562b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -28,6 +28,7 @@
#include "catalog/pg_type.h"
#include "commands/copy.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
@@ -788,9 +789,13 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
Oid relid;
Node *query = NULL;
- /* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
{
+ /*
+ * Disallow COPY to/from program except to superusers. If COPY is to/from
+ * a file then diallow unless the current user is either superuser or has
+ * been granted the appropriate permissions on the target parent directory.
+ */
if (stmt->is_program)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -798,11 +803,43 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
else
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to COPY to or from a file"),
- errhint("Anyone can COPY to stdout or from stdin. "
- "psql's \\copy command also works for anyone.")));
+ {
+ char *path;
+ Oid diralias_id;
+ AclResult aclresult;
+
+ /* Get the parent directory */
+ path = pstrdup(stmt->filename);
+ canonicalize_path(path);
+ get_parent_directory(path);
+
+ /* Search for directory in pg_diralias */
+ diralias_id = get_diralias_oid_by_path(path);
+
+ /*
+ * If an entry does not exist for the path in pg_diralias then raise
+ * an error.
+ */
+ if (!OidIsValid(diralias_id))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("a directory alias entry for \"%s\" does not exist.",
+ path)));
+
+ /* Check directory alias entry permissions */
+ if (stmt->is_from)
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
+ else
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_UPDATE);
+
+ /* If the current user has insufficient privileges then raise an error. */
+ if (aclresult != ACLCHECK_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have permissions to COPY to or from \"%s\"", path),
+ errhint("Anyone can COPY to stdout or from stdin. "
+ "psql's \\copy command also works for anyone.")));
+ }
}
if (stmt->relation)
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 8583581..8cae12e 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -380,6 +380,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
list_length(objname) - 1));
}
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias \"%s\" does not exist, skipping");
+ name = NameListToString(objname);
+ break;
case OBJECT_EVENT_TRIGGER:
msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
name = NameListToString(objname);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1b8c94b..b70c322 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -73,6 +73,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"COLLATION", true},
{"CONVERSION", true},
{"DATABASE", false},
+ {"DIRALIAS", true},
{"DOMAIN", true},
{"EXTENSION", true},
{"EVENT TRIGGER", false},
@@ -924,6 +925,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_CONSTRAINT:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_DOMAIN:
case OBJECT_EXTENSION:
case OBJECT_FDW:
@@ -998,6 +1000,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_ROWSECURITY:
+ case OCLASS_DIRALIAS:
return true;
case MAX_OCLASS:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 21b070a..8941fa2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2702,6 +2702,20 @@ _copyGrantRoleStmt(const GrantRoleStmt *from)
return newnode;
}
+static GrantDirAliasStmt *
+_copyGrantDirAliasStmt(const GrantDirAliasStmt *from)
+{
+ GrantDirAliasStmt *newnode = makeNode(GrantDirAliasStmt);
+
+ COPY_NODE_FIELD(directories);
+ COPY_NODE_FIELD(permissions);
+ COPY_NODE_FIELD(grantees);
+ COPY_SCALAR_FIELD(is_grant);
+ COPY_STRING_FIELD(grantor);
+
+ return newnode;
+}
+
static AlterDefaultPrivilegesStmt *
_copyAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *from)
{
@@ -3879,6 +3893,28 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static CreateDirAliasStmt *
+_copyCreateDirAliasStmt(const CreateDirAliasStmt *from)
+{
+ CreateDirAliasStmt *newnode = makeNode(CreateDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+}
+
+static AlterDirAliasStmt *
+_copyAlterDirAliasStmt(const AlterDirAliasStmt *from)
+{
+ AlterDirAliasStmt *newnode = makeNode(AlterDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -4318,6 +4354,9 @@ copyObject(const void *from)
case T_GrantStmt:
retval = _copyGrantStmt(from);
break;
+ case T_GrantDirAliasStmt:
+ retval = _copyGrantDirAliasStmt(from);
+ break;
case T_GrantRoleStmt:
retval = _copyGrantRoleStmt(from);
break;
@@ -4597,6 +4636,12 @@ copyObject(const void *from)
case T_AlterPolicyStmt:
retval = _copyAlterPolicyStmt(from);
break;
+ case T_CreateDirAliasStmt:
+ retval = _copyCreateDirAliasStmt(from);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _copyAlterDirAliasStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 358395f..e266ad8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1046,6 +1046,18 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b)
}
static bool
+_equalGrantDirAliasStmt(const GrantDirAliasStmt *a, const GrantDirAliasStmt *b)
+{
+ COMPARE_NODE_FIELD(directories);
+ COMPARE_NODE_FIELD(permissions);
+ COMPARE_NODE_FIELD(grantees);
+ COMPARE_SCALAR_FIELD(is_grant);
+ COMPARE_STRING_FIELD(grantor);
+
+ return true;
+}
+
+static bool
_equalAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *a, const AlterDefaultPrivilegesStmt *b)
{
COMPARE_NODE_FIELD(options);
@@ -2034,6 +2046,24 @@ _equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
}
static bool
+_equalCreateDirAliasStmt(const CreateDirAliasStmt *a, const CreateDirAliasStmt *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+}
+
+static bool
+_equalAlterDirAliasStmt(const AlterDirAliasStmt *a, const AlterDirAliasStmt *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+}
+
+static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
COMPARE_SCALAR_FIELD(kind);
@@ -2778,6 +2808,9 @@ equal(const void *a, const void *b)
case T_GrantStmt:
retval = _equalGrantStmt(a, b);
break;
+ case T_GrantDirAliasStmt:
+ retval = _equalGrantDirAliasStmt(a, b);
+ break;
case T_GrantRoleStmt:
retval = _equalGrantRoleStmt(a, b);
break;
@@ -3057,6 +3090,12 @@ equal(const void *a, const void *b)
case T_AlterPolicyStmt:
retval = _equalAlterPolicyStmt(a, b);
break;
+ case T_CreateDirAliasStmt:
+ retval = _equalCreateDirAliasStmt(a, b);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _equalAlterDirAliasStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c98c27a..1a95229 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -258,7 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
- CreateMatViewStmt RefreshMatViewStmt
+ CreateMatViewStmt RefreshMatViewStmt GrantDirStmt RevokeDirStmt
+ CreateDirAliasStmt AlterDirAliasStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -324,6 +325,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole
+%type <node> dir_perm_opts
+%type <list> dir_permissions dir_perm_list
+
%type <str> iso_level opt_encoding
%type <node> grantee
%type <list> grantee_list
@@ -559,7 +563,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DICTIONARY DIRALIAS DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -734,6 +739,7 @@ stmt :
| AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
+ | AlterDirAliasStmt
| AlterDomainStmt
| AlterEnumStmt
| AlterExtensionStmt
@@ -769,6 +775,7 @@ stmt :
| CreateAssertStmt
| CreateCastStmt
| CreateConversionStmt
+ | CreateDirAliasStmt
| CreateDomainStmt
| CreateExtensionStmt
| CreateFdwStmt
@@ -820,6 +827,7 @@ stmt :
| ExplainStmt
| FetchStmt
| GrantStmt
+ | GrantDirStmt
| GrantRoleStmt
| ImportForeignSchemaStmt
| IndexStmt
@@ -837,6 +845,7 @@ stmt :
| RemoveOperStmt
| RenameStmt
| RevokeStmt
+ | RevokeDirStmt
| RevokeRoleStmt
| RuleStmt
| SecLabelStmt
@@ -4622,6 +4631,34 @@ row_security_cmd:
/*****************************************************************************
*
+ * QUERIES:
+ * CREATE DIRALIAS <name> AS <path>
+ * ALTER DIRALIAS <name> AS <path>
+ *
+ *****************************************************************************/
+
+CreateDirAliasStmt:
+ CREATE DIRALIAS name AS Sconst
+ {
+ CreateDirAliasStmt *n = makeNode(CreateDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+AlterDirAliasStmt:
+ ALTER DIRALIAS name AS Sconst
+ {
+ AlterDirAliasStmt *n = makeNode(AlterDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*****************************************************************************
+ *
* QUERIES :
* CREATE TRIGGER ...
* DROP TRIGGER ...
@@ -5497,6 +5534,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
| EXTENSION { $$ = OBJECT_EXTENSION; }
+ | DIRALIAS { $$ = OBJECT_DIRALIAS; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
@@ -6330,6 +6368,67 @@ opt_granted_by: GRANTED BY RoleId { $$ = $3; }
/*****************************************************************************
*
+ * QUERIES:
+ * GRANT ON DIRALIAS <alias> <permissions> TO <roles>
+ * REVOKE ON DIRALIAS <alias> <permsissions> FROM <roles>
+ *
+ *****************************************************************************/
+GrantDirStmt:
+ GRANT ON DIRALIAS name_list dir_permissions TO grantee_list
+ opt_granted_by
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = true;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ n->grantor = $8;
+ $$ = (Node*)n;
+ }
+ ;
+
+RevokeDirStmt:
+ REVOKE ON DIRALIAS name_list dir_permissions FROM grantee_list
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = false;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ $$ = (Node*)n;
+ }
+ ;
+
+/* either ALL or a list of individual permissions */
+dir_permissions: dir_perm_list
+ { $$ = $1; }
+ | ALL { $$ = NIL; }
+ ;
+
+
+dir_perm_list: dir_perm_opts { $$ = list_make1($1); }
+ | dir_perm_list ',' dir_perm_opts { $$ = lappend($1, $3); }
+ ;
+
+dir_perm_opts:
+ READ
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("select");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ | WRITE
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("update");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ ;
+
+/*****************************************************************************
+ *
* ALTER DEFAULT PRIVILEGES statement
*
*****************************************************************************/
@@ -7289,6 +7388,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER DIRALIAS name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_DIRALIAS;
+ n->object = list_make1(makeString($3));
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER DOMAIN_P any_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -13087,6 +13195,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DICTIONARY
+ | DIRALIAS
| DISABLE_P
| DISCARD
| DOCUMENT_P
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4a2a339..4980016 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -33,6 +33,7 @@
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/discard.h"
#include "commands/event_trigger.h"
#include "commands/explain.h"
@@ -185,6 +186,7 @@ check_xact_readonly(Node *parsetree)
case T_DropRoleStmt:
case T_GrantStmt:
case T_GrantRoleStmt:
+ case T_GrantDirAliasStmt:
case T_AlterDefaultPrivilegesStmt:
case T_TruncateStmt:
case T_DropOwnedStmt:
@@ -557,6 +559,10 @@ standard_ProcessUtility(Node *parsetree,
GrantRole((GrantRoleStmt *) parsetree);
break;
+ case T_GrantDirAliasStmt:
+ ExecuteGrantDirAliasStmt((GrantDirAliasStmt *) parsetree);
+ break;
+
case T_CreatedbStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
@@ -1329,6 +1335,14 @@ ProcessUtilitySlow(Node *parsetree,
AlterPolicy((AlterPolicyStmt *) parsetree);
break;
+ case T_CreateDirAliasStmt: /* CREATE DIRALIAS */
+ CreateDirAlias((CreateDirAliasStmt *) parsetree);
+ break;
+
+ case T_AlterDirAliasStmt: /* ALTER DIRALIAS */
+ AlterDirAlias((AlterDirAliasStmt *) parsetree);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(parsetree));
@@ -1596,6 +1610,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
break;
+ case OBJECT_DIRALIAS:
+ tag = "ALTER DIRALIAS";
+ break;
case OBJECT_DOMAIN:
tag = "ALTER DOMAIN";
break;
@@ -1959,6 +1976,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
+ case OBJECT_DIRALIAS:
+ tag = "DROP DIRALIAS";
+ break;
default:
tag = "???";
}
@@ -2016,6 +2036,14 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_GrantDirAliasStmt:
+ {
+ GrantDirAliasStmt *stmt = (GrantDirAliasStmt *) parsetree;
+
+ tag = (stmt->is_grant ? "GRANT ON DIRALIAS" : "REVOKE ON DIRALIAS");
+ }
+ break;
+
case T_GrantRoleStmt:
{
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
@@ -2310,6 +2338,14 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER POLICY";
break;
+ case T_CreateDirAliasStmt:
+ tag = "CREATE DIRALIAS";
+ break;
+
+ case T_AlterDirAliasStmt:
+ tag = "ALTER DIRALIAS";
+ break;
+
case T_PrepareStmt:
tag = "PREPARE";
break;
@@ -2862,6 +2898,14 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_AlterDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterTSDictionaryStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index dc6eb2c..7cc00ef 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -790,6 +790,10 @@ acldefault(GrantObjectType objtype, Oid ownerId)
world_default = ACL_USAGE;
owner_default = ACL_ALL_RIGHTS_TYPE;
break;
+ case ACL_OBJECT_DIRALIAS:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_DIRALIAS;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3a0957f..58dd1dc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -22,11 +22,13 @@
#include "access/htup_details.h"
#include "catalog/pg_type.h"
+#include "commands/diralias.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -37,46 +39,44 @@ typedef struct
DIR *dirdesc;
} directory_fctx;
-
/*
- * Convert a "text" filename argument to C string, and check it's allowable.
+ * Check the directory permissions for the provided filename/path.
*
- * Filename may be absolute or relative to the DataDir, but we only allow
- * absolute paths that match DataDir or Log_directory.
+ * The filename must be an absolute path to the file.
*/
-static char *
-convert_and_check_filename(text *arg)
+static void
+check_directory_permissions(char *directory)
{
- char *filename;
+ Oid diralias_id;
+ AclResult aclresult;
- filename = text_to_cstring(arg);
- canonicalize_path(filename); /* filename can change length here */
+ /* Do not allow relative paths */
+ if (!is_absolute_path(directory))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("relative path not allowed")));
- if (is_absolute_path(filename))
- {
- /* Disallow '/a/b/data/..' */
- if (path_contains_parent_reference(filename))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("reference to parent directory (\"..\") not allowed"))));
-
- /*
- * Allow absolute paths if within DataDir or Log_directory, even
- * though Log_directory might be outside DataDir.
- */
- if (!path_is_prefix_of_path(DataDir, filename) &&
- (!is_absolute_path(Log_directory) ||
- !path_is_prefix_of_path(Log_directory, filename)))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("absolute path not allowed"))));
- }
- else if (!path_is_relative_and_below_cwd(filename))
+ /* Search for directory in pg_diralias */
+ diralias_id = get_diralias_oid_by_path(directory);
+
+ /*
+ * If an entry does not exist for the path in pg_diralias then raise
+ * an error.
+ */
+ if (!OidIsValid(diralias_id))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("path must be in or below the current directory"))));
+ errmsg("directory alias entry for \"%s\" does not exist",
+ directory)));
+
+ /* Check directory alias entry permissions */
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
- return filename;
+ /* If the current user has insufficient privileges then raise an error */
+ if (aclresult != ACLCHECK_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have read permissions on directory")));
}
@@ -173,13 +173,19 @@ pg_read_file(PG_FUNCTION_ARGS)
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (bytes_to_read < 0)
ereport(ERROR,
@@ -197,13 +203,19 @@ pg_read_file_all(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
}
@@ -218,13 +230,19 @@ pg_read_binary_file(PG_FUNCTION_ARGS)
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (bytes_to_read < 0)
ereport(ERROR,
@@ -242,13 +260,19 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
}
@@ -261,18 +285,23 @@ pg_stat_file(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
struct stat fst;
Datum values[6];
bool isnull[6];
HeapTuple tuple;
TupleDesc tupdesc;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to get file information"))));
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (stat(filename, &fst) < 0)
ereport(ERROR,
@@ -331,11 +360,6 @@ pg_ls_dir(PG_FUNCTION_ARGS)
struct dirent *de;
directory_fctx *fctx;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to get directory listings"))));
-
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
@@ -344,7 +368,12 @@ pg_ls_dir(PG_FUNCTION_ARGS)
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(directory_fctx));
- fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
+ fctx->location = text_to_cstring(PG_GETARG_TEXT_P(0));
+ canonicalize_path(fctx->location);
+
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ check_directory_permissions(fctx->location);
fctx->dirdesc = AllocateDir(fctx->location);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..383e1c9 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -37,6 +37,7 @@
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_foreign_data_wrapper.h"
@@ -367,6 +368,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {DirAliasRelationId, /* DIRALIASOID */
+ DirAliasOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{EnumRelationId, /* ENUMOID */
EnumOidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 8bfc604..da46c9f 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -103,6 +103,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
int numForeignServers;
int numDefaultACLs;
int numEventTriggers;
+ int numDirectoryAliases;
if (g_verbose)
write_msg(NULL, "reading schemas\n");
@@ -251,6 +252,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
write_msg(NULL, "reading row-security policies\n");
getRowSecurity(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading directory aliases\n");
+ getDirectoryAliases(fout, &numDirectoryAliases);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 259c472..08761e3 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -19,6 +19,7 @@
#include "dumputils.h"
#include "parser/keywords.h"
+#include "pg_backup_utils.h"
/* Globals from keywords.c */
@@ -545,10 +546,16 @@ buildACLCommands(const char *name, const char *subname,
* wire-in knowledge about the default public privileges for different
* kinds of objects.
*/
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM PUBLIC;\n",
+ name);
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ }
/*
* We still need some hacking though to cover the case where new default
@@ -593,16 +600,34 @@ buildACLCommands(const char *name, const char *subname,
? strcmp(privswgo->data, "ALL") != 0
: strcmp(privs->data, "ALL") != 0)
{
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
- type, name, fmtId(grantee->data));
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s;\n",
+ name, fmtId(grantee->data));
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(grantee->data));
+ }
+
if (privs->len > 0)
- appendPQExpBuffer(firstsql,
- "%sGRANT %s ON %s %s TO %s;\n",
- prefix, privs->data, type, name,
- fmtId(grantee->data));
+ {
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql,
+ "%sGRANT ON DIRALIAS %s %s TO %s;\n",
+ prefix, name, privs->data,
+ fmtId(grantee->data));
+ else
+ appendPQExpBuffer(firstsql,
+ "%sGRANT %s ON %s %s TO %s;\n",
+ prefix, privs->data, type, name,
+ fmtId(grantee->data));
+ }
+
if (privswgo->len > 0)
appendPQExpBuffer(firstsql,
"%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
@@ -622,8 +647,14 @@ buildACLCommands(const char *name, const char *subname,
if (privs->len > 0)
{
- appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
- prefix, privs->data, type, name);
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(secondsql, "%sGRANT ON DIRALIAS %s %s TO ",
+ prefix, name, privs->data);
+ else
+ appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
+ prefix, privs->data, type, name);
+
if (grantee->len == 0)
appendPQExpBufferStr(secondsql, "PUBLIC;\n");
else if (strncmp(grantee->data, "group ",
@@ -660,11 +691,18 @@ buildACLCommands(const char *name, const char *subname,
*/
if (!found_owner_privs && owner)
{
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
- type, name, fmtId(owner));
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s",
+ name, fmtId(owner));
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(owner));
+ }
}
destroyPQExpBuffer(grantee);
@@ -873,6 +911,11 @@ do { \
CONVERT_PRIV('r', "SELECT");
CONVERT_PRIV('w', "UPDATE");
}
+ else if (strcmp(type, "DIRALIAS") == 0)
+ {
+ CONVERT_PRIV('r', "READ");
+ CONVERT_PRIV('w', "WRITE");
+ }
else
abort();
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 00c882d..d1abe61 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3106,7 +3106,8 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
strcmp(type, "SCHEMA") == 0 ||
strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
strcmp(type, "SERVER") == 0 ||
- strcmp(type, "USER MAPPING") == 0)
+ strcmp(type, "USER MAPPING") == 0 ||
+ strcmp(type, "DIRALIAS") == 0)
{
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
@@ -3307,7 +3308,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
- strcmp(te->desc, "SERVER") == 0)
+ strcmp(te->desc, "SERVER") == 0 ||
+ strcmp(te->desc, "DIRALIAS") == 0)
{
PQExpBuffer temp = createPQExpBuffer();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56a4cb..9b6a6fa 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -191,6 +191,7 @@ static void dumpUserMappings(Archive *fout,
const char *servername, const char *namespace,
const char *owner, CatalogId catalogId, DumpId dumpId);
static void dumpDefaultACL(Archive *fout, DumpOptions *dopt, DefaultACLInfo *daclinfo);
+static void dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo);
static void dumpACL(Archive *fout, DumpOptions *dopt, CatalogId objCatId, DumpId objDumpId,
const char *type, const char *name, const char *subname,
@@ -2983,6 +2984,99 @@ dumpRowSecurity(Archive *fout, DumpOptions *dopt, RowSecurityInfo *rsinfo)
destroyPQExpBuffer(delqry);
}
+void
+getDirectoryAliases(Archive *fout, int *numDirectoryAliases)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ DirectoryAliasInfo *dirinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_diralias;
+ int i_dirpath;
+ int i_rolname;
+ int i_diracl;
+ int i, ntups;
+
+ if (fout->remoteVersion < 90500)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, diralias, dirpath, "
+ "(%s dirowner) AS rolname, diracl "
+ "FROM pg_catalog.pg_directory",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ dirinfo = (DirectoryAliasInfo *) pg_malloc(ntups * sizeof(DirectoryAliasInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_diralias = PQfnumber(res, "diralias");
+ i_dirpath = PQfnumber(res, "dirpath");
+ i_rolname = PQfnumber(res, "rolname");
+ i_diracl = PQfnumber(res, "diracl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ dirinfo[i].dobj.objType = DO_DIRALIAS;
+ dirinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ dirinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&dirinfo[i].dobj);
+ dirinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_diralias));
+ dirinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ dirinfo[i].dirpath = pg_strdup(PQgetvalue(res, i, i_dirpath));
+ dirinfo[i].diracl = pg_strdup(PQgetvalue(res, i, i_diracl));
+ }
+}
+
+static void
+dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo)
+{
+ PQExpBuffer create_query;
+ PQExpBuffer delete_query;
+ char *diralias;
+
+ if (!dirinfo->dobj.dump || dopt->dataOnly)
+ return;
+
+ create_query = createPQExpBuffer();
+ delete_query = createPQExpBuffer();
+
+ diralias = pg_strdup(fmtId(dirinfo->dobj.name));
+
+ appendPQExpBuffer(delete_query, "DROP DIRALIAS %s;\n", diralias);
+
+ appendPQExpBuffer(create_query, "CREATE DIRALIAS %s AS \'%s\';\n",
+ diralias, dirinfo->dirpath);
+
+ ArchiveEntry(fout, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ dirinfo->dobj.name,
+ NULL, NULL,
+ dirinfo->rolname, false,
+ "DIRALIAS", SECTION_POST_DATA,
+ create_query->data, delete_query->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump ACL - because of the special GRANT syntax, we cannot use dumpACL */
+ if (dirinfo->diracl)
+ dumpACL(fout, dopt, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ "DIRALIAS", diralias, NULL, dirinfo->dobj.name,
+ NULL, dirinfo->rolname,
+ dirinfo->diracl);
+
+ destroyPQExpBuffer(create_query);
+ destroyPQExpBuffer(delete_query);
+}
+
static void
binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
PQExpBuffer upgrade_buffer,
@@ -8214,6 +8308,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
case DO_ROW_SECURITY:
dumpRowSecurity(fout, dopt, (RowSecurityInfo *) dobj);
break;
+ case DO_DIRALIAS:
+ dumpDirectoryAlias(fout, dopt, (DirectoryAliasInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
@@ -15617,6 +15714,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_EVENT_TRIGGER:
case DO_DEFAULT_ACL:
case DO_ROW_SECURITY:
+ case DO_DIRALIAS:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 10ae87a..67ef824 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -76,7 +76,8 @@ typedef enum
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
DO_REFRESH_MATVIEW,
- DO_ROW_SECURITY
+ DO_ROW_SECURITY,
+ DO_DIRALIAS
} DumpableObjectType;
typedef struct _dumpableObject
@@ -469,6 +470,15 @@ typedef struct _rowSecurityInfo
char *rsecwithcheck;
} RowSecurityInfo;
+typedef struct _directoryAliasInfo
+{
+ DumpableObject dobj;
+ char *diralias;
+ char *diracl;
+ char *dirpath;
+ char *rolname; /* name of owner */
+} DirectoryAliasInfo;
+
/* global decls */
extern bool force_quotes; /* double-quotes for identifiers flag */
extern bool g_verbose; /* verbose flag */
@@ -550,5 +560,6 @@ extern void getExtensionMembership(Archive *fout, DumpOptions *dopt, ExtensionIn
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getDirectoryAliases(Archive *fout, int *numDirectoryAliases);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 030bccc..5cece21 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -73,7 +73,8 @@ static const int oldObjectTypePriority[] =
13, /* DO_POST_DATA_BOUNDARY */
20, /* DO_EVENT_TRIGGER */
15, /* DO_REFRESH_MATVIEW */
- 21 /* DO_ROW_SECURITY */
+ 21, /* DO_ROW_SECURITY */
+ 22, /* DO_DIRALIAS */
};
/*
@@ -122,7 +123,8 @@ static const int newObjectTypePriority[] =
25, /* DO_POST_DATA_BOUNDARY */
32, /* DO_EVENT_TRIGGER */
33, /* DO_REFRESH_MATVIEW */
- 34 /* DO_ROW_SECURITY */
+ 34, /* DO_ROW_SECURITY */
+ 35, /* DO_DIRALIAS */
};
static DumpId preDataBoundId;
@@ -1443,6 +1445,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"ROW-SECURITY POLICY (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_DIRALIAS:
+ snprintf(buf, bufsize,
+ "DIRECTORY ALIAS (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_PRE_DATA_BOUNDARY:
snprintf(buf, bufsize,
"PRE-DATA BOUNDARY (ID %d)",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 6a4913a..1779bc0 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -148,6 +148,7 @@ typedef enum ObjectClass
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_ROWSECURITY, /* pg_rowsecurity */
+ OCLASS_DIRALIAS, /* pg_diralias */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 870692c..e8a195d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
+DECLARE_UNIQUE_INDEX(pg_directory_oid_index, 6101, on pg_diralias using btree(oid oid_ops));
+#define DirAliasOidIndexId 6101
+
+DECLARE_UNIQUE_INDEX(pg_directory_name_index, 6102, on pg_diralias using btree(dirname name_ops));
+#define DirAliasNameIndexId 6102
+
DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops));
#define RowSecurityOidIndexId 3257
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..6b293aa 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -278,6 +278,7 @@ typedef enum NodeTag
T_AlterDomainStmt,
T_SetOperationStmt,
T_GrantStmt,
+ T_GrantDirAliasStmt,
T_GrantRoleStmt,
T_AlterDefaultPrivilegesStmt,
T_ClosePortalStmt,
@@ -368,6 +369,8 @@ typedef enum NodeTag
T_AlterSystemStmt,
T_CreatePolicyStmt,
T_AlterPolicyStmt,
+ T_CreateDirAliasStmt,
+ T_AlterDirAliasStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cef9544..4b7197c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1212,6 +1212,7 @@ typedef enum ObjectType
OBJECT_COLLATION,
OBJECT_CONVERSION,
OBJECT_DATABASE,
+ OBJECT_DIRALIAS,
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
@@ -1412,6 +1413,7 @@ typedef enum GrantObjectType
ACL_OBJECT_LARGEOBJECT, /* largeobject */
ACL_OBJECT_NAMESPACE, /* namespace */
ACL_OBJECT_TABLESPACE, /* tablespace */
+ ACL_OBJECT_DIRALIAS, /* directory alias */
ACL_OBJECT_TYPE /* type */
} GrantObjectType;
@@ -1483,6 +1485,20 @@ typedef struct GrantRoleStmt
} GrantRoleStmt;
/* ----------------------
+ * Grant/Revoke Directory Permission Statement
+ * ----------------------
+ */
+typedef struct GrantDirAliasStmt
+{
+ NodeTag type;
+ List *directories; /* the directory alias/name */
+ List *permissions; /* list of permission to be granted/revoked */
+ List *grantees; /* list of roles to be granted/revoked permission */
+ bool is_grant; /* true = GRANT, false = REVOKE */
+ char *grantor; /* set grantor to other than current role */
+} GrantDirAliasStmt;
+
+/* ----------------------
* Alter Default Privileges Statement
* ----------------------
*/
@@ -1890,6 +1906,28 @@ typedef struct AlterPolicyStmt
} AlterPolicyStmt;
/* ----------------------
+ * Create DIRALIAS Statement
+ * ----------------------
+ */
+typedef struct CreateDirAliasStmt
+{
+ NodeTag type;
+ char *name;
+ char *path;
+} CreateDirAliasStmt;
+
+/* ----------------------
+ * Alter DIRALIAS Statement
+ * ----------------------
+ */
+typedef struct AlterDirAliasStmt
+{
+ NodeTag type;
+ char *name;
+ char *path;
+} AlterDirAliasStmt;
+
+/* ----------------------
* Create TRIGGER Statement
* ----------------------
*/
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index e14dc9a..eb31cf7 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -125,6 +125,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
+PG_KEYWORD("diralias", DIRALIAS, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index a8e3164..cb75b1a 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -156,6 +156,7 @@ typedef ArrayType Acl;
#define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_DIRALIAS (ACL_SELECT|ACL_UPDATE)
/* operation codes for pg_*_aclmask */
typedef enum
@@ -197,6 +198,7 @@ typedef enum AclObjectKind
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_DIRALIAS, /* pg_diralias */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
@@ -255,6 +257,7 @@ extern Datum aclexplode(PG_FUNCTION_ARGS);
*/
extern void ExecuteGrantStmt(GrantStmt *stmt);
extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt);
+extern void ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt);
extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid);
extern void RemoveDefaultACLById(Oid defaclOid);
@@ -281,6 +284,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
AclMode mask, AclMaskHow how);
extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
AclMode mask, AclMaskHow how);
+extern AclMode pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask,
+ AclMaskHow how);
extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
Oid roleid, AclMode mode);
@@ -297,6 +302,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+extern AclResult pg_diralias_aclcheck(Oid diroid, Oid roleid, AclMode mode);
extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
const char *objectname);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..c4b822e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -54,6 +54,7 @@ enum SysCacheIdentifier
CONVOID,
DATABASEOID,
DEFACLROLENSPOBJ,
+ DIRALIASOID,
ENUMOID,
ENUMTYPOIDNAME,
EVENTTRIGGERNAME,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 2c8ec11..8cc083e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -102,6 +102,7 @@ pg_db_role_setting|t
pg_default_acl|t
pg_depend|t
pg_description|t
+pg_directory|t
pg_enum|t
pg_event_trigger|t
pg_extension|t
"Brightwell, Adam" <adam.brightwell@crunchydatasolutions.com> writes:
The attached patch for review implements a directory permission system that
allows for providing a directory read/write capability to directories for
COPY TO/FROM and Generic File Access Functions to non-superusers.
TBH, this sounds like it's adding a lot of mechanism and *significant*
risk of unforeseen security issues in order to solve a problem that we
do not need to solve. The field demand for such a feature is just about
indistinguishable from zero.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 15, 2014 at 11:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Brightwell, Adam" <adam.brightwell@crunchydatasolutions.com> writes:
The attached patch for review implements a directory permission system that
allows for providing a directory read/write capability to directories for
COPY TO/FROM and Generic File Access Functions to non-superusers.TBH, this sounds like it's adding a lot of mechanism and *significant*
risk of unforeseen security issues in order to solve a problem that we
do not need to solve. The field demand for such a feature is just about
indistinguishable from zero.
I am also not convinced that we need this. If we need to allow
non-superusers COPY permission at all, can we just exclude certain
"unsafe" directories (like the data directory, and tablespaces) and
let them access anything else? Or can we have a whitelist of
directories stored as a PGC_SUSER GUC? This seems awfully heavyweight
for what it is.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Robert Haas (robertmhaas@gmail.com) wrote:
On Wed, Oct 15, 2014 at 11:34 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Brightwell, Adam" <adam.brightwell@crunchydatasolutions.com> writes:
The attached patch for review implements a directory permission system that
allows for providing a directory read/write capability to directories for
COPY TO/FROM and Generic File Access Functions to non-superusers.TBH, this sounds like it's adding a lot of mechanism and *significant*
risk of unforeseen security issues in order to solve a problem that we
do not need to solve. The field demand for such a feature is just about
indistinguishable from zero.I am also not convinced that we need this. If we need to allow
non-superusers COPY permission at all, can we just exclude certain
"unsafe" directories (like the data directory, and tablespaces) and
let them access anything else?
Wow.. I'd say 'no' to this, certainly. Granularity is required here.
I want to give a non-superuser the ability to slurp data off a specific
NFS mount, not read /etc/passwd..
Or can we have a whitelist of
directories stored as a PGC_SUSER GUC? This seems awfully heavyweight
for what it is.
Hrm, perhaps this would work though..
Allow me to outline a few use-cases which I see for this though and
perhaps that'll help us make progress.
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server. Now, things can
certainly be set up on the server to import *all* logs and then grant
access to a non-superuser, but generally it's "I need to review the log
from X to Y" and not *all* logs need to be stored or kept in PG.
In years past, I've wanted to be able to grant this ability out for
users to do loads without having to transfer the data through the user's
laptop or get them to log onto the Linux box from their Windows desktop
and pull the data in via psql (it's a bigger deal than some might
think..), and then there's the general ETL case where, without this, you
end up running something like Pentaho and having to pass all the data
through Java to get it into the database.
Building on that is the concept of *background* loads, with
pg_background. That's a killer capability, in my view. "Hey, PG, go
load all the files in this directory into this table, but don't make me
have to stick around and make sure my laptop is still connected for the
next 3 hours."
Next, the file_fdw could leverage this catalog to do its own checks and
allow non-superusers to use it, which would be fantastic and gets back
to the 'log file' use-case above.
And then there is the next-level item: CREATE TABLESPACE, which we
already see folks like RDS and others having to hack the codebase to
add as a non-superuser capability. It'd need to be an independently
grantable capability, of course.
Thanks!
Stephen
On Thu, Oct 16, 2014 at 12:01:28PM -0400, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server. Now, things can
certainly be set up on the server to import *all* logs and then grant
access to a non-superuser, but generally it's "I need to review the log
from X to Y" and not *all* logs need to be stored or kept in PG.
Why is this patch showing up before being discussed? You are having to
back into the discusion because of this.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ Everyone has their own god. +
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Bruce Momjian (bruce@momjian.us) wrote:
On Thu, Oct 16, 2014 at 12:01:28PM -0400, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server. Now, things can
certainly be set up on the server to import *all* logs and then grant
access to a non-superuser, but generally it's "I need to review the log
from X to Y" and not *all* logs need to be stored or kept in PG.Why is this patch showing up before being discussed? You are having to
back into the discusion because of this.
For my part, I didn't actually see it as being a questionable use-case
from the start.. That was obviously incorrect, though I didn't know
that previously. The general idea has been discussed a couple of times
before, at least as far back as 2005:
/messages/by-id/430F78E0.9020206@cs.concordia.ca
It's also a feature available in other databases (at least MySQL and
Oracle, but I'm pretty sure others also).
I can also recall chatting with folks about it a couple of times over
the years at various conferences. Still, perhaps it would have been
better to post about the idea before the patch, but hindsight is often
20/20.
Thanks!
Stephen
On 10/16/14 12:01 PM, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server.
I think that can be done with a security-definer function.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
You patch is missing the files src/include/catalog/pg_diralias.h,
src/include/commands/diralias.h, and src/backend/commands/diralias.c.
(Hint: git add -N)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter,
You patch is missing the files src/include/catalog/pg_diralias.h,
src/include/commands/diralias.h, and src/backend/commands/diralias.c.
(Hint: git add -N)
Yikes, sorry about that, not sure how that happened. Attached is an
updated patch.
-Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
Attachments:
directory-alias-v2.patchtext/x-patch; charset=US-ASCII; name=directory-alias-v2.patchDownload
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index b257b02..8cdc5cb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
- toasting.h indexing.h \
+ pg_diralias.h toasting.h indexing.h \
)
# location of Catalog.pm
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d30612c..3717bf5 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -30,6 +30,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
@@ -48,6 +49,7 @@
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "commands/dbcommands.h"
+#include "commands/diralias.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "foreign/foreign.h"
@@ -3183,6 +3185,190 @@ ExecGrant_Type(InternalGrant *istmt)
heap_close(relation, RowExclusiveLock);
}
+/*
+ * ExecuteGrantDirAliasStmt
+ * handles the execution of the GRANT/REVOKE ON DIRALIAS command.
+ *
+ * stmt - the GrantDirAliasStmt that describes the directory aliases and
+ * permissions to be granted/revoked.
+ */
+void
+ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt)
+{
+ Relation pg_diralias_rel;
+ Oid grantor;
+ List *grantee_ids = NIL;
+ AclMode permissions;
+ ListCell *item;
+
+ /* Must be superuser to grant directory alias permissions */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to grant directory alias permissions")));
+
+ /*
+ * Grantor is optional. If it is not provided then set it to the current
+ * user.
+ */
+ if (stmt->grantor)
+ grantor = get_role_oid(stmt->grantor, false);
+ else
+ grantor = GetUserId();
+
+ /* Convert grantee names to oids */
+ foreach(item, stmt->grantees)
+ {
+ PrivGrantee *grantee = (PrivGrantee *) lfirst(item);
+
+ if (grantee->rolname == NULL)
+ grantee_ids = lappend_oid(grantee_ids, ACL_ID_PUBLIC);
+ else
+ {
+ Oid roleid = get_role_oid(grantee->rolname, false);
+ grantee_ids = lappend_oid(grantee_ids, roleid);
+ }
+ }
+
+ permissions = ACL_NO_RIGHTS;
+
+ /* If ALL was provided then set permissions to ACL_ALL_RIGHTS_DIRALIAS */
+ if (stmt->permissions == NIL)
+ permissions = ACL_ALL_RIGHTS_DIRALIAS;
+ else
+ {
+ /* Condense all permissions */
+ foreach(item, stmt->permissions)
+ {
+ AccessPriv *priv = (AccessPriv *) lfirst(item);
+ permissions |= string_to_privilege(priv->priv_name);
+ }
+ }
+
+
+ /*
+ * Though it shouldn't be possible to provide permissions other than READ
+ * and WRITE, check to make sure no others have been set. If they have,
+ * then warn the user and correct the permissions.
+ */
+ if (permissions & !((AclMode) ACL_ALL_RIGHTS_DIRALIAS))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("directory aliases only support READ and WRITE permissions")));
+
+ permissions &= ACL_ALL_RIGHTS_DIRALIAS;
+ }
+
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /* Grant/Revoke permissions on directory aliases */
+ foreach(item, stmt->directories)
+ {
+ Datum values[Natts_pg_diralias];
+ bool replaces[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ HeapTuple new_tuple;
+ Datum datum;
+ Oid owner_id;
+ Acl *dir_acl;
+ Acl *new_acl;
+ bool is_null;
+ int num_old_members;
+ int num_new_members;
+ Oid *old_members;
+ Oid *new_members;
+ Oid diralias_id;
+ char *name;
+
+ name = strVal(lfirst(item));
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(name));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for directory alias \"%s\"", name);
+
+ /*
+ * Get directory alias owner id. Since all superusers are considered
+ * to be owners of a directory alias, it is safe to assume that the
+ * current user is an owner, given the superuser check above.
+ */
+ owner_id = GetUserId();
+
+ /* Get directory alias ACL */
+ datum = heap_getattr(tuple, Anum_pg_diralias_diracl,
+ RelationGetDescr(pg_diralias_rel), &is_null);
+
+ /* Get the directory alias oid */
+ diralias_id = HeapTupleGetOid(tuple);
+
+ /*
+ * If there are currently no permissions granted on the directory alias,
+ * then add default permissions, which should include the permssions
+ * granted to the owner of the table. Directory aliases are owned by
+ * all superusers.
+ */
+ if (is_null)
+ {
+ dir_acl = acldefault(ACL_OBJECT_DIRALIAS, owner_id);
+ num_old_members = 0;
+ old_members = NULL;
+ }
+ else
+ {
+ dir_acl = DatumGetAclPCopy(datum);
+
+ /* Get the roles in the current ACL */
+ num_old_members = aclmembers(dir_acl, &old_members);
+ }
+
+ /* Merge new ACL with current ACL */
+ new_acl = merge_acl_with_grant(dir_acl, stmt->is_grant, false,
+ DROP_CASCADE, grantee_ids, permissions,
+ grantor, owner_id);
+
+ num_new_members = aclmembers(new_acl, &new_members);
+
+ /* Insert new ACL value */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(replaces, 0, sizeof(replaces));
+
+ values[Anum_pg_diralias_diracl - 1] = PointerGetDatum(new_acl);
+ replaces[Anum_pg_diralias_diracl - 1] = true;
+
+ new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel),
+ values, nulls, replaces);
+
+ simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, new_tuple);
+
+ /* Update shared dependency ACL information */
+ updateAclDependencies(DirAliasRelationId, diralias_id, 0,
+ owner_id,
+ num_old_members, old_members,
+ num_new_members, new_members);
+
+ /* Clean Up */
+ pfree(new_acl);
+ heap_endscan(scandesc);
+ }
+
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+}
+
static AclMode
string_to_privilege(const char *privname)
@@ -3307,6 +3493,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
gettext_noop("permission denied for event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("permission denied for extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("permission denied for directory alias %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
@@ -3353,6 +3541,8 @@ static const char *const not_owner_msg[MAX_ACL_KIND] =
gettext_noop("must be owner of event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("must be owner of extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("must be owner of directory alias %s"),
};
@@ -4194,6 +4384,62 @@ pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
}
/*
+ * Exported routine for examining a user's permissions for a directory alias.
+ */
+AclMode
+pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask, AclMaskHow how)
+{
+ AclMode result;
+ HeapTuple tuple;
+ Datum aclDatum;
+ bool isNull;
+ Acl *acl;
+
+ /* Bypass permission checks for superusers */
+ if (superuser_arg(roleid))
+ return mask;
+
+ /* Must get the directory alias's tuple from pg_diralias */
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(dir_oid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias with OID %u does not exist", dir_oid)));
+
+ aclDatum = SysCacheGetAttr(DIRALIASOID, tuple,
+ Anum_pg_diralias_diracl, &isNull);
+ if (isNull)
+ {
+ /* No ACL, so build default ACL */
+ acl = acldefault(ACL_OBJECT_DIRALIAS, roleid);
+ aclDatum = (Datum) 0;
+ }
+ else
+ {
+ /* detoast rel's ACL if necessary */
+ acl = DatumGetAclP(aclDatum);
+ }
+
+ /*
+ * We use InvalidOid as the ownerid for determining the aclmask. This is
+ * because directory aliases belong to all superusers. aclmask() uses the
+ * ownerid to determine grant options by implying that owners always have
+ * all grant options. If roleid, is not a superuser and therefore an owner
+ * (which it couldn't be at this point), then this check in aclmask() must
+ * be false. Therefore, by using InvalidOid we are guaranteed this behavior.
+ */
+ result = aclmask(acl, roleid, InvalidOid, mask, how);
+
+ /* if we have a detoasted copy, free it */
+ if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+ pfree(acl);
+
+ ReleaseSysCache(tuple);
+
+ return result;
+}
+
+/*
* Exported routine for examining a user's privileges for a type.
*/
AclMode
@@ -4513,6 +4759,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode)
}
/*
+ * Exported routine for checking a user's access permissions to a directory alias
+ */
+AclResult
+pg_diralias_aclcheck(Oid dir_oid, Oid roleid, AclMode mode)
+{
+ if (pg_diralias_aclmask(dir_oid, roleid, mode, ACLMASK_ANY) != 0)
+ return ACLCHECK_OK;
+ else
+ return ACLCHECK_NO_PRIV;
+}
+
+/*
* Ownership check for a relation (specified by OID).
*/
bool
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 256486c..b056559 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -33,6 +33,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
@@ -56,6 +57,7 @@
#include "catalog/pg_user_mapping.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -1255,6 +1257,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_DIRALIAS:
+ RemoveDirAliasById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2325,6 +2331,9 @@ getObjectClass(const ObjectAddress *object)
case RowSecurityRelationId:
return OCLASS_ROWSECURITY;
+
+ case DirAliasRelationId:
+ return OCLASS_DIRALIAS;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..872d233 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -26,6 +26,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_default_acl.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -54,6 +55,7 @@
#include "catalog/pg_user_mapping.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -358,6 +360,18 @@ static const ObjectPropertyType ObjectProperty[] =
false
},
{
+ DirAliasRelationId,
+ DirAliasOidIndexId,
+ DIRALIASOID,
+ -1,
+ Anum_pg_diralias_dirname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ -1,
+ false
+ },
+ {
EventTriggerRelationId,
EventTriggerOidIndexId,
EVENTTRIGGEROID,
@@ -536,6 +550,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
&relation, missing_ok);
break;
case OBJECT_DATABASE:
+ case OBJECT_DIRALIAS:
case OBJECT_EXTENSION:
case OBJECT_TABLESPACE:
case OBJECT_ROLE:
@@ -746,6 +761,9 @@ get_object_address_unqualified(ObjectType objtype,
case OBJECT_DATABASE:
msg = gettext_noop("database name cannot be qualified");
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias cannot be qualified");
+ break;
case OBJECT_EXTENSION:
msg = gettext_noop("extension name cannot be qualified");
break;
@@ -790,6 +808,11 @@ get_object_address_unqualified(ObjectType objtype,
address.objectId = get_database_oid(name, missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_DIRALIAS:
+ address.classId = DirAliasRelationId;
+ address.objectId = get_diralias_oid(name, missing_ok);
+ address.objectSubId = 0;
+ break;
case OBJECT_EXTENSION:
address.classId = ExtensionRelationId;
address.objectId = get_extension_oid(name, missing_ok);
@@ -1318,6 +1341,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_DIRALIAS:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
@@ -2224,6 +2248,18 @@ getObjectDescription(const ObjectAddress *object)
break;
}
+ case OCLASS_DIRALIAS:
+ {
+ char *diralias_name;
+
+ diralias_name = get_diralias_name(object->objectId);
+ if (!diralias_name)
+ elog(ERROR, "cache lookup failed for directory alias %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("directory alias %s"), diralias_name);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index b1ac704..36a897c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
- dbcommands.o define.o discard.o dropcmds.o \
+ dbcommands.o define.o diralias.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
policy.o portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c9a9baf..47f8d49 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -41,6 +41,7 @@
#include "commands/conversioncmds.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
@@ -349,6 +350,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_EVENT_TRIGGER:
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6b83576..3a9562b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -28,6 +28,7 @@
#include "catalog/pg_type.h"
#include "commands/copy.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
@@ -788,9 +789,13 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
Oid relid;
Node *query = NULL;
- /* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
{
+ /*
+ * Disallow COPY to/from program except to superusers. If COPY is to/from
+ * a file then diallow unless the current user is either superuser or has
+ * been granted the appropriate permissions on the target parent directory.
+ */
if (stmt->is_program)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -798,11 +803,43 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
else
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to COPY to or from a file"),
- errhint("Anyone can COPY to stdout or from stdin. "
- "psql's \\copy command also works for anyone.")));
+ {
+ char *path;
+ Oid diralias_id;
+ AclResult aclresult;
+
+ /* Get the parent directory */
+ path = pstrdup(stmt->filename);
+ canonicalize_path(path);
+ get_parent_directory(path);
+
+ /* Search for directory in pg_diralias */
+ diralias_id = get_diralias_oid_by_path(path);
+
+ /*
+ * If an entry does not exist for the path in pg_diralias then raise
+ * an error.
+ */
+ if (!OidIsValid(diralias_id))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("a directory alias entry for \"%s\" does not exist.",
+ path)));
+
+ /* Check directory alias entry permissions */
+ if (stmt->is_from)
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
+ else
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_UPDATE);
+
+ /* If the current user has insufficient privileges then raise an error. */
+ if (aclresult != ACLCHECK_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have permissions to COPY to or from \"%s\"", path),
+ errhint("Anyone can COPY to stdout or from stdin. "
+ "psql's \\copy command also works for anyone.")));
+ }
}
if (stmt->relation)
diff --git a/src/backend/commands/diralias.c b/src/backend/commands/diralias.c
new file mode 100644
index 0000000..f269624
--- /dev/null
+++ b/src/backend/commands/diralias.c
@@ -0,0 +1,375 @@
+/*-------------------------------------------------------------------------
+ *
+ * directory.c
+ * Commands for manipulating directories.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/directory.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_diralias.h"
+#include "commands/diralias.h"
+#include "commands/user.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * RemoveDirAliasById
+ * remove a directory alias by its OID. If a directory does not exist with
+ * the provided oid, then an error is raised.
+ *
+ * diralias_id - the oid of the directory alias.
+ */
+void
+RemoveDirAliasById(Oid diralias_id)
+{
+ Relation pg_diralias_rel;
+ HeapTuple tuple;
+
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /*
+ * Find the directory alias to delete.
+ */
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id));
+
+ /* If the directory alias exists, then remove it, otherwise raise an error. */
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for directory alias %u", diralias_id);
+
+ simple_heap_delete(pg_diralias_rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+}
+
+/*
+ * CreateDirAlias
+ * handles the execution of the CREATE DIRALIAS command.
+ *
+ * stmt - the CreateDirAliasStmt that describes the directory alias entry to
+ * create.
+ */
+void
+CreateDirAlias(CreateDirAliasStmt *stmt)
+{
+ Relation pg_diralias_rel;
+ Datum values[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ Oid diralias_id;
+ char *path;
+
+ /* Must be superuser to create a directory alias entry. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create directory alias")));
+
+ /* Unix-ify the path, and strip any trailing slashes */
+ path = pstrdup(stmt->path);
+ canonicalize_path(path);
+
+ /* Disallow quotes */
+ if (strchr(path, '\''))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("directory path cannot contain single quotes")));
+
+ /*
+ * Allowing relative paths seems risky and really a bad idea. Therefore,
+ * if a relative path is provided then an error is raised.
+ *
+ * This also helps us ensure that directory path is not empty or whitespace.
+ */
+ if (!is_absolute_path(path))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("directory path must be an absolute path")));
+
+ /* Open pg_diralias catalog */
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /*
+ * Make sure a duplicate does not already exist. Need to check both the name
+ * and the path. If either exists, then raise an error.
+ */
+
+ /* Check alias name does not already exist */
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->name));
+
+ /*
+ * We use a heapscan here even though there is an index on alias and path.
+ * We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("directory alias \"%s\" already exists", stmt->name)));
+
+ heap_endscan(scandesc);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirpath,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(path));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ /* Check that path does not already exist. */
+ if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("directory alias with path \"%s\" already exists", path)));
+
+ heap_endscan(scandesc);
+
+ /*
+ * All is well and safe to insert.
+ */
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_diralias_dirname - 1] = CStringGetDatum(stmt->name);
+ values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path);
+
+ /* No ACL items are set on the directory by default */
+ nulls[Anum_pg_diralias_diracl - 1] = true;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_diralias_rel), values, nulls);
+
+ diralias_id = simple_heap_insert(pg_diralias_rel, tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, tuple);
+
+ /* Post creation hook for new directory alias */
+ InvokeObjectPostCreateHook(DirAliasRelationId, diralias_id, 0);
+
+ /* Clean up */
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+}
+
+/*
+ * AlterDirAlias
+ * handles the execution of the ALTER DIRALIAS command.
+ *
+ * stmt - the AlterDirAliasStmt that describes the directory alias entry to alter.
+ */
+void
+AlterDirAlias(AlterDirAliasStmt *stmt)
+{
+ Relation pg_diralias_rel;
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ Datum values[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ bool replaces[Natts_pg_diralias];
+ HeapTuple new_tuple;
+ char *path;
+
+ /* Must be superuser to alter directory alias */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter directory alias")));
+
+ /* Unix-ify the new path, and strip any trailing slashes */
+ path = pstrdup(stmt->path);
+ canonicalize_path(path);
+
+ /* Disallow quotes */
+ if (strchr(path, '\''))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("directory path cannot contain single quotes")));
+
+ /* Open pg_diralias catalog */
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /* Search for directory alias by name */
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->name));
+
+ /*
+ * We use a heapscan here even though there is an index on alias and path.
+ * We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ /* If directory alias does not exist then raise an error */
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias \"%s\" does not exist", stmt->name)));
+
+ /* Build new tuple and update pg_diralias */
+ memset(nulls, 0, sizeof(nulls));
+ memset(replaces, 0, sizeof(replaces));
+ memset(values, 0, sizeof(values));
+
+ values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path);
+ replaces[Anum_pg_diralias_dirpath - 1] = true;
+
+ new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel),
+ values, nulls, replaces);
+
+ simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, new_tuple);
+
+ /* Post alter hook for directory alias */
+ InvokeObjectPostAlterHook(DirAliasRelationId, HeapTupleGetOid(tuple), 0);
+
+ /* Clean Up */
+ heap_freetuple(new_tuple);
+ heap_endscan(scandesc);
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+}
+
+/*
+ * get_diralias_name
+ * given a directory alias OID, look up the name. If the directory does not
+ * exist then NULL is returned.
+ *
+ * diralias_id - the OID of the directory alias entry in pg_diralias.
+ */
+char *
+get_diralias_name(Oid diralias_id)
+{
+ char *name = NULL;
+ HeapTuple tuple;
+
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id));
+ if (HeapTupleIsValid(tuple))
+ {
+ name = pstrdup(NameStr(((Form_pg_diralias) GETSTRUCT(tuple))->dirname));
+ ReleaseSysCache(tuple);
+ }
+
+ return name;
+}
+
+/*
+ * get_directory_oid_by_path
+ * given a directory path, look up the OID. If the directory does not exist
+ * this InvalidOid is returned.
+ *
+ * path - the path of the directory
+ */
+Oid
+get_diralias_oid_by_path(const char *path)
+{
+ Oid dir_id = InvalidOid;
+ Relation pg_diralias_rel;
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData skey[1];
+
+ /*
+ * Search pg_diralias. We use a heapscan here even though there is an index
+ * on alias. We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirpath,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(path));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ if (HeapTupleIsValid(tuple))
+ dir_id = HeapTupleGetOid(tuple);
+
+ heap_endscan(scandesc);
+ heap_close(pg_diralias_rel, AccessShareLock);
+
+ return dir_id;
+}
+
+/*
+ * get_directory_oid
+ * given a directory alias name, look up the OID. If a directory alias does
+ * not exist for the given name then raise an error. However, if missing_ok
+ * is true, then return InvalidOid.
+ *
+ * name - the name of the directory alias
+ * missing_ok - false if an error should be raised if the directory alias does
+ * not exist.
+ */
+Oid
+get_diralias_oid(const char *name, bool missing_ok)
+{
+ Oid dir_id;
+ Relation pg_diralias_rel;
+ ScanKeyData skey[1];
+ SysScanDesc sscan;
+ HeapTuple tuple;
+
+ /* Search pg_diralias for a directory alias entry with provided name */
+ pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(name));
+
+ sscan = systable_beginscan(pg_diralias_rel, DirAliasNameIndexId,
+ true, NULL, 1, skey);
+
+ tuple = systable_getnext(sscan);
+
+ if (HeapTupleIsValid(tuple))
+ dir_id = HeapTupleGetOid(tuple);
+ else
+ dir_id = InvalidOid;
+
+ systable_endscan(sscan);
+ heap_close(pg_diralias_rel, AccessShareLock);
+
+ if (!OidIsValid(dir_id) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias \"%s\" does not exist", name)));
+
+ return dir_id;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 8583581..8cae12e 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -380,6 +380,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
list_length(objname) - 1));
}
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias \"%s\" does not exist, skipping");
+ name = NameListToString(objname);
+ break;
case OBJECT_EVENT_TRIGGER:
msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
name = NameListToString(objname);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1b8c94b..b70c322 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -73,6 +73,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"COLLATION", true},
{"CONVERSION", true},
{"DATABASE", false},
+ {"DIRALIAS", true},
{"DOMAIN", true},
{"EXTENSION", true},
{"EVENT TRIGGER", false},
@@ -924,6 +925,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_CONSTRAINT:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_DOMAIN:
case OBJECT_EXTENSION:
case OBJECT_FDW:
@@ -998,6 +1000,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_ROWSECURITY:
+ case OCLASS_DIRALIAS:
return true;
case MAX_OCLASS:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 21b070a..8941fa2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2702,6 +2702,20 @@ _copyGrantRoleStmt(const GrantRoleStmt *from)
return newnode;
}
+static GrantDirAliasStmt *
+_copyGrantDirAliasStmt(const GrantDirAliasStmt *from)
+{
+ GrantDirAliasStmt *newnode = makeNode(GrantDirAliasStmt);
+
+ COPY_NODE_FIELD(directories);
+ COPY_NODE_FIELD(permissions);
+ COPY_NODE_FIELD(grantees);
+ COPY_SCALAR_FIELD(is_grant);
+ COPY_STRING_FIELD(grantor);
+
+ return newnode;
+}
+
static AlterDefaultPrivilegesStmt *
_copyAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *from)
{
@@ -3879,6 +3893,28 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static CreateDirAliasStmt *
+_copyCreateDirAliasStmt(const CreateDirAliasStmt *from)
+{
+ CreateDirAliasStmt *newnode = makeNode(CreateDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+}
+
+static AlterDirAliasStmt *
+_copyAlterDirAliasStmt(const AlterDirAliasStmt *from)
+{
+ AlterDirAliasStmt *newnode = makeNode(AlterDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -4318,6 +4354,9 @@ copyObject(const void *from)
case T_GrantStmt:
retval = _copyGrantStmt(from);
break;
+ case T_GrantDirAliasStmt:
+ retval = _copyGrantDirAliasStmt(from);
+ break;
case T_GrantRoleStmt:
retval = _copyGrantRoleStmt(from);
break;
@@ -4597,6 +4636,12 @@ copyObject(const void *from)
case T_AlterPolicyStmt:
retval = _copyAlterPolicyStmt(from);
break;
+ case T_CreateDirAliasStmt:
+ retval = _copyCreateDirAliasStmt(from);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _copyAlterDirAliasStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 358395f..e266ad8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1046,6 +1046,18 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b)
}
static bool
+_equalGrantDirAliasStmt(const GrantDirAliasStmt *a, const GrantDirAliasStmt *b)
+{
+ COMPARE_NODE_FIELD(directories);
+ COMPARE_NODE_FIELD(permissions);
+ COMPARE_NODE_FIELD(grantees);
+ COMPARE_SCALAR_FIELD(is_grant);
+ COMPARE_STRING_FIELD(grantor);
+
+ return true;
+}
+
+static bool
_equalAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *a, const AlterDefaultPrivilegesStmt *b)
{
COMPARE_NODE_FIELD(options);
@@ -2034,6 +2046,24 @@ _equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
}
static bool
+_equalCreateDirAliasStmt(const CreateDirAliasStmt *a, const CreateDirAliasStmt *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+}
+
+static bool
+_equalAlterDirAliasStmt(const AlterDirAliasStmt *a, const AlterDirAliasStmt *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+}
+
+static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
COMPARE_SCALAR_FIELD(kind);
@@ -2778,6 +2808,9 @@ equal(const void *a, const void *b)
case T_GrantStmt:
retval = _equalGrantStmt(a, b);
break;
+ case T_GrantDirAliasStmt:
+ retval = _equalGrantDirAliasStmt(a, b);
+ break;
case T_GrantRoleStmt:
retval = _equalGrantRoleStmt(a, b);
break;
@@ -3057,6 +3090,12 @@ equal(const void *a, const void *b)
case T_AlterPolicyStmt:
retval = _equalAlterPolicyStmt(a, b);
break;
+ case T_CreateDirAliasStmt:
+ retval = _equalCreateDirAliasStmt(a, b);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _equalAlterDirAliasStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0de9584..24d5eb5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -258,7 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
- CreateMatViewStmt RefreshMatViewStmt
+ CreateMatViewStmt RefreshMatViewStmt GrantDirStmt RevokeDirStmt
+ CreateDirAliasStmt AlterDirAliasStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -324,6 +325,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole
+%type <node> dir_perm_opts
+%type <list> dir_permissions dir_perm_list
+
%type <str> iso_level opt_encoding
%type <node> grantee
%type <list> grantee_list
@@ -559,7 +563,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DICTIONARY DIRALIAS DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -734,6 +739,7 @@ stmt :
| AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
+ | AlterDirAliasStmt
| AlterDomainStmt
| AlterEnumStmt
| AlterExtensionStmt
@@ -769,6 +775,7 @@ stmt :
| CreateAssertStmt
| CreateCastStmt
| CreateConversionStmt
+ | CreateDirAliasStmt
| CreateDomainStmt
| CreateExtensionStmt
| CreateFdwStmt
@@ -820,6 +827,7 @@ stmt :
| ExplainStmt
| FetchStmt
| GrantStmt
+ | GrantDirStmt
| GrantRoleStmt
| ImportForeignSchemaStmt
| IndexStmt
@@ -837,6 +845,7 @@ stmt :
| RemoveOperStmt
| RenameStmt
| RevokeStmt
+ | RevokeDirStmt
| RevokeRoleStmt
| RuleStmt
| SecLabelStmt
@@ -4606,6 +4615,34 @@ row_security_cmd:
/*****************************************************************************
*
+ * QUERIES:
+ * CREATE DIRALIAS <name> AS <path>
+ * ALTER DIRALIAS <name> AS <path>
+ *
+ *****************************************************************************/
+
+CreateDirAliasStmt:
+ CREATE DIRALIAS name AS Sconst
+ {
+ CreateDirAliasStmt *n = makeNode(CreateDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+AlterDirAliasStmt:
+ ALTER DIRALIAS name AS Sconst
+ {
+ AlterDirAliasStmt *n = makeNode(AlterDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+/*****************************************************************************
+ *
* QUERIES :
* CREATE TRIGGER ...
* DROP TRIGGER ...
@@ -5481,6 +5518,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
| EXTENSION { $$ = OBJECT_EXTENSION; }
+ | DIRALIAS { $$ = OBJECT_DIRALIAS; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
@@ -6314,6 +6352,67 @@ opt_granted_by: GRANTED BY RoleId { $$ = $3; }
/*****************************************************************************
*
+ * QUERIES:
+ * GRANT ON DIRALIAS <alias> <permissions> TO <roles>
+ * REVOKE ON DIRALIAS <alias> <permsissions> FROM <roles>
+ *
+ *****************************************************************************/
+GrantDirStmt:
+ GRANT ON DIRALIAS name_list dir_permissions TO grantee_list
+ opt_granted_by
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = true;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ n->grantor = $8;
+ $$ = (Node*)n;
+ }
+ ;
+
+RevokeDirStmt:
+ REVOKE ON DIRALIAS name_list dir_permissions FROM grantee_list
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = false;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ $$ = (Node*)n;
+ }
+ ;
+
+/* either ALL or a list of individual permissions */
+dir_permissions: dir_perm_list
+ { $$ = $1; }
+ | ALL { $$ = NIL; }
+ ;
+
+
+dir_perm_list: dir_perm_opts { $$ = list_make1($1); }
+ | dir_perm_list ',' dir_perm_opts { $$ = lappend($1, $3); }
+ ;
+
+dir_perm_opts:
+ READ
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("select");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ | WRITE
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("update");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ ;
+
+/*****************************************************************************
+ *
* ALTER DEFAULT PRIVILEGES statement
*
*****************************************************************************/
@@ -7273,6 +7372,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER DIRALIAS name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_DIRALIAS;
+ n->object = list_make1(makeString($3));
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER DOMAIN_P any_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
@@ -13051,6 +13159,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DICTIONARY
+ | DIRALIAS
| DISABLE_P
| DISCARD
| DOCUMENT_P
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4a2a339..4980016 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -33,6 +33,7 @@
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+#include "commands/diralias.h"
#include "commands/discard.h"
#include "commands/event_trigger.h"
#include "commands/explain.h"
@@ -185,6 +186,7 @@ check_xact_readonly(Node *parsetree)
case T_DropRoleStmt:
case T_GrantStmt:
case T_GrantRoleStmt:
+ case T_GrantDirAliasStmt:
case T_AlterDefaultPrivilegesStmt:
case T_TruncateStmt:
case T_DropOwnedStmt:
@@ -557,6 +559,10 @@ standard_ProcessUtility(Node *parsetree,
GrantRole((GrantRoleStmt *) parsetree);
break;
+ case T_GrantDirAliasStmt:
+ ExecuteGrantDirAliasStmt((GrantDirAliasStmt *) parsetree);
+ break;
+
case T_CreatedbStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
@@ -1329,6 +1335,14 @@ ProcessUtilitySlow(Node *parsetree,
AlterPolicy((AlterPolicyStmt *) parsetree);
break;
+ case T_CreateDirAliasStmt: /* CREATE DIRALIAS */
+ CreateDirAlias((CreateDirAliasStmt *) parsetree);
+ break;
+
+ case T_AlterDirAliasStmt: /* ALTER DIRALIAS */
+ AlterDirAlias((AlterDirAliasStmt *) parsetree);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(parsetree));
@@ -1596,6 +1610,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
break;
+ case OBJECT_DIRALIAS:
+ tag = "ALTER DIRALIAS";
+ break;
case OBJECT_DOMAIN:
tag = "ALTER DOMAIN";
break;
@@ -1959,6 +1976,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
+ case OBJECT_DIRALIAS:
+ tag = "DROP DIRALIAS";
+ break;
default:
tag = "???";
}
@@ -2016,6 +2036,14 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_GrantDirAliasStmt:
+ {
+ GrantDirAliasStmt *stmt = (GrantDirAliasStmt *) parsetree;
+
+ tag = (stmt->is_grant ? "GRANT ON DIRALIAS" : "REVOKE ON DIRALIAS");
+ }
+ break;
+
case T_GrantRoleStmt:
{
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
@@ -2310,6 +2338,14 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER POLICY";
break;
+ case T_CreateDirAliasStmt:
+ tag = "CREATE DIRALIAS";
+ break;
+
+ case T_AlterDirAliasStmt:
+ tag = "ALTER DIRALIAS";
+ break;
+
case T_PrepareStmt:
tag = "PREPARE";
break;
@@ -2862,6 +2898,14 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_AlterDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterTSDictionaryStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index dc6eb2c..7cc00ef 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -790,6 +790,10 @@ acldefault(GrantObjectType objtype, Oid ownerId)
world_default = ACL_USAGE;
owner_default = ACL_ALL_RIGHTS_TYPE;
break;
+ case ACL_OBJECT_DIRALIAS:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_DIRALIAS;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3a0957f..58dd1dc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -22,11 +22,13 @@
#include "access/htup_details.h"
#include "catalog/pg_type.h"
+#include "commands/diralias.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
+#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -37,46 +39,44 @@ typedef struct
DIR *dirdesc;
} directory_fctx;
-
/*
- * Convert a "text" filename argument to C string, and check it's allowable.
+ * Check the directory permissions for the provided filename/path.
*
- * Filename may be absolute or relative to the DataDir, but we only allow
- * absolute paths that match DataDir or Log_directory.
+ * The filename must be an absolute path to the file.
*/
-static char *
-convert_and_check_filename(text *arg)
+static void
+check_directory_permissions(char *directory)
{
- char *filename;
+ Oid diralias_id;
+ AclResult aclresult;
- filename = text_to_cstring(arg);
- canonicalize_path(filename); /* filename can change length here */
+ /* Do not allow relative paths */
+ if (!is_absolute_path(directory))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("relative path not allowed")));
- if (is_absolute_path(filename))
- {
- /* Disallow '/a/b/data/..' */
- if (path_contains_parent_reference(filename))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("reference to parent directory (\"..\") not allowed"))));
-
- /*
- * Allow absolute paths if within DataDir or Log_directory, even
- * though Log_directory might be outside DataDir.
- */
- if (!path_is_prefix_of_path(DataDir, filename) &&
- (!is_absolute_path(Log_directory) ||
- !path_is_prefix_of_path(Log_directory, filename)))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("absolute path not allowed"))));
- }
- else if (!path_is_relative_and_below_cwd(filename))
+ /* Search for directory in pg_diralias */
+ diralias_id = get_diralias_oid_by_path(directory);
+
+ /*
+ * If an entry does not exist for the path in pg_diralias then raise
+ * an error.
+ */
+ if (!OidIsValid(diralias_id))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("path must be in or below the current directory"))));
+ errmsg("directory alias entry for \"%s\" does not exist",
+ directory)));
+
+ /* Check directory alias entry permissions */
+ aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
- return filename;
+ /* If the current user has insufficient privileges then raise an error */
+ if (aclresult != ACLCHECK_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must have read permissions on directory")));
}
@@ -173,13 +173,19 @@ pg_read_file(PG_FUNCTION_ARGS)
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (bytes_to_read < 0)
ereport(ERROR,
@@ -197,13 +203,19 @@ pg_read_file_all(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
}
@@ -218,13 +230,19 @@ pg_read_binary_file(PG_FUNCTION_ARGS)
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (bytes_to_read < 0)
ereport(ERROR,
@@ -242,13 +260,19 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to read files"))));
+ /* Convert and cleanup the filename */
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
}
@@ -261,18 +285,23 @@ pg_stat_file(PG_FUNCTION_ARGS)
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
struct stat fst;
Datum values[6];
bool isnull[6];
HeapTuple tuple;
TupleDesc tupdesc;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to get file information"))));
+ filename = text_to_cstring(filename_t);
+ canonicalize_path(filename);
- filename = convert_and_check_filename(filename_t);
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ {
+ directory = pstrdup(filename);
+ get_parent_directory(directory);
+ check_directory_permissions(directory);
+ }
if (stat(filename, &fst) < 0)
ereport(ERROR,
@@ -331,11 +360,6 @@ pg_ls_dir(PG_FUNCTION_ARGS)
struct dirent *de;
directory_fctx *fctx;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to get directory listings"))));
-
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
@@ -344,7 +368,12 @@ pg_ls_dir(PG_FUNCTION_ARGS)
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(directory_fctx));
- fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
+ fctx->location = text_to_cstring(PG_GETARG_TEXT_P(0));
+ canonicalize_path(fctx->location);
+
+ /* Superuser is always allowed to bypass directory permissions */
+ if (!superuser())
+ check_directory_permissions(fctx->location);
fctx->dirdesc = AllocateDir(fctx->location);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..383e1c9 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -37,6 +37,7 @@
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
+#include "catalog/pg_diralias.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_foreign_data_wrapper.h"
@@ -367,6 +368,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {DirAliasRelationId, /* DIRALIASOID */
+ DirAliasOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{EnumRelationId, /* ENUMOID */
EnumOidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 8bfc604..da46c9f 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -103,6 +103,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
int numForeignServers;
int numDefaultACLs;
int numEventTriggers;
+ int numDirectoryAliases;
if (g_verbose)
write_msg(NULL, "reading schemas\n");
@@ -251,6 +252,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
write_msg(NULL, "reading row-security policies\n");
getRowSecurity(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading directory aliases\n");
+ getDirectoryAliases(fout, &numDirectoryAliases);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 259c472..08761e3 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -19,6 +19,7 @@
#include "dumputils.h"
#include "parser/keywords.h"
+#include "pg_backup_utils.h"
/* Globals from keywords.c */
@@ -545,10 +546,16 @@ buildACLCommands(const char *name, const char *subname,
* wire-in knowledge about the default public privileges for different
* kinds of objects.
*/
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM PUBLIC;\n",
+ name);
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
+ }
/*
* We still need some hacking though to cover the case where new default
@@ -593,16 +600,34 @@ buildACLCommands(const char *name, const char *subname,
? strcmp(privswgo->data, "ALL") != 0
: strcmp(privs->data, "ALL") != 0)
{
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
- type, name, fmtId(grantee->data));
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s;\n",
+ name, fmtId(grantee->data));
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(grantee->data));
+ }
+
if (privs->len > 0)
- appendPQExpBuffer(firstsql,
- "%sGRANT %s ON %s %s TO %s;\n",
- prefix, privs->data, type, name,
- fmtId(grantee->data));
+ {
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql,
+ "%sGRANT ON DIRALIAS %s %s TO %s;\n",
+ prefix, name, privs->data,
+ fmtId(grantee->data));
+ else
+ appendPQExpBuffer(firstsql,
+ "%sGRANT %s ON %s %s TO %s;\n",
+ prefix, privs->data, type, name,
+ fmtId(grantee->data));
+ }
+
if (privswgo->len > 0)
appendPQExpBuffer(firstsql,
"%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
@@ -622,8 +647,14 @@ buildACLCommands(const char *name, const char *subname,
if (privs->len > 0)
{
- appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
- prefix, privs->data, type, name);
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(secondsql, "%sGRANT ON DIRALIAS %s %s TO ",
+ prefix, name, privs->data);
+ else
+ appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
+ prefix, privs->data, type, name);
+
if (grantee->len == 0)
appendPQExpBufferStr(secondsql, "PUBLIC;\n");
else if (strncmp(grantee->data, "group ",
@@ -660,11 +691,18 @@ buildACLCommands(const char *name, const char *subname,
*/
if (!found_owner_privs && owner)
{
- appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
- if (subname)
- appendPQExpBuffer(firstsql, "(%s)", subname);
- appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
- type, name, fmtId(owner));
+ /* Handle special GRANT syntax for DIRALIAS */
+ if (strcmp(type, "DIRALIAS") == 0)
+ appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s",
+ name, fmtId(owner));
+ else
+ {
+ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
+ if (subname)
+ appendPQExpBuffer(firstsql, "(%s)", subname);
+ appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+ type, name, fmtId(owner));
+ }
}
destroyPQExpBuffer(grantee);
@@ -873,6 +911,11 @@ do { \
CONVERT_PRIV('r', "SELECT");
CONVERT_PRIV('w', "UPDATE");
}
+ else if (strcmp(type, "DIRALIAS") == 0)
+ {
+ CONVERT_PRIV('r', "READ");
+ CONVERT_PRIV('w', "WRITE");
+ }
else
abort();
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ed28d36..0449b87 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3106,7 +3106,8 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
strcmp(type, "SCHEMA") == 0 ||
strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
strcmp(type, "SERVER") == 0 ||
- strcmp(type, "USER MAPPING") == 0)
+ strcmp(type, "USER MAPPING") == 0 ||
+ strcmp(type, "DIRALIAS") == 0)
{
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
@@ -3307,7 +3308,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
- strcmp(te->desc, "SERVER") == 0)
+ strcmp(te->desc, "SERVER") == 0 ||
+ strcmp(te->desc, "DIRALIAS") == 0)
{
PQExpBuffer temp = createPQExpBuffer();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1e8f089..c1c8c01 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -191,6 +191,7 @@ static void dumpUserMappings(Archive *fout,
const char *servername, const char *namespace,
const char *owner, CatalogId catalogId, DumpId dumpId);
static void dumpDefaultACL(Archive *fout, DumpOptions *dopt, DefaultACLInfo *daclinfo);
+static void dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo);
static void dumpACL(Archive *fout, DumpOptions *dopt, CatalogId objCatId, DumpId objDumpId,
const char *type, const char *name, const char *subname,
@@ -2987,6 +2988,99 @@ dumpRowSecurity(Archive *fout, DumpOptions *dopt, RowSecurityInfo *rsinfo)
destroyPQExpBuffer(delqry);
}
+void
+getDirectoryAliases(Archive *fout, int *numDirectoryAliases)
+{
+ PQExpBuffer query;
+ PGresult *res;
+ DirectoryAliasInfo *dirinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_diralias;
+ int i_dirpath;
+ int i_rolname;
+ int i_diracl;
+ int i, ntups;
+
+ if (fout->remoteVersion < 90500)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, diralias, dirpath, "
+ "(%s dirowner) AS rolname, diracl "
+ "FROM pg_catalog.pg_directory",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ dirinfo = (DirectoryAliasInfo *) pg_malloc(ntups * sizeof(DirectoryAliasInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_diralias = PQfnumber(res, "diralias");
+ i_dirpath = PQfnumber(res, "dirpath");
+ i_rolname = PQfnumber(res, "rolname");
+ i_diracl = PQfnumber(res, "diracl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ dirinfo[i].dobj.objType = DO_DIRALIAS;
+ dirinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ dirinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&dirinfo[i].dobj);
+ dirinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_diralias));
+ dirinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ dirinfo[i].dirpath = pg_strdup(PQgetvalue(res, i, i_dirpath));
+ dirinfo[i].diracl = pg_strdup(PQgetvalue(res, i, i_diracl));
+ }
+}
+
+static void
+dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo)
+{
+ PQExpBuffer create_query;
+ PQExpBuffer delete_query;
+ char *diralias;
+
+ if (!dirinfo->dobj.dump || dopt->dataOnly)
+ return;
+
+ create_query = createPQExpBuffer();
+ delete_query = createPQExpBuffer();
+
+ diralias = pg_strdup(fmtId(dirinfo->dobj.name));
+
+ appendPQExpBuffer(delete_query, "DROP DIRALIAS %s;\n", diralias);
+
+ appendPQExpBuffer(create_query, "CREATE DIRALIAS %s AS \'%s\';\n",
+ diralias, dirinfo->dirpath);
+
+ ArchiveEntry(fout, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ dirinfo->dobj.name,
+ NULL, NULL,
+ dirinfo->rolname, false,
+ "DIRALIAS", SECTION_POST_DATA,
+ create_query->data, delete_query->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump ACL - because of the special GRANT syntax, we cannot use dumpACL */
+ if (dirinfo->diracl)
+ dumpACL(fout, dopt, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ "DIRALIAS", diralias, NULL, dirinfo->dobj.name,
+ NULL, dirinfo->rolname,
+ dirinfo->diracl);
+
+ destroyPQExpBuffer(create_query);
+ destroyPQExpBuffer(delete_query);
+}
+
static void
binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
PQExpBuffer upgrade_buffer,
@@ -8218,6 +8312,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
case DO_ROW_SECURITY:
dumpRowSecurity(fout, dopt, (RowSecurityInfo *) dobj);
break;
+ case DO_DIRALIAS:
+ dumpDirectoryAlias(fout, dopt, (DirectoryAliasInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
@@ -15615,6 +15712,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
case DO_EVENT_TRIGGER:
case DO_DEFAULT_ACL:
case DO_ROW_SECURITY:
+ case DO_DIRALIAS:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a7eb2fd..14a712c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -76,7 +76,8 @@ typedef enum
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
DO_REFRESH_MATVIEW,
- DO_ROW_SECURITY
+ DO_ROW_SECURITY,
+ DO_DIRALIAS
} DumpableObjectType;
typedef struct _dumpableObject
@@ -469,6 +470,15 @@ typedef struct _rowSecurityInfo
char *rsecwithcheck;
} RowSecurityInfo;
+typedef struct _directoryAliasInfo
+{
+ DumpableObject dobj;
+ char *diralias;
+ char *diracl;
+ char *dirpath;
+ char *rolname; /* name of owner */
+} DirectoryAliasInfo;
+
/* global decls */
extern bool force_quotes; /* double-quotes for identifiers flag */
extern bool g_verbose; /* verbose flag */
@@ -550,5 +560,6 @@ extern void getExtensionMembership(Archive *fout, DumpOptions *dopt, ExtensionIn
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getDirectoryAliases(Archive *fout, int *numDirectoryAliases);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 030bccc..5cece21 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -73,7 +73,8 @@ static const int oldObjectTypePriority[] =
13, /* DO_POST_DATA_BOUNDARY */
20, /* DO_EVENT_TRIGGER */
15, /* DO_REFRESH_MATVIEW */
- 21 /* DO_ROW_SECURITY */
+ 21, /* DO_ROW_SECURITY */
+ 22, /* DO_DIRALIAS */
};
/*
@@ -122,7 +123,8 @@ static const int newObjectTypePriority[] =
25, /* DO_POST_DATA_BOUNDARY */
32, /* DO_EVENT_TRIGGER */
33, /* DO_REFRESH_MATVIEW */
- 34 /* DO_ROW_SECURITY */
+ 34, /* DO_ROW_SECURITY */
+ 35, /* DO_DIRALIAS */
};
static DumpId preDataBoundId;
@@ -1443,6 +1445,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"ROW-SECURITY POLICY (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_DIRALIAS:
+ snprintf(buf, bufsize,
+ "DIRECTORY ALIAS (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_PRE_DATA_BOUNDARY:
snprintf(buf, bufsize,
"PRE-DATA BOUNDARY (ID %d)",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 6a4913a..1779bc0 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -148,6 +148,7 @@ typedef enum ObjectClass
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_ROWSECURITY, /* pg_rowsecurity */
+ OCLASS_DIRALIAS, /* pg_diralias */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 870692c..e8a195d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
+DECLARE_UNIQUE_INDEX(pg_directory_oid_index, 6101, on pg_diralias using btree(oid oid_ops));
+#define DirAliasOidIndexId 6101
+
+DECLARE_UNIQUE_INDEX(pg_directory_name_index, 6102, on pg_diralias using btree(dirname name_ops));
+#define DirAliasNameIndexId 6102
+
DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops));
#define RowSecurityOidIndexId 3257
diff --git a/src/include/catalog/pg_diralias.h b/src/include/catalog/pg_diralias.h
new file mode 100644
index 0000000..e58f047
--- /dev/null
+++ b/src/include/catalog/pg_diralias.h
@@ -0,0 +1,46 @@
+/*
+ * pg_diralias.h
+ * definition of the system catalog for directory permissions (pg_diralias)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_DIRALIAS_H
+#define PG_DIRALIAS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_diralias definition. cpp turns this into
+ * typedef struct FormData_pg_diralias
+ * ----------------
+ */
+#define DirAliasRelationId 6100
+
+CATALOG(pg_diralias,6100)
+{
+ NameData dirname; /* directory alias name */
+ text dirpath; /* directory path */
+#ifdef CATALOG_VARLEN
+ aclitem diracl[1]; /* directory permissions */
+#endif
+} FormData_pg_diralias;
+
+/* ----------------
+ * Form_pg_diralias corresponds to a pointer to a row with
+ * the format of pg_diralias relation.
+ * ----------------
+ */
+typedef FormData_pg_diralias *Form_pg_diralias;
+
+/* ----------------
+ * compiler constants for pg_diralias
+ * ----------------
+ */
+#define Natts_pg_diralias 3
+#define Anum_pg_diralias_dirname 1
+#define Anum_pg_diralias_dirpath 2
+#define Anum_pg_diralias_diracl 3
+
+#endif /* PG_DIRALIAS_H */
\ No newline at end of file
diff --git a/src/include/commands/diralias.h b/src/include/commands/diralias.h
new file mode 100644
index 0000000..851d321
--- /dev/null
+++ b/src/include/commands/diralias.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * directory.h
+ * prototypes for directory.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/directory.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef DIRECTORY_H
+#define DIRECTORY_H
+
+#include "nodes/parsenodes.h"
+
+extern void RemoveDirAliasById(Oid dir_id);
+extern void CreateDirAlias(CreateDirAliasStmt *stmt);
+extern void AlterDirAlias(AlterDirAliasStmt *stmt);
+
+extern char *get_diralias_name(Oid dir_id);
+extern Oid get_diralias_oid(const char *name, bool missing_ok);
+extern Oid get_diralias_owner(Oid dir_id);
+extern Oid get_diralias_oid_by_path(const char *path);
+
+#endif /* DIRECTORY_H */
\ No newline at end of file
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 154d943..6b293aa 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -278,6 +278,7 @@ typedef enum NodeTag
T_AlterDomainStmt,
T_SetOperationStmt,
T_GrantStmt,
+ T_GrantDirAliasStmt,
T_GrantRoleStmt,
T_AlterDefaultPrivilegesStmt,
T_ClosePortalStmt,
@@ -368,6 +369,8 @@ typedef enum NodeTag
T_AlterSystemStmt,
T_CreatePolicyStmt,
T_AlterPolicyStmt,
+ T_CreateDirAliasStmt,
+ T_AlterDirAliasStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cef9544..4b7197c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1212,6 +1212,7 @@ typedef enum ObjectType
OBJECT_COLLATION,
OBJECT_CONVERSION,
OBJECT_DATABASE,
+ OBJECT_DIRALIAS,
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
@@ -1412,6 +1413,7 @@ typedef enum GrantObjectType
ACL_OBJECT_LARGEOBJECT, /* largeobject */
ACL_OBJECT_NAMESPACE, /* namespace */
ACL_OBJECT_TABLESPACE, /* tablespace */
+ ACL_OBJECT_DIRALIAS, /* directory alias */
ACL_OBJECT_TYPE /* type */
} GrantObjectType;
@@ -1483,6 +1485,20 @@ typedef struct GrantRoleStmt
} GrantRoleStmt;
/* ----------------------
+ * Grant/Revoke Directory Permission Statement
+ * ----------------------
+ */
+typedef struct GrantDirAliasStmt
+{
+ NodeTag type;
+ List *directories; /* the directory alias/name */
+ List *permissions; /* list of permission to be granted/revoked */
+ List *grantees; /* list of roles to be granted/revoked permission */
+ bool is_grant; /* true = GRANT, false = REVOKE */
+ char *grantor; /* set grantor to other than current role */
+} GrantDirAliasStmt;
+
+/* ----------------------
* Alter Default Privileges Statement
* ----------------------
*/
@@ -1890,6 +1906,28 @@ typedef struct AlterPolicyStmt
} AlterPolicyStmt;
/* ----------------------
+ * Create DIRALIAS Statement
+ * ----------------------
+ */
+typedef struct CreateDirAliasStmt
+{
+ NodeTag type;
+ char *name;
+ char *path;
+} CreateDirAliasStmt;
+
+/* ----------------------
+ * Alter DIRALIAS Statement
+ * ----------------------
+ */
+typedef struct AlterDirAliasStmt
+{
+ NodeTag type;
+ char *name;
+ char *path;
+} AlterDirAliasStmt;
+
+/* ----------------------
* Create TRIGGER Statement
* ----------------------
*/
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index e14dc9a..eb31cf7 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -125,6 +125,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
+PG_KEYWORD("diralias", DIRALIAS, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index a8e3164..cb75b1a 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -156,6 +156,7 @@ typedef ArrayType Acl;
#define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_DIRALIAS (ACL_SELECT|ACL_UPDATE)
/* operation codes for pg_*_aclmask */
typedef enum
@@ -197,6 +198,7 @@ typedef enum AclObjectKind
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_DIRALIAS, /* pg_diralias */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
@@ -255,6 +257,7 @@ extern Datum aclexplode(PG_FUNCTION_ARGS);
*/
extern void ExecuteGrantStmt(GrantStmt *stmt);
extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt);
+extern void ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt);
extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid);
extern void RemoveDefaultACLById(Oid defaclOid);
@@ -281,6 +284,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
AclMode mask, AclMaskHow how);
extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
AclMode mask, AclMaskHow how);
+extern AclMode pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask,
+ AclMaskHow how);
extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
Oid roleid, AclMode mode);
@@ -297,6 +302,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode);
extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+extern AclResult pg_diralias_aclcheck(Oid diroid, Oid roleid, AclMode mode);
extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
const char *objectname);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..c4b822e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -54,6 +54,7 @@ enum SysCacheIdentifier
CONVOID,
DATABASEOID,
DEFACLROLENSPOBJ,
+ DIRALIASOID,
ENUMOID,
ENUMTYPOIDNAME,
EVENTTRIGGERNAME,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 2c8ec11..334f648 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -102,6 +102,7 @@ pg_db_role_setting|t
pg_default_acl|t
pg_depend|t
pg_description|t
+pg_diralias|t
pg_enum|t
pg_event_trigger|t
pg_extension|t
* Peter Eisentraut (peter_e@gmx.net) wrote:
On 10/16/14 12:01 PM, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server.I think that can be done with a security-definer function.
Of course it can be. We could replace the entire authorization system
with security definer functions too. I don't view this as an argument
against this feature, particularly as we know other systems have it,
users have asked for multiple times, and large PG deployments have had
to hack around our lack of it.
Thanks,
Stephen
All,
Attached is a patch with minor updates/corrections.
-Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
Attachments:
directory-alias-v3.patchtext/x-patch; charset=US-ASCII; name=directory-alias-v3.patchDownload
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index b257b02..8cdc5cb
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** POSTGRES_BKI_SRCS = $(addprefix $(top_sr
*** 41,47 ****
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
! toasting.h indexing.h \
)
# location of Catalog.pm
--- 41,47 ----
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
! pg_diralias.h toasting.h indexing.h \
)
# location of Catalog.pm
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d30612c..3717bf5
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 30,35 ****
--- 30,36 ----
#include "catalog/pg_collation.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
+ #include "catalog/pg_diralias.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
***************
*** 48,53 ****
--- 49,55 ----
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "commands/dbcommands.h"
+ #include "commands/diralias.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "foreign/foreign.h"
*************** ExecGrant_Type(InternalGrant *istmt)
*** 3183,3188 ****
--- 3185,3374 ----
heap_close(relation, RowExclusiveLock);
}
+ /*
+ * ExecuteGrantDirAliasStmt
+ * handles the execution of the GRANT/REVOKE ON DIRALIAS command.
+ *
+ * stmt - the GrantDirAliasStmt that describes the directory aliases and
+ * permissions to be granted/revoked.
+ */
+ void
+ ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt)
+ {
+ Relation pg_diralias_rel;
+ Oid grantor;
+ List *grantee_ids = NIL;
+ AclMode permissions;
+ ListCell *item;
+
+ /* Must be superuser to grant directory alias permissions */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to grant directory alias permissions")));
+
+ /*
+ * Grantor is optional. If it is not provided then set it to the current
+ * user.
+ */
+ if (stmt->grantor)
+ grantor = get_role_oid(stmt->grantor, false);
+ else
+ grantor = GetUserId();
+
+ /* Convert grantee names to oids */
+ foreach(item, stmt->grantees)
+ {
+ PrivGrantee *grantee = (PrivGrantee *) lfirst(item);
+
+ if (grantee->rolname == NULL)
+ grantee_ids = lappend_oid(grantee_ids, ACL_ID_PUBLIC);
+ else
+ {
+ Oid roleid = get_role_oid(grantee->rolname, false);
+ grantee_ids = lappend_oid(grantee_ids, roleid);
+ }
+ }
+
+ permissions = ACL_NO_RIGHTS;
+
+ /* If ALL was provided then set permissions to ACL_ALL_RIGHTS_DIRALIAS */
+ if (stmt->permissions == NIL)
+ permissions = ACL_ALL_RIGHTS_DIRALIAS;
+ else
+ {
+ /* Condense all permissions */
+ foreach(item, stmt->permissions)
+ {
+ AccessPriv *priv = (AccessPriv *) lfirst(item);
+ permissions |= string_to_privilege(priv->priv_name);
+ }
+ }
+
+
+ /*
+ * Though it shouldn't be possible to provide permissions other than READ
+ * and WRITE, check to make sure no others have been set. If they have,
+ * then warn the user and correct the permissions.
+ */
+ if (permissions & !((AclMode) ACL_ALL_RIGHTS_DIRALIAS))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("directory aliases only support READ and WRITE permissions")));
+
+ permissions &= ACL_ALL_RIGHTS_DIRALIAS;
+ }
+
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /* Grant/Revoke permissions on directory aliases */
+ foreach(item, stmt->directories)
+ {
+ Datum values[Natts_pg_diralias];
+ bool replaces[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ HeapTuple new_tuple;
+ Datum datum;
+ Oid owner_id;
+ Acl *dir_acl;
+ Acl *new_acl;
+ bool is_null;
+ int num_old_members;
+ int num_new_members;
+ Oid *old_members;
+ Oid *new_members;
+ Oid diralias_id;
+ char *name;
+
+ name = strVal(lfirst(item));
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(name));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for directory alias \"%s\"", name);
+
+ /*
+ * Get directory alias owner id. Since all superusers are considered
+ * to be owners of a directory alias, it is safe to assume that the
+ * current user is an owner, given the superuser check above.
+ */
+ owner_id = GetUserId();
+
+ /* Get directory alias ACL */
+ datum = heap_getattr(tuple, Anum_pg_diralias_diracl,
+ RelationGetDescr(pg_diralias_rel), &is_null);
+
+ /* Get the directory alias oid */
+ diralias_id = HeapTupleGetOid(tuple);
+
+ /*
+ * If there are currently no permissions granted on the directory alias,
+ * then add default permissions, which should include the permssions
+ * granted to the owner of the table. Directory aliases are owned by
+ * all superusers.
+ */
+ if (is_null)
+ {
+ dir_acl = acldefault(ACL_OBJECT_DIRALIAS, owner_id);
+ num_old_members = 0;
+ old_members = NULL;
+ }
+ else
+ {
+ dir_acl = DatumGetAclPCopy(datum);
+
+ /* Get the roles in the current ACL */
+ num_old_members = aclmembers(dir_acl, &old_members);
+ }
+
+ /* Merge new ACL with current ACL */
+ new_acl = merge_acl_with_grant(dir_acl, stmt->is_grant, false,
+ DROP_CASCADE, grantee_ids, permissions,
+ grantor, owner_id);
+
+ num_new_members = aclmembers(new_acl, &new_members);
+
+ /* Insert new ACL value */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(replaces, 0, sizeof(replaces));
+
+ values[Anum_pg_diralias_diracl - 1] = PointerGetDatum(new_acl);
+ replaces[Anum_pg_diralias_diracl - 1] = true;
+
+ new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel),
+ values, nulls, replaces);
+
+ simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, new_tuple);
+
+ /* Update shared dependency ACL information */
+ updateAclDependencies(DirAliasRelationId, diralias_id, 0,
+ owner_id,
+ num_old_members, old_members,
+ num_new_members, new_members);
+
+ /* Clean Up */
+ pfree(new_acl);
+ heap_endscan(scandesc);
+ }
+
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+ }
+
static AclMode
string_to_privilege(const char *privname)
*************** static const char *const no_priv_msg[MAX
*** 3307,3312 ****
--- 3493,3500 ----
gettext_noop("permission denied for event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("permission denied for extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("permission denied for directory alias %s"),
};
static const char *const not_owner_msg[MAX_ACL_KIND] =
*************** static const char *const not_owner_msg[M
*** 3353,3358 ****
--- 3541,3548 ----
gettext_noop("must be owner of event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("must be owner of extension %s"),
+ /* ACL_KIND_DIRALIAS */
+ gettext_noop("must be owner of directory alias %s"),
};
*************** pg_foreign_server_aclmask(Oid srv_oid, O
*** 4194,4199 ****
--- 4384,4445 ----
}
/*
+ * Exported routine for examining a user's permissions for a directory alias.
+ */
+ AclMode
+ pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask, AclMaskHow how)
+ {
+ AclMode result;
+ HeapTuple tuple;
+ Datum aclDatum;
+ bool isNull;
+ Acl *acl;
+
+ /* Bypass permission checks for superusers */
+ if (superuser_arg(roleid))
+ return mask;
+
+ /* Must get the directory alias's tuple from pg_diralias */
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(dir_oid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias with OID %u does not exist", dir_oid)));
+
+ aclDatum = SysCacheGetAttr(DIRALIASOID, tuple,
+ Anum_pg_diralias_diracl, &isNull);
+ if (isNull)
+ {
+ /* No ACL, so build default ACL */
+ acl = acldefault(ACL_OBJECT_DIRALIAS, roleid);
+ aclDatum = (Datum) 0;
+ }
+ else
+ {
+ /* detoast rel's ACL if necessary */
+ acl = DatumGetAclP(aclDatum);
+ }
+
+ /*
+ * We use InvalidOid as the ownerid for determining the aclmask. This is
+ * because directory aliases belong to all superusers. aclmask() uses the
+ * ownerid to determine grant options by implying that owners always have
+ * all grant options. If roleid, is not a superuser and therefore an owner
+ * (which it couldn't be at this point), then this check in aclmask() must
+ * be false. Therefore, by using InvalidOid we are guaranteed this behavior.
+ */
+ result = aclmask(acl, roleid, InvalidOid, mask, how);
+
+ /* if we have a detoasted copy, free it */
+ if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+ pfree(acl);
+
+ ReleaseSysCache(tuple);
+
+ return result;
+ }
+
+ /*
* Exported routine for examining a user's privileges for a type.
*/
AclMode
*************** pg_type_aclcheck(Oid type_oid, Oid rolei
*** 4510,4515 ****
--- 4756,4773 ----
return ACLCHECK_OK;
else
return ACLCHECK_NO_PRIV;
+ }
+
+ /*
+ * Exported routine for checking a user's access permissions to a directory alias
+ */
+ AclResult
+ pg_diralias_aclcheck(Oid dir_oid, Oid roleid, AclMode mode)
+ {
+ if (pg_diralias_aclmask(dir_oid, roleid, mode, ACLMASK_ANY) != 0)
+ return ACLCHECK_OK;
+ else
+ return ACLCHECK_NO_PRIV;
}
/*
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index 256486c..b056559
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 33,38 ****
--- 33,39 ----
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
+ #include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
***************
*** 56,61 ****
--- 57,63 ----
#include "catalog/pg_user_mapping.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+ #include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
*************** doDeletion(const ObjectAddress *object,
*** 1255,1260 ****
--- 1257,1266 ----
RemovePolicyById(object->objectId);
break;
+ case OCLASS_DIRALIAS:
+ RemoveDirAliasById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2325,2330 ****
--- 2331,2339 ----
case RowSecurityRelationId:
return OCLASS_ROWSECURITY;
+
+ case DirAliasRelationId:
+ return OCLASS_DIRALIAS;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index b69b75b..872d233
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 26,31 ****
--- 26,32 ----
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_default_acl.h"
+ #include "catalog/pg_diralias.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
***************
*** 54,59 ****
--- 55,61 ----
#include "catalog/pg_user_mapping.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+ #include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
*************** static const ObjectPropertyType ObjectPr
*** 358,363 ****
--- 360,377 ----
false
},
{
+ DirAliasRelationId,
+ DirAliasOidIndexId,
+ DIRALIASOID,
+ -1,
+ Anum_pg_diralias_dirname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ -1,
+ false
+ },
+ {
EventTriggerRelationId,
EventTriggerOidIndexId,
EVENTTRIGGEROID,
*************** get_object_address(ObjectType objtype, L
*** 536,541 ****
--- 550,556 ----
&relation, missing_ok);
break;
case OBJECT_DATABASE:
+ case OBJECT_DIRALIAS:
case OBJECT_EXTENSION:
case OBJECT_TABLESPACE:
case OBJECT_ROLE:
*************** get_object_address_unqualified(ObjectTyp
*** 746,751 ****
--- 761,769 ----
case OBJECT_DATABASE:
msg = gettext_noop("database name cannot be qualified");
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias cannot be qualified");
+ break;
case OBJECT_EXTENSION:
msg = gettext_noop("extension name cannot be qualified");
break;
*************** get_object_address_unqualified(ObjectTyp
*** 790,795 ****
--- 808,818 ----
address.objectId = get_database_oid(name, missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_DIRALIAS:
+ address.classId = DirAliasRelationId;
+ address.objectId = get_diralias_oid(name, missing_ok);
+ address.objectSubId = 0;
+ break;
case OBJECT_EXTENSION:
address.classId = ExtensionRelationId;
address.objectId = get_extension_oid(name, missing_ok);
*************** check_object_ownership(Oid roleid, Objec
*** 1318,1323 ****
--- 1341,1347 ----
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_DIRALIAS:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
*************** getObjectDescription(const ObjectAddress
*** 2224,2229 ****
--- 2248,2265 ----
break;
}
+ case OCLASS_DIRALIAS:
+ {
+ char *diralias_name;
+
+ diralias_name = get_diralias_name(object->objectId);
+ if (!diralias_name)
+ elog(ERROR, "cache lookup failed for directory alias %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("directory alias %s"), diralias_name);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
new file mode 100644
index b1ac704..36a897c
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
*************** include $(top_builddir)/src/Makefile.glo
*** 14,20 ****
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
! dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
policy.o portalcmds.o prepare.o proclang.o \
--- 14,20 ----
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
! dbcommands.o define.o diralias.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
policy.o portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
new file mode 100644
index c9a9baf..47f8d49
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 41,46 ****
--- 41,47 ----
#include "commands/conversioncmds.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+ #include "commands/diralias.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
*************** ExecRenameStmt(RenameStmt *stmt)
*** 349,354 ****
--- 350,356 ----
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_EVENT_TRIGGER:
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 6b83576..3a9562b
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 28,33 ****
--- 28,34 ----
#include "catalog/pg_type.h"
#include "commands/copy.h"
#include "commands/defrem.h"
+ #include "commands/diralias.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
*************** DoCopy(const CopyStmt *stmt, const char
*** 788,796 ****
Oid relid;
Node *query = NULL;
- /* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
{
if (stmt->is_program)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
--- 789,801 ----
Oid relid;
Node *query = NULL;
if (!pipe && !superuser())
{
+ /*
+ * Disallow COPY to/from program except to superusers. If COPY is to/from
+ * a file then diallow unless the current user is either superuser or has
+ * been granted the appropriate permissions on the target parent directory.
+ */
if (stmt->is_program)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
*************** DoCopy(const CopyStmt *stmt, const char
*** 798,808 ****
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
else
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser to COPY to or from a file"),
! errhint("Anyone can COPY to stdout or from stdin. "
! "psql's \\copy command also works for anyone.")));
}
if (stmt->relation)
--- 803,845 ----
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
else
! {
! char *path;
! Oid diralias_id;
! AclResult aclresult;
!
! /* Get the parent directory */
! path = pstrdup(stmt->filename);
! canonicalize_path(path);
! get_parent_directory(path);
!
! /* Search for directory in pg_diralias */
! diralias_id = get_diralias_oid_by_path(path);
!
! /*
! * If an entry does not exist for the path in pg_diralias then raise
! * an error.
! */
! if (!OidIsValid(diralias_id))
! ereport(ERROR,
! (errcode(ERRCODE_UNDEFINED_OBJECT),
! errmsg("a directory alias entry for \"%s\" does not exist.",
! path)));
!
! /* Check directory alias entry permissions */
! if (stmt->is_from)
! aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
! else
! aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_UPDATE);
!
! /* If the current user has insufficient privileges then raise an error. */
! if (aclresult != ACLCHECK_OK)
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must have permissions to COPY to or from \"%s\"", path),
! errhint("Anyone can COPY to stdout or from stdin. "
! "psql's \\copy command also works for anyone.")));
! }
}
if (stmt->relation)
diff --git a/src/backend/commands/diralias.c b/src/backend/commands/diralias.c
new file mode 100644
index ...9ccd77e
*** a/src/backend/commands/diralias.c
--- b/src/backend/commands/diralias.c
***************
*** 0 ****
--- 1,375 ----
+ /*-------------------------------------------------------------------------
+ *
+ * diralias.c
+ * Commands for manipulating directory aliases.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/diralias.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/objectaccess.h"
+ #include "catalog/pg_authid.h"
+ #include "catalog/pg_diralias.h"
+ #include "commands/diralias.h"
+ #include "commands/user.h"
+ #include "miscadmin.h"
+ #include "utils/acl.h"
+ #include "utils/builtins.h"
+ #include "utils/guc.h"
+ #include "utils/fmgroids.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+
+ /*
+ * RemoveDirAliasById
+ * remove a directory alias by its OID. If a directory does not exist with
+ * the provided oid, then an error is raised.
+ *
+ * diralias_id - the oid of the directory alias.
+ */
+ void
+ RemoveDirAliasById(Oid diralias_id)
+ {
+ Relation pg_diralias_rel;
+ HeapTuple tuple;
+
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /*
+ * Find the directory alias to delete.
+ */
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id));
+
+ /* If the directory alias exists, then remove it, otherwise raise an error. */
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for directory alias %u", diralias_id);
+
+ simple_heap_delete(pg_diralias_rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+ }
+
+ /*
+ * CreateDirAlias
+ * handles the execution of the CREATE DIRALIAS command.
+ *
+ * stmt - the CreateDirAliasStmt that describes the directory alias entry to
+ * create.
+ */
+ void
+ CreateDirAlias(CreateDirAliasStmt *stmt)
+ {
+ Relation pg_diralias_rel;
+ Datum values[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ Oid diralias_id;
+ char *path;
+
+ /* Must be superuser to create a directory alias entry. */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create directory alias")));
+
+ /* Unix-ify the path, and strip any trailing slashes */
+ path = pstrdup(stmt->path);
+ canonicalize_path(path);
+
+ /* Disallow quotes */
+ if (strchr(path, '\''))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("directory path cannot contain single quotes")));
+
+ /*
+ * Allowing relative paths seems risky and really a bad idea. Therefore,
+ * if a relative path is provided then an error is raised.
+ *
+ * This also helps us ensure that directory path is not empty or whitespace.
+ */
+ if (!is_absolute_path(path))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("directory path must be an absolute path")));
+
+ /* Open pg_diralias catalog */
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /*
+ * Make sure a duplicate does not already exist. Need to check both the name
+ * and the path. If either exists, then raise an error.
+ */
+
+ /* Check alias name does not already exist */
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->name));
+
+ /*
+ * We use a heapscan here even though there is an index on alias and path.
+ * We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("directory alias \"%s\" already exists", stmt->name)));
+
+ heap_endscan(scandesc);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirpath,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(path));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ /* Check that path does not already exist. */
+ if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("directory alias with path \"%s\" already exists", path)));
+
+ heap_endscan(scandesc);
+
+ /*
+ * All is well and safe to insert.
+ */
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_diralias_dirname - 1] = CStringGetDatum(stmt->name);
+ values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path);
+
+ /* No ACL items are set on the directory by default */
+ nulls[Anum_pg_diralias_diracl - 1] = true;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_diralias_rel), values, nulls);
+
+ diralias_id = simple_heap_insert(pg_diralias_rel, tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, tuple);
+
+ /* Post creation hook for new directory alias */
+ InvokeObjectPostCreateHook(DirAliasRelationId, diralias_id, 0);
+
+ /* Clean up */
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+ }
+
+ /*
+ * AlterDirAlias
+ * handles the execution of the ALTER DIRALIAS command.
+ *
+ * stmt - the AlterDirAliasStmt that describes the directory alias entry to alter.
+ */
+ void
+ AlterDirAlias(AlterDirAliasStmt *stmt)
+ {
+ Relation pg_diralias_rel;
+ ScanKeyData skey[1];
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ Datum values[Natts_pg_diralias];
+ bool nulls[Natts_pg_diralias];
+ bool replaces[Natts_pg_diralias];
+ HeapTuple new_tuple;
+ char *path;
+
+ /* Must be superuser to alter directory alias */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter directory alias")));
+
+ /* Unix-ify the new path, and strip any trailing slashes */
+ path = pstrdup(stmt->path);
+ canonicalize_path(path);
+
+ /* Disallow quotes */
+ if (strchr(path, '\''))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_NAME),
+ errmsg("directory path cannot contain single quotes")));
+
+ /* Open pg_diralias catalog */
+ pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock);
+
+ /* Search for directory alias by name */
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->name));
+
+ /*
+ * We use a heapscan here even though there is an index on alias and path.
+ * We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ /* If directory alias does not exist then raise an error */
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias \"%s\" does not exist", stmt->name)));
+
+ /* Build new tuple and update pg_diralias */
+ memset(nulls, 0, sizeof(nulls));
+ memset(replaces, 0, sizeof(replaces));
+ memset(values, 0, sizeof(values));
+
+ values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path);
+ replaces[Anum_pg_diralias_dirpath - 1] = true;
+
+ new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel),
+ values, nulls, replaces);
+
+ simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_diralias_rel, new_tuple);
+
+ /* Post alter hook for directory alias */
+ InvokeObjectPostAlterHook(DirAliasRelationId, HeapTupleGetOid(tuple), 0);
+
+ /* Clean Up */
+ heap_freetuple(new_tuple);
+ heap_endscan(scandesc);
+ heap_close(pg_diralias_rel, RowExclusiveLock);
+ }
+
+ /*
+ * get_diralias_name
+ * given a directory alias OID, look up the name. If the directory does not
+ * exist then NULL is returned.
+ *
+ * diralias_id - the OID of the directory alias entry in pg_diralias.
+ */
+ char *
+ get_diralias_name(Oid diralias_id)
+ {
+ char *name = NULL;
+ HeapTuple tuple;
+
+ tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id));
+ if (HeapTupleIsValid(tuple))
+ {
+ name = pstrdup(NameStr(((Form_pg_diralias) GETSTRUCT(tuple))->dirname));
+ ReleaseSysCache(tuple);
+ }
+
+ return name;
+ }
+
+ /*
+ * get_directory_oid_by_path
+ * given a directory path, look up the OID. If the directory does not exist
+ * this InvalidOid is returned.
+ *
+ * path - the path of the directory
+ */
+ Oid
+ get_diralias_oid_by_path(const char *path)
+ {
+ Oid dir_id = InvalidOid;
+ Relation pg_diralias_rel;
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData skey[1];
+
+ /*
+ * Search pg_diralias. We use a heapscan here even though there is an index
+ * on alias. We do this on the theory that pg_diralias will usually have a
+ * relatively small number of entries and therefore it is safe to assume
+ * an index scan would be wasted effort.
+ */
+ pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirpath,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(path));
+
+ scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey);
+ tuple = heap_getnext(scandesc, ForwardScanDirection);
+
+ if (HeapTupleIsValid(tuple))
+ dir_id = HeapTupleGetOid(tuple);
+
+ heap_endscan(scandesc);
+ heap_close(pg_diralias_rel, AccessShareLock);
+
+ return dir_id;
+ }
+
+ /*
+ * get_directory_oid
+ * given a directory alias name, look up the OID. If a directory alias does
+ * not exist for the given name then raise an error. However, if missing_ok
+ * is true, then return InvalidOid.
+ *
+ * name - the name of the directory alias
+ * missing_ok - false if an error should be raised if the directory alias does
+ * not exist.
+ */
+ Oid
+ get_diralias_oid(const char *name, bool missing_ok)
+ {
+ Oid dir_id;
+ Relation pg_diralias_rel;
+ ScanKeyData skey[1];
+ SysScanDesc sscan;
+ HeapTuple tuple;
+
+ /* Search pg_diralias for a directory alias entry with provided name */
+ pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_diralias_dirname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(name));
+
+ sscan = systable_beginscan(pg_diralias_rel, DirAliasNameIndexId,
+ true, NULL, 1, skey);
+
+ tuple = systable_getnext(sscan);
+
+ if (HeapTupleIsValid(tuple))
+ dir_id = HeapTupleGetOid(tuple);
+ else
+ dir_id = InvalidOid;
+
+ systable_endscan(sscan);
+ heap_close(pg_diralias_rel, AccessShareLock);
+
+ if (!OidIsValid(dir_id) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("directory alias \"%s\" does not exist", name)));
+
+ return dir_id;
+ }
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
new file mode 100644
index 8583581..8cae12e
*** a/src/backend/commands/dropcmds.c
--- b/src/backend/commands/dropcmds.c
*************** does_not_exist_skipping(ObjectType objty
*** 380,385 ****
--- 380,389 ----
list_length(objname) - 1));
}
break;
+ case OBJECT_DIRALIAS:
+ msg = gettext_noop("directory alias \"%s\" does not exist, skipping");
+ name = NameListToString(objname);
+ break;
case OBJECT_EVENT_TRIGGER:
msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
name = NameListToString(objname);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 1b8c94b..b70c322
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** static event_trigger_support_data event_
*** 73,78 ****
--- 73,79 ----
{"COLLATION", true},
{"CONVERSION", true},
{"DATABASE", false},
+ {"DIRALIAS", true},
{"DOMAIN", true},
{"EXTENSION", true},
{"EVENT TRIGGER", false},
*************** EventTriggerSupportsObjectType(ObjectTyp
*** 924,929 ****
--- 925,931 ----
case OBJECT_CONSTRAINT:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_DIRALIAS:
case OBJECT_DOMAIN:
case OBJECT_EXTENSION:
case OBJECT_FDW:
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 998,1003 ****
--- 1000,1006 ----
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_ROWSECURITY:
+ case OCLASS_DIRALIAS:
return true;
case MAX_OCLASS:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 21b070a..8941fa2
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyGrantRoleStmt(const GrantRoleStmt *
*** 2702,2707 ****
--- 2702,2721 ----
return newnode;
}
+ static GrantDirAliasStmt *
+ _copyGrantDirAliasStmt(const GrantDirAliasStmt *from)
+ {
+ GrantDirAliasStmt *newnode = makeNode(GrantDirAliasStmt);
+
+ COPY_NODE_FIELD(directories);
+ COPY_NODE_FIELD(permissions);
+ COPY_NODE_FIELD(grantees);
+ COPY_SCALAR_FIELD(is_grant);
+ COPY_STRING_FIELD(grantor);
+
+ return newnode;
+ }
+
static AlterDefaultPrivilegesStmt *
_copyAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *from)
{
*************** _copyAlterPolicyStmt(const AlterPolicySt
*** 3879,3884 ****
--- 3893,3920 ----
return newnode;
}
+ static CreateDirAliasStmt *
+ _copyCreateDirAliasStmt(const CreateDirAliasStmt *from)
+ {
+ CreateDirAliasStmt *newnode = makeNode(CreateDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+ }
+
+ static AlterDirAliasStmt *
+ _copyAlterDirAliasStmt(const AlterDirAliasStmt *from)
+ {
+ AlterDirAliasStmt *newnode = makeNode(AlterDirAliasStmt);
+
+ COPY_STRING_FIELD(name);
+ COPY_STRING_FIELD(path);
+
+ return newnode;
+ }
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
*************** copyObject(const void *from)
*** 4318,4323 ****
--- 4354,4362 ----
case T_GrantStmt:
retval = _copyGrantStmt(from);
break;
+ case T_GrantDirAliasStmt:
+ retval = _copyGrantDirAliasStmt(from);
+ break;
case T_GrantRoleStmt:
retval = _copyGrantRoleStmt(from);
break;
*************** copyObject(const void *from)
*** 4597,4602 ****
--- 4636,4647 ----
case T_AlterPolicyStmt:
retval = _copyAlterPolicyStmt(from);
break;
+ case T_CreateDirAliasStmt:
+ retval = _copyCreateDirAliasStmt(from);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _copyAlterDirAliasStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 358395f..e266ad8
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalGrantRoleStmt(const GrantRoleStmt
*** 1046,1051 ****
--- 1046,1063 ----
}
static bool
+ _equalGrantDirAliasStmt(const GrantDirAliasStmt *a, const GrantDirAliasStmt *b)
+ {
+ COMPARE_NODE_FIELD(directories);
+ COMPARE_NODE_FIELD(permissions);
+ COMPARE_NODE_FIELD(grantees);
+ COMPARE_SCALAR_FIELD(is_grant);
+ COMPARE_STRING_FIELD(grantor);
+
+ return true;
+ }
+
+ static bool
_equalAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *a, const AlterDefaultPrivilegesStmt *b)
{
COMPARE_NODE_FIELD(options);
*************** _equalAlterPolicyStmt(const AlterPolicyS
*** 2034,2039 ****
--- 2046,2069 ----
}
static bool
+ _equalCreateDirAliasStmt(const CreateDirAliasStmt *a, const CreateDirAliasStmt *b)
+ {
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+ }
+
+ static bool
+ _equalAlterDirAliasStmt(const AlterDirAliasStmt *a, const AlterDirAliasStmt *b)
+ {
+ COMPARE_STRING_FIELD(name);
+ COMPARE_STRING_FIELD(path);
+
+ return true;
+ }
+
+ static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
COMPARE_SCALAR_FIELD(kind);
*************** equal(const void *a, const void *b)
*** 2778,2783 ****
--- 2808,2816 ----
case T_GrantStmt:
retval = _equalGrantStmt(a, b);
break;
+ case T_GrantDirAliasStmt:
+ retval = _equalGrantDirAliasStmt(a, b);
+ break;
case T_GrantRoleStmt:
retval = _equalGrantRoleStmt(a, b);
break;
*************** equal(const void *a, const void *b)
*** 3057,3062 ****
--- 3090,3101 ----
case T_AlterPolicyStmt:
retval = _equalAlterPolicyStmt(a, b);
break;
+ case T_CreateDirAliasStmt:
+ retval = _equalCreateDirAliasStmt(a, b);
+ break;
+ case T_AlterDirAliasStmt:
+ retval = _equalAlterDirAliasStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index 0de9584..24d5eb5
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 258,264 ****
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
! CreateMatViewStmt RefreshMatViewStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
--- 258,265 ----
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
! CreateMatViewStmt RefreshMatViewStmt GrantDirStmt RevokeDirStmt
! CreateDirAliasStmt AlterDirAliasStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
*************** static Node *makeRecursiveViewSelect(cha
*** 324,329 ****
--- 325,333 ----
%type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole
+ %type <node> dir_perm_opts
+ %type <list> dir_permissions dir_perm_list
+
%type <str> iso_level opt_encoding
%type <node> grantee
%type <list> grantee_list
*************** static Node *makeRecursiveViewSelect(cha
*** 559,565 ****
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
! DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
--- 563,570 ----
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
! DICTIONARY DIRALIAS DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
! DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
*************** stmt :
*** 734,739 ****
--- 739,745 ----
| AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
+ | AlterDirAliasStmt
| AlterDomainStmt
| AlterEnumStmt
| AlterExtensionStmt
*************** stmt :
*** 769,774 ****
--- 775,781 ----
| CreateAssertStmt
| CreateCastStmt
| CreateConversionStmt
+ | CreateDirAliasStmt
| CreateDomainStmt
| CreateExtensionStmt
| CreateFdwStmt
*************** stmt :
*** 820,825 ****
--- 827,833 ----
| ExplainStmt
| FetchStmt
| GrantStmt
+ | GrantDirStmt
| GrantRoleStmt
| ImportForeignSchemaStmt
| IndexStmt
*************** stmt :
*** 837,842 ****
--- 845,851 ----
| RemoveOperStmt
| RenameStmt
| RevokeStmt
+ | RevokeDirStmt
| RevokeRoleStmt
| RuleStmt
| SecLabelStmt
*************** row_security_cmd:
*** 4606,4611 ****
--- 4615,4648 ----
/*****************************************************************************
*
+ * QUERIES:
+ * CREATE DIRALIAS <name> AS <path>
+ * ALTER DIRALIAS <name> AS <path>
+ *
+ *****************************************************************************/
+
+ CreateDirAliasStmt:
+ CREATE DIRALIAS name AS Sconst
+ {
+ CreateDirAliasStmt *n = makeNode(CreateDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+ AlterDirAliasStmt:
+ ALTER DIRALIAS name AS Sconst
+ {
+ AlterDirAliasStmt *n = makeNode(AlterDirAliasStmt);
+ n->name = $3;
+ n->path = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
* QUERIES :
* CREATE TRIGGER ...
* DROP TRIGGER ...
*************** drop_type: TABLE { $$ = OBJECT_T
*** 5481,5486 ****
--- 5518,5524 ----
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| SCHEMA { $$ = OBJECT_SCHEMA; }
| EXTENSION { $$ = OBJECT_EXTENSION; }
+ | DIRALIAS { $$ = OBJECT_DIRALIAS; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
*************** opt_granted_by: GRANTED BY RoleId {
*** 6314,6319 ****
--- 6352,6418 ----
/*****************************************************************************
*
+ * QUERIES:
+ * GRANT ON DIRALIAS <alias> <permissions> TO <roles>
+ * REVOKE ON DIRALIAS <alias> <permsissions> FROM <roles>
+ *
+ *****************************************************************************/
+ GrantDirStmt:
+ GRANT ON DIRALIAS name_list dir_permissions TO grantee_list
+ opt_granted_by
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = true;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ n->grantor = $8;
+ $$ = (Node*)n;
+ }
+ ;
+
+ RevokeDirStmt:
+ REVOKE ON DIRALIAS name_list dir_permissions FROM grantee_list
+ {
+ GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt);
+ n->is_grant = false;
+ n->directories = $4;
+ n->permissions = $5;
+ n->grantees = $7;
+ $$ = (Node*)n;
+ }
+ ;
+
+ /* either ALL or a list of individual permissions */
+ dir_permissions: dir_perm_list
+ { $$ = $1; }
+ | ALL { $$ = NIL; }
+ ;
+
+
+ dir_perm_list: dir_perm_opts { $$ = list_make1($1); }
+ | dir_perm_list ',' dir_perm_opts { $$ = lappend($1, $3); }
+ ;
+
+ dir_perm_opts:
+ READ
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("select");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ | WRITE
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup("update");
+ n->cols = NIL;
+ $$ = (Node*)n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
* ALTER DEFAULT PRIVILEGES statement
*
*****************************************************************************/
*************** RenameStmt: ALTER AGGREGATE func_name ag
*** 7273,7278 ****
--- 7372,7386 ----
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER DIRALIAS name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_DIRALIAS;
+ n->object = list_make1(makeString($3));
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
| ALTER DOMAIN_P any_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
*************** unreserved_keyword:
*** 13051,13056 ****
--- 13159,13165 ----
| DELIMITER
| DELIMITERS
| DICTIONARY
+ | DIRALIAS
| DISABLE_P
| DISCARD
| DOCUMENT_P
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
new file mode 100644
index 4a2a339..4980016
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 33,38 ****
--- 33,39 ----
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+ #include "commands/diralias.h"
#include "commands/discard.h"
#include "commands/event_trigger.h"
#include "commands/explain.h"
*************** check_xact_readonly(Node *parsetree)
*** 185,190 ****
--- 186,192 ----
case T_DropRoleStmt:
case T_GrantStmt:
case T_GrantRoleStmt:
+ case T_GrantDirAliasStmt:
case T_AlterDefaultPrivilegesStmt:
case T_TruncateStmt:
case T_DropOwnedStmt:
*************** standard_ProcessUtility(Node *parsetree,
*** 557,562 ****
--- 559,568 ----
GrantRole((GrantRoleStmt *) parsetree);
break;
+ case T_GrantDirAliasStmt:
+ ExecuteGrantDirAliasStmt((GrantDirAliasStmt *) parsetree);
+ break;
+
case T_CreatedbStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
*************** ProcessUtilitySlow(Node *parsetree,
*** 1329,1334 ****
--- 1335,1348 ----
AlterPolicy((AlterPolicyStmt *) parsetree);
break;
+ case T_CreateDirAliasStmt: /* CREATE DIRALIAS */
+ CreateDirAlias((CreateDirAliasStmt *) parsetree);
+ break;
+
+ case T_AlterDirAliasStmt: /* ALTER DIRALIAS */
+ AlterDirAlias((AlterDirAliasStmt *) parsetree);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(parsetree));
*************** AlterObjectTypeCommandTag(ObjectType obj
*** 1596,1601 ****
--- 1610,1618 ----
case OBJECT_DATABASE:
tag = "ALTER DATABASE";
break;
+ case OBJECT_DIRALIAS:
+ tag = "ALTER DIRALIAS";
+ break;
case OBJECT_DOMAIN:
tag = "ALTER DOMAIN";
break;
*************** CreateCommandTag(Node *parsetree)
*** 1959,1964 ****
--- 1976,1984 ----
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
+ case OBJECT_DIRALIAS:
+ tag = "DROP DIRALIAS";
+ break;
default:
tag = "???";
}
*************** CreateCommandTag(Node *parsetree)
*** 2016,2021 ****
--- 2036,2049 ----
}
break;
+ case T_GrantDirAliasStmt:
+ {
+ GrantDirAliasStmt *stmt = (GrantDirAliasStmt *) parsetree;
+
+ tag = (stmt->is_grant ? "GRANT ON DIRALIAS" : "REVOKE ON DIRALIAS");
+ }
+ break;
+
case T_GrantRoleStmt:
{
GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
*************** CreateCommandTag(Node *parsetree)
*** 2310,2315 ****
--- 2338,2351 ----
tag = "ALTER POLICY";
break;
+ case T_CreateDirAliasStmt:
+ tag = "CREATE DIRALIAS";
+ break;
+
+ case T_AlterDirAliasStmt:
+ tag = "ALTER DIRALIAS";
+ break;
+
case T_PrepareStmt:
tag = "PREPARE";
break;
*************** GetCommandLogLevel(Node *parsetree)
*** 2862,2867 ****
--- 2898,2911 ----
lev = LOGSTMT_DDL;
break;
+ case T_CreateDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_AlterDirAliasStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterTSDictionaryStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
new file mode 100644
index dc6eb2c..7cc00ef
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** acldefault(GrantObjectType objtype, Oid
*** 790,795 ****
--- 790,799 ----
world_default = ACL_USAGE;
owner_default = ACL_ALL_RIGHTS_TYPE;
break;
+ case ACL_OBJECT_DIRALIAS:
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_ALL_RIGHTS_DIRALIAS;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
world_default = ACL_NO_RIGHTS; /* keep compiler quiet */
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
new file mode 100644
index 3a0957f..58dd1dc
*** a/src/backend/utils/adt/genfile.c
--- b/src/backend/utils/adt/genfile.c
***************
*** 22,32 ****
--- 22,34 ----
#include "access/htup_details.h"
#include "catalog/pg_type.h"
+ #include "commands/diralias.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
+ #include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
*************** typedef struct
*** 37,82 ****
DIR *dirdesc;
} directory_fctx;
-
/*
! * Convert a "text" filename argument to C string, and check it's allowable.
*
! * Filename may be absolute or relative to the DataDir, but we only allow
! * absolute paths that match DataDir or Log_directory.
*/
! static char *
! convert_and_check_filename(text *arg)
{
! char *filename;
! filename = text_to_cstring(arg);
! canonicalize_path(filename); /* filename can change length here */
! if (is_absolute_path(filename))
! {
! /* Disallow '/a/b/data/..' */
! if (path_contains_parent_reference(filename))
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("reference to parent directory (\"..\") not allowed"))));
! /*
! * Allow absolute paths if within DataDir or Log_directory, even
! * though Log_directory might be outside DataDir.
! */
! if (!path_is_prefix_of_path(DataDir, filename) &&
! (!is_absolute_path(Log_directory) ||
! !path_is_prefix_of_path(Log_directory, filename)))
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("absolute path not allowed"))));
! }
! else if (!path_is_relative_and_below_cwd(filename))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("path must be in or below the current directory"))));
! return filename;
}
--- 39,82 ----
DIR *dirdesc;
} directory_fctx;
/*
! * Check the directory permissions for the provided filename/path.
*
! * The filename must be an absolute path to the file.
*/
! static void
! check_directory_permissions(char *directory)
{
! Oid diralias_id;
! AclResult aclresult;
! /* Do not allow relative paths */
! if (!is_absolute_path(directory))
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("relative path not allowed")));
! /* Search for directory in pg_diralias */
! diralias_id = get_diralias_oid_by_path(directory);
! /*
! * If an entry does not exist for the path in pg_diralias then raise
! * an error.
! */
! if (!OidIsValid(diralias_id))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("directory alias entry for \"%s\" does not exist",
! directory)));
! /* Check directory alias entry permissions */
! aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT);
!
! /* If the current user has insufficient privileges then raise an error */
! if (aclresult != ACLCHECK_OK)
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must have read permissions on directory")));
}
*************** pg_read_file(PG_FUNCTION_ARGS)
*** 173,185 ****
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("must be superuser to read files"))));
! filename = convert_and_check_filename(filename_t);
if (bytes_to_read < 0)
ereport(ERROR,
--- 173,191 ----
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
! /* Convert and cleanup the filename */
! filename = text_to_cstring(filename_t);
! canonicalize_path(filename);
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! {
! directory = pstrdup(filename);
! get_parent_directory(directory);
! check_directory_permissions(directory);
! }
if (bytes_to_read < 0)
ereport(ERROR,
*************** pg_read_file_all(PG_FUNCTION_ARGS)
*** 197,209 ****
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("must be superuser to read files"))));
! filename = convert_and_check_filename(filename_t);
PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
}
--- 203,221 ----
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
! /* Convert and cleanup the filename */
! filename = text_to_cstring(filename_t);
! canonicalize_path(filename);
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! {
! directory = pstrdup(filename);
! get_parent_directory(directory);
! check_directory_permissions(directory);
! }
PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
}
*************** pg_read_binary_file(PG_FUNCTION_ARGS)
*** 218,230 ****
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("must be superuser to read files"))));
! filename = convert_and_check_filename(filename_t);
if (bytes_to_read < 0)
ereport(ERROR,
--- 230,248 ----
int64 seek_offset = PG_GETARG_INT64(1);
int64 bytes_to_read = PG_GETARG_INT64(2);
char *filename;
+ char *directory;
! /* Convert and cleanup the filename */
! filename = text_to_cstring(filename_t);
! canonicalize_path(filename);
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! {
! directory = pstrdup(filename);
! get_parent_directory(directory);
! check_directory_permissions(directory);
! }
if (bytes_to_read < 0)
ereport(ERROR,
*************** pg_read_binary_file_all(PG_FUNCTION_ARGS
*** 242,254 ****
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("must be superuser to read files"))));
! filename = convert_and_check_filename(filename_t);
PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
}
--- 260,278 ----
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
! /* Convert and cleanup the filename */
! filename = text_to_cstring(filename_t);
! canonicalize_path(filename);
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! {
! directory = pstrdup(filename);
! get_parent_directory(directory);
! check_directory_permissions(directory);
! }
PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
}
*************** pg_stat_file(PG_FUNCTION_ARGS)
*** 261,278 ****
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
struct stat fst;
Datum values[6];
bool isnull[6];
HeapTuple tuple;
TupleDesc tupdesc;
! if (!superuser())
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("must be superuser to get file information"))));
! filename = convert_and_check_filename(filename_t);
if (stat(filename, &fst) < 0)
ereport(ERROR,
--- 285,307 ----
{
text *filename_t = PG_GETARG_TEXT_P(0);
char *filename;
+ char *directory;
struct stat fst;
Datum values[6];
bool isnull[6];
HeapTuple tuple;
TupleDesc tupdesc;
! filename = text_to_cstring(filename_t);
! canonicalize_path(filename);
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! {
! directory = pstrdup(filename);
! get_parent_directory(directory);
! check_directory_permissions(directory);
! }
if (stat(filename, &fst) < 0)
ereport(ERROR,
*************** pg_ls_dir(PG_FUNCTION_ARGS)
*** 331,341 ****
struct dirent *de;
directory_fctx *fctx;
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- (errmsg("must be superuser to get directory listings"))));
-
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
--- 360,365 ----
*************** pg_ls_dir(PG_FUNCTION_ARGS)
*** 344,350 ****
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(directory_fctx));
! fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
fctx->dirdesc = AllocateDir(fctx->location);
--- 368,379 ----
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
fctx = palloc(sizeof(directory_fctx));
! fctx->location = text_to_cstring(PG_GETARG_TEXT_P(0));
! canonicalize_path(fctx->location);
!
! /* Superuser is always allowed to bypass directory permissions */
! if (!superuser())
! check_directory_permissions(fctx->location);
fctx->dirdesc = AllocateDir(fctx->location);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
new file mode 100644
index 94d951c..383e1c9
*** a/src/backend/utils/cache/syscache.c
--- b/src/backend/utils/cache/syscache.c
***************
*** 37,42 ****
--- 37,43 ----
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
+ #include "catalog/pg_diralias.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_foreign_data_wrapper.h"
*************** static const struct cachedesc cacheinfo[
*** 366,371 ****
--- 367,383 ----
0
},
8
+ },
+ {DirAliasRelationId, /* DIRALIASOID */
+ DirAliasOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0
+ },
+ 8
},
{EnumRelationId, /* ENUMOID */
EnumOidIndexId,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 8bfc604..da46c9f
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
*************** getSchemaData(Archive *fout, DumpOptions
*** 103,108 ****
--- 103,109 ----
int numForeignServers;
int numDefaultACLs;
int numEventTriggers;
+ int numDirectoryAliases;
if (g_verbose)
write_msg(NULL, "reading schemas\n");
*************** getSchemaData(Archive *fout, DumpOptions
*** 251,256 ****
--- 252,261 ----
write_msg(NULL, "reading row-security policies\n");
getRowSecurity(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading directory aliases\n");
+ getDirectoryAliases(fout, &numDirectoryAliases);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
new file mode 100644
index 259c472..08761e3
*** a/src/bin/pg_dump/dumputils.c
--- b/src/bin/pg_dump/dumputils.c
***************
*** 19,24 ****
--- 19,25 ----
#include "dumputils.h"
#include "parser/keywords.h"
+ #include "pg_backup_utils.h"
/* Globals from keywords.c */
*************** buildACLCommands(const char *name, const
*** 545,554 ****
* wire-in knowledge about the default public privileges for different
* kinds of objects.
*/
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
/*
* We still need some hacking though to cover the case where new default
--- 546,561 ----
* wire-in knowledge about the default public privileges for different
* kinds of objects.
*/
! if (strcmp(type, "DIRALIAS") == 0)
! appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM PUBLIC;\n",
! name);
! else
! {
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
! }
/*
* We still need some hacking though to cover the case where new default
*************** buildACLCommands(const char *name, const
*** 593,608 ****
? strcmp(privswgo->data, "ALL") != 0
: strcmp(privs->data, "ALL") != 0)
{
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
! type, name, fmtId(grantee->data));
if (privs->len > 0)
! appendPQExpBuffer(firstsql,
! "%sGRANT %s ON %s %s TO %s;\n",
! prefix, privs->data, type, name,
! fmtId(grantee->data));
if (privswgo->len > 0)
appendPQExpBuffer(firstsql,
"%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
--- 600,633 ----
? strcmp(privswgo->data, "ALL") != 0
: strcmp(privs->data, "ALL") != 0)
{
! /* Handle special GRANT syntax for DIRALIAS */
! if (strcmp(type, "DIRALIAS") == 0)
! appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s;\n",
! name, fmtId(grantee->data));
! else
! {
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
! type, name, fmtId(grantee->data));
! }
!
if (privs->len > 0)
! {
! /* Handle special GRANT syntax for DIRALIAS */
! if (strcmp(type, "DIRALIAS") == 0)
! appendPQExpBuffer(firstsql,
! "%sGRANT ON DIRALIAS %s %s TO %s;\n",
! prefix, name, privs->data,
! fmtId(grantee->data));
! else
! appendPQExpBuffer(firstsql,
! "%sGRANT %s ON %s %s TO %s;\n",
! prefix, privs->data, type, name,
! fmtId(grantee->data));
! }
!
if (privswgo->len > 0)
appendPQExpBuffer(firstsql,
"%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
*************** buildACLCommands(const char *name, const
*** 622,629 ****
if (privs->len > 0)
{
! appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
! prefix, privs->data, type, name);
if (grantee->len == 0)
appendPQExpBufferStr(secondsql, "PUBLIC;\n");
else if (strncmp(grantee->data, "group ",
--- 647,660 ----
if (privs->len > 0)
{
! /* Handle special GRANT syntax for DIRALIAS */
! if (strcmp(type, "DIRALIAS") == 0)
! appendPQExpBuffer(secondsql, "%sGRANT ON DIRALIAS %s %s TO ",
! prefix, name, privs->data);
! else
! appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ",
! prefix, privs->data, type, name);
!
if (grantee->len == 0)
appendPQExpBufferStr(secondsql, "PUBLIC;\n");
else if (strncmp(grantee->data, "group ",
*************** buildACLCommands(const char *name, const
*** 660,670 ****
*/
if (!found_owner_privs && owner)
{
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
! type, name, fmtId(owner));
}
destroyPQExpBuffer(grantee);
--- 691,708 ----
*/
if (!found_owner_privs && owner)
{
! /* Handle special GRANT syntax for DIRALIAS */
! if (strcmp(type, "DIRALIAS") == 0)
! appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s",
! name, fmtId(owner));
! else
! {
! appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
! if (subname)
! appendPQExpBuffer(firstsql, "(%s)", subname);
! appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
! type, name, fmtId(owner));
! }
}
destroyPQExpBuffer(grantee);
*************** do { \
*** 873,878 ****
--- 911,921 ----
CONVERT_PRIV('r', "SELECT");
CONVERT_PRIV('w', "UPDATE");
}
+ else if (strcmp(type, "DIRALIAS") == 0)
+ {
+ CONVERT_PRIV('r', "READ");
+ CONVERT_PRIV('w', "WRITE");
+ }
else
abort();
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
new file mode 100644
index ed28d36..0449b87
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _getObjectDescription(PQExpBuffer buf, T
*** 3106,3112 ****
strcmp(type, "SCHEMA") == 0 ||
strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
strcmp(type, "SERVER") == 0 ||
! strcmp(type, "USER MAPPING") == 0)
{
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
--- 3106,3113 ----
strcmp(type, "SCHEMA") == 0 ||
strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
strcmp(type, "SERVER") == 0 ||
! strcmp(type, "USER MAPPING") == 0 ||
! strcmp(type, "DIRALIAS") == 0)
{
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3307,3313 ****
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
! strcmp(te->desc, "SERVER") == 0)
{
PQExpBuffer temp = createPQExpBuffer();
--- 3308,3315 ----
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
! strcmp(te->desc, "SERVER") == 0 ||
! strcmp(te->desc, "DIRALIAS") == 0)
{
PQExpBuffer temp = createPQExpBuffer();
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
new file mode 100644
index 1e8f089..2c154c0
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static void dumpUserMappings(Archive *fo
*** 191,196 ****
--- 191,197 ----
const char *servername, const char *namespace,
const char *owner, CatalogId catalogId, DumpId dumpId);
static void dumpDefaultACL(Archive *fout, DumpOptions *dopt, DefaultACLInfo *daclinfo);
+ static void dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo);
static void dumpACL(Archive *fout, DumpOptions *dopt, CatalogId objCatId, DumpId objDumpId,
const char *type, const char *name, const char *subname,
*************** dumpRowSecurity(Archive *fout, DumpOptio
*** 2987,2992 ****
--- 2988,3086 ----
destroyPQExpBuffer(delqry);
}
+ void
+ getDirectoryAliases(Archive *fout, int *numDirectoryAliases)
+ {
+ PQExpBuffer query;
+ PGresult *res;
+ DirectoryAliasInfo *dirinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_dirname;
+ int i_dirpath;
+ int i_rolname;
+ int i_diracl;
+ int i, ntups;
+
+ if (fout->remoteVersion < 90500)
+ return;
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ appendPQExpBuffer(query, "SELECT tableoid, oid, dirname, dirpath, diracl, "
+ "(%s (SELECT relowner FROM pg_catalog.pg_class WHERE oid = tableoid)) AS rolname "
+ "FROM pg_catalog.pg_diralias",
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ dirinfo = (DirectoryAliasInfo *) pg_malloc(ntups * sizeof(DirectoryAliasInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_dirname = PQfnumber(res, "dirname");
+ i_dirpath = PQfnumber(res, "dirpath");
+ i_rolname = PQfnumber(res, "rolname");
+ i_diracl = PQfnumber(res, "diracl");
+
+ for (i = 0; i < ntups; i++)
+ {
+ dirinfo[i].dobj.objType = DO_DIRALIAS;
+ dirinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ dirinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&dirinfo[i].dobj);
+ dirinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_dirname));
+ dirinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ dirinfo[i].dirpath = pg_strdup(PQgetvalue(res, i, i_dirpath));
+ dirinfo[i].diracl = pg_strdup(PQgetvalue(res, i, i_diracl));
+ }
+ }
+
+ static void
+ dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo)
+ {
+ PQExpBuffer create_query;
+ PQExpBuffer delete_query;
+ char *dirname;
+
+ if (!dirinfo->dobj.dump || dopt->dataOnly)
+ return;
+
+ create_query = createPQExpBuffer();
+ delete_query = createPQExpBuffer();
+
+ dirname = pg_strdup(fmtId(dirinfo->dobj.name));
+
+ appendPQExpBuffer(delete_query, "DROP DIRALIAS %s;\n", dirname);
+
+ appendPQExpBuffer(create_query, "CREATE DIRALIAS %s AS \'%s\';\n",
+ dirname, dirinfo->dirpath);
+
+ ArchiveEntry(fout, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ dirinfo->dobj.name,
+ NULL, NULL,
+ dirinfo->rolname, false,
+ "DIRALIAS", SECTION_POST_DATA,
+ create_query->data, delete_query->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump ACL - because of the special GRANT syntax, we cannot use dumpACL */
+ if (dirinfo->diracl)
+ dumpACL(fout, dopt, dirinfo->dobj.catId, dirinfo->dobj.dumpId,
+ "DIRALIAS", dirname, NULL, dirinfo->dobj.name,
+ NULL, dirinfo->rolname,
+ dirinfo->diracl);
+
+ destroyPQExpBuffer(create_query);
+ destroyPQExpBuffer(delete_query);
+ }
+
static void
binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
PQExpBuffer upgrade_buffer,
*************** dumpDumpableObject(Archive *fout, DumpOp
*** 8218,8223 ****
--- 8312,8320 ----
case DO_ROW_SECURITY:
dumpRowSecurity(fout, dopt, (RowSecurityInfo *) dobj);
break;
+ case DO_DIRALIAS:
+ dumpDirectoryAlias(fout, dopt, (DirectoryAliasInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
*************** addBoundaryDependencies(DumpableObject *
*** 15615,15620 ****
--- 15712,15718 ----
case DO_EVENT_TRIGGER:
case DO_DEFAULT_ACL:
case DO_ROW_SECURITY:
+ case DO_DIRALIAS:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
new file mode 100644
index a7eb2fd..14a712c
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
*************** typedef enum
*** 76,82 ****
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
DO_REFRESH_MATVIEW,
! DO_ROW_SECURITY
} DumpableObjectType;
typedef struct _dumpableObject
--- 76,83 ----
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
DO_REFRESH_MATVIEW,
! DO_ROW_SECURITY,
! DO_DIRALIAS
} DumpableObjectType;
typedef struct _dumpableObject
*************** typedef struct _rowSecurityInfo
*** 469,474 ****
--- 470,484 ----
char *rsecwithcheck;
} RowSecurityInfo;
+ typedef struct _directoryAliasInfo
+ {
+ DumpableObject dobj;
+ char *diralias;
+ char *diracl;
+ char *dirpath;
+ char *rolname; /* name of owner */
+ } DirectoryAliasInfo;
+
/* global decls */
extern bool force_quotes; /* double-quotes for identifiers flag */
extern bool g_verbose; /* verbose flag */
*************** extern void getExtensionMembership(Archi
*** 550,554 ****
--- 560,565 ----
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
+ extern void getDirectoryAliases(Archive *fout, int *numDirectoryAliases);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
new file mode 100644
index 030bccc..5cece21
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** static const int oldObjectTypePriority[]
*** 73,79 ****
13, /* DO_POST_DATA_BOUNDARY */
20, /* DO_EVENT_TRIGGER */
15, /* DO_REFRESH_MATVIEW */
! 21 /* DO_ROW_SECURITY */
};
/*
--- 73,80 ----
13, /* DO_POST_DATA_BOUNDARY */
20, /* DO_EVENT_TRIGGER */
15, /* DO_REFRESH_MATVIEW */
! 21, /* DO_ROW_SECURITY */
! 22, /* DO_DIRALIAS */
};
/*
*************** static const int newObjectTypePriority[]
*** 122,128 ****
25, /* DO_POST_DATA_BOUNDARY */
32, /* DO_EVENT_TRIGGER */
33, /* DO_REFRESH_MATVIEW */
! 34 /* DO_ROW_SECURITY */
};
static DumpId preDataBoundId;
--- 123,130 ----
25, /* DO_POST_DATA_BOUNDARY */
32, /* DO_EVENT_TRIGGER */
33, /* DO_REFRESH_MATVIEW */
! 34, /* DO_ROW_SECURITY */
! 35, /* DO_DIRALIAS */
};
static DumpId preDataBoundId;
*************** describeDumpableObject(DumpableObject *o
*** 1443,1448 ****
--- 1445,1455 ----
"ROW-SECURITY POLICY (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
+ case DO_DIRALIAS:
+ snprintf(buf, bufsize,
+ "DIRECTORY ALIAS (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_PRE_DATA_BOUNDARY:
snprintf(buf, bufsize,
"PRE-DATA BOUNDARY (ID %d)",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
new file mode 100644
index 6a4913a..1779bc0
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
*************** typedef enum ObjectClass
*** 148,153 ****
--- 148,154 ----
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_ROWSECURITY, /* pg_rowsecurity */
+ OCLASS_DIRALIAS, /* pg_diralias */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
new file mode 100644
index 870692c..ce7464a
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 299,304 ****
--- 299,310 ----
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
+ DECLARE_UNIQUE_INDEX(pg_diralias_oid_index, 6101, on pg_diralias using btree(oid oid_ops));
+ #define DirAliasOidIndexId 6101
+
+ DECLARE_UNIQUE_INDEX(pg_diralias_name_index, 6102, on pg_diralias using btree(dirname name_ops));
+ #define DirAliasNameIndexId 6102
+
DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops));
#define RowSecurityOidIndexId 3257
diff --git a/src/include/catalog/pg_diralias.h b/src/include/catalog/pg_diralias.h
new file mode 100644
index ...e58f047
*** a/src/include/catalog/pg_diralias.h
--- b/src/include/catalog/pg_diralias.h
***************
*** 0 ****
--- 1,46 ----
+ /*
+ * pg_diralias.h
+ * definition of the system catalog for directory permissions (pg_diralias)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+ #ifndef PG_DIRALIAS_H
+ #define PG_DIRALIAS_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_diralias definition. cpp turns this into
+ * typedef struct FormData_pg_diralias
+ * ----------------
+ */
+ #define DirAliasRelationId 6100
+
+ CATALOG(pg_diralias,6100)
+ {
+ NameData dirname; /* directory alias name */
+ text dirpath; /* directory path */
+ #ifdef CATALOG_VARLEN
+ aclitem diracl[1]; /* directory permissions */
+ #endif
+ } FormData_pg_diralias;
+
+ /* ----------------
+ * Form_pg_diralias corresponds to a pointer to a row with
+ * the format of pg_diralias relation.
+ * ----------------
+ */
+ typedef FormData_pg_diralias *Form_pg_diralias;
+
+ /* ----------------
+ * compiler constants for pg_diralias
+ * ----------------
+ */
+ #define Natts_pg_diralias 3
+ #define Anum_pg_diralias_dirname 1
+ #define Anum_pg_diralias_dirpath 2
+ #define Anum_pg_diralias_diracl 3
+
+ #endif /* PG_DIRALIAS_H */
\ No newline at end of file
diff --git a/src/include/commands/diralias.h b/src/include/commands/diralias.h
new file mode 100644
index ...fc2cf23
*** a/src/include/commands/diralias.h
--- b/src/include/commands/diralias.h
***************
*** 0 ****
--- 1,29 ----
+ /*-------------------------------------------------------------------------
+ *
+ * diralias.h
+ * prototypes for diralias.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/diralias.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef DIRECTORY_H
+ #define DIRECTORY_H
+
+ #include "nodes/parsenodes.h"
+
+ extern void RemoveDirAliasById(Oid dir_id);
+ extern void CreateDirAlias(CreateDirAliasStmt *stmt);
+ extern void AlterDirAlias(AlterDirAliasStmt *stmt);
+
+ extern char *get_diralias_name(Oid dir_id);
+ extern Oid get_diralias_oid(const char *name, bool missing_ok);
+ extern Oid get_diralias_owner(Oid dir_id);
+ extern Oid get_diralias_oid_by_path(const char *path);
+
+ #endif /* DIRECTORY_H */
\ No newline at end of file
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 154d943..6b293aa
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 278,283 ****
--- 278,284 ----
T_AlterDomainStmt,
T_SetOperationStmt,
T_GrantStmt,
+ T_GrantDirAliasStmt,
T_GrantRoleStmt,
T_AlterDefaultPrivilegesStmt,
T_ClosePortalStmt,
*************** typedef enum NodeTag
*** 368,373 ****
--- 369,376 ----
T_AlterSystemStmt,
T_CreatePolicyStmt,
T_AlterPolicyStmt,
+ T_CreateDirAliasStmt,
+ T_AlterDirAliasStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index cef9544..4b7197c
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum ObjectType
*** 1212,1217 ****
--- 1212,1218 ----
OBJECT_COLLATION,
OBJECT_CONVERSION,
OBJECT_DATABASE,
+ OBJECT_DIRALIAS,
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
*************** typedef enum GrantObjectType
*** 1412,1417 ****
--- 1413,1419 ----
ACL_OBJECT_LARGEOBJECT, /* largeobject */
ACL_OBJECT_NAMESPACE, /* namespace */
ACL_OBJECT_TABLESPACE, /* tablespace */
+ ACL_OBJECT_DIRALIAS, /* directory alias */
ACL_OBJECT_TYPE /* type */
} GrantObjectType;
*************** typedef struct GrantRoleStmt
*** 1483,1488 ****
--- 1485,1504 ----
} GrantRoleStmt;
/* ----------------------
+ * Grant/Revoke Directory Permission Statement
+ * ----------------------
+ */
+ typedef struct GrantDirAliasStmt
+ {
+ NodeTag type;
+ List *directories; /* the directory alias/name */
+ List *permissions; /* list of permission to be granted/revoked */
+ List *grantees; /* list of roles to be granted/revoked permission */
+ bool is_grant; /* true = GRANT, false = REVOKE */
+ char *grantor; /* set grantor to other than current role */
+ } GrantDirAliasStmt;
+
+ /* ----------------------
* Alter Default Privileges Statement
* ----------------------
*/
*************** typedef struct AlterPolicyStmt
*** 1890,1895 ****
--- 1906,1933 ----
} AlterPolicyStmt;
/* ----------------------
+ * Create DIRALIAS Statement
+ * ----------------------
+ */
+ typedef struct CreateDirAliasStmt
+ {
+ NodeTag type;
+ char *name;
+ char *path;
+ } CreateDirAliasStmt;
+
+ /* ----------------------
+ * Alter DIRALIAS Statement
+ * ----------------------
+ */
+ typedef struct AlterDirAliasStmt
+ {
+ NodeTag type;
+ char *name;
+ char *path;
+ } AlterDirAliasStmt;
+
+ /* ----------------------
* Create TRIGGER Statement
* ----------------------
*/
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
new file mode 100644
index e14dc9a..eb31cf7
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
*************** PG_KEYWORD("delimiter", DELIMITER, UNRES
*** 125,130 ****
--- 125,131 ----
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
+ PG_KEYWORD("diralias", DIRALIAS, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index a8e3164..cb75b1a
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** typedef ArrayType Acl;
*** 156,161 ****
--- 156,162 ----
#define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+ #define ACL_ALL_RIGHTS_DIRALIAS (ACL_SELECT|ACL_UPDATE)
/* operation codes for pg_*_aclmask */
typedef enum
*************** typedef enum AclObjectKind
*** 197,202 ****
--- 198,204 ----
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_DIRALIAS, /* pg_diralias */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
*************** extern Datum aclexplode(PG_FUNCTION_ARGS
*** 255,260 ****
--- 257,263 ----
*/
extern void ExecuteGrantStmt(GrantStmt *stmt);
extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt);
+ extern void ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt);
extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid);
extern void RemoveDefaultACLById(Oid defaclOid);
*************** extern AclMode pg_foreign_server_aclmask
*** 281,286 ****
--- 284,291 ----
AclMode mask, AclMaskHow how);
extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid,
AclMode mask, AclMaskHow how);
+ extern AclMode pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask,
+ AclMaskHow how);
extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
Oid roleid, AclMode mode);
*************** extern AclResult pg_tablespace_aclcheck(
*** 297,302 ****
--- 302,308 ----
extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode);
extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode);
extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode);
+ extern AclResult pg_diralias_aclcheck(Oid diroid, Oid roleid, AclMode mode);
extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
const char *objectname);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
new file mode 100644
index f97229f..c4b822e
*** a/src/include/utils/syscache.h
--- b/src/include/utils/syscache.h
*************** enum SysCacheIdentifier
*** 54,59 ****
--- 54,60 ----
CONVOID,
DATABASEOID,
DEFACLROLENSPOBJ,
+ DIRALIASOID,
ENUMOID,
ENUMTYPOIDNAME,
EVENTTRIGGERNAME,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index 2c8ec11..334f648
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
*************** pg_db_role_setting|t
*** 102,107 ****
--- 102,108 ----
pg_default_acl|t
pg_depend|t
pg_description|t
+ pg_diralias|t
pg_enum|t
pg_event_trigger|t
pg_extension|t
I think the way this should work is that if you create a DIRALIAS, then
the COPY command should refer to it by logical name, e.g.,
CREATE DIRALIAS dumpster AS '/tmp/trash';
COPY mytable TO dumpster;
If you squint a bit, this is the same as a tablespace. Maybe those two
concepts could be combined.
On the other hand, we already have file_fdw, which does something very
similar.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/27/14 7:27 AM, Stephen Frost wrote:
* Peter Eisentraut (peter_e@gmx.net) wrote:
On 10/16/14 12:01 PM, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server.I think that can be done with a security-definer function.
Of course it can be. We could replace the entire authorization system
with security definer functions too.
I don't think that is correct.
It's easy to do something with security definer functions if it's single
purpose, with a single argument, like load this file into this table,
let these users do it.
It's not easy to do it with functions if you have many parameters, like
in a general SELECT statement.
So I would like to see at least three wildly different use cases for
this before believing that a security definer function isn't appropriate.
I don't view this as an argument
against this feature, particularly as we know other systems have it,
users have asked for multiple times, and large PG deployments have had
to hack around our lack of it.
What other systems have it? Do you have links to their documentation?
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Eisentraut (peter_e@gmx.net) wrote:
On 10/27/14 7:27 AM, Stephen Frost wrote:
* Peter Eisentraut (peter_e@gmx.net) wrote:
On 10/16/14 12:01 PM, Stephen Frost wrote:
This started out as a request for a non-superuser to be able to review
the log files without needing access to the server.I think that can be done with a security-definer function.
Of course it can be. We could replace the entire authorization system
with security definer functions too.I don't think that is correct.
Of course it is- you simply have to move all the logic into the
function.
It's easy to do something with security definer functions if it's single
purpose, with a single argument, like load this file into this table,
let these users do it.
The files won't be consistently named and there may be cases to make
ad-hoc runs or test runs. No, it isn't as simple as always being a
single, specific filename and when consider that there needs to be
intelligence about the actual path being specified and making sure that
there can't be '..' and similar, it gets to be a pretty ugly situation
to make our users have to deal with.
It's not easy to do it with functions if you have many parameters, like
in a general SELECT statement.
You could define SRFs for every table.
So I would like to see at least three wildly different use cases for
this before believing that a security definer function isn't appropriate.
I'm not following this- there's probably 100s of use-cases for this, but
they're all variations n 'read and/or write data server-side instead of
through a front-end connection', which is what the purpose of the
feature is.. I do see this as being useful for COPY, Large Object, and
the file_fdw...
I don't view this as an argument
against this feature, particularly as we know other systems have it,
users have asked for multiple times, and large PG deployments have had
to hack around our lack of it.What other systems have it? Do you have links to their documentation?
MySQL:
http://dev.mysql.com/doc/refman/5.1/en/privileges-provided.html#priv_file
(note they provide a way to limit access also, via secure_file_priv)
Oracle:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5007.htm
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9013.htm#i2125999
SQL Server:
http://msdn.microsoft.com/en-us/library/ms175915.aspx
(Note: they can actually run as the user connected instead of the SQL DB
server, if Windows authentication is used, which is basically doing
Kerberos proxying unless I'm mistaken; it's unclear how the security is
maintained if it's a SQL server logon user..).
etc...
Thanks,
Stephen
* Peter Eisentraut (peter_e@gmx.net) wrote:
I think the way this should work is that if you create a DIRALIAS, then
the COPY command should refer to it by logical name, e.g.,CREATE DIRALIAS dumpster AS '/tmp/trash';
COPY mytable TO dumpster;
You'd have to be able to specify the filename also. I'm not against the
idea of using the 'diralias' alias name this way, just saying it isn't
quite as simple as the above.
If you squint a bit, this is the same as a tablespace. Maybe those two
concepts could be combined.
CREATE TABLESPACE is something else which could be supported with
diralias, though it'd have to be an independently grantable capability
and it'd be a bad idea to let a user create tablespaces in a directory
and then also be able to copy from/to files there (backend crashes,
etc). This exact capability is more-or-less what RDS has had to hack on
to PG for their environment, as I understand it, in case you're looking
for a use-case.
On the other hand, we already have file_fdw, which does something very
similar.
It's really not at all the same.. Perhaps we'll get there some day, but
we're a very long way away from file_fdw having the ability to replace
normal tablespaces...
Thanks!
Stephen
On Mon, Oct 27, 2014 at 5:59 PM, Adam Brightwell
<adam.brightwell@crunchydatasolutions.com> wrote:
Attached is a patch with minor updates/corrections.
Given that no fewer than four people - all committers - have expressed
doubts about the design of this patch, I wonder why you're bothering
to post a new version. It seems to me that you should be discussing
the fundamental design, not making minor updates to the code. I
really hope this is not moving in the direction of another "surprise
commit" like we had with RLS. There is absolutely NOT consensus on
this design or anything close to it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Robert Haas (robertmhaas@gmail.com) wrote:
There is absolutely NOT consensus on
this design or anything close to it.
There is no doubt that consensus on the desirability and design needs
to be reached before we can even consider committing it. I suspect
Adam posted it simply because he had identified issues himself and
wanted to make others aware that things had been fixed.
That said, it sounds like the primary concern has been if we want this
feature at all and there hasn't been much discussion of the design
itself. Comments about the technical design would be great. I
appreciate your thoughts about using a PGC_SUSER GUC, but I don't feel
like it really works as it's all-or-nothing and doesn't provide
read-vs-write, unless we extend it out to be multiple GUCs and then
there is still the question about per-role access..
I'm not sure that I see a way to allow the per-role granularity without
having a top-level catalog object on which the GRANT can be executed and
ACL information stored. Perhaps it's unfortunate that we don't have a
more generic way to address that but I'm not sure I really see another
catalog table as a big problem..
Thanks!
Stephen
On 2014-10-28 09:24:18 -0400, Stephen Frost wrote:
* Robert Haas (robertmhaas@gmail.com) wrote:
There is absolutely NOT consensus on
this design or anything close to it.There is no doubt that consensus on the desirability and design needs
to be reached before we can even consider committing it. I suspect
Adam posted it simply because he had identified issues himself and
wanted to make others aware that things had been fixed.That said, it sounds like the primary concern has been if we want this
feature at all and there hasn't been much discussion of the design
itself.
Well, why waste time on the technical details when we haven't agreed
that the feature is worthwile? Review bandwidth is a serious problem in
this community.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Andres Freund (andres@2ndquadrant.com) wrote:
On 2014-10-28 09:24:18 -0400, Stephen Frost wrote:
There is no doubt that consensus on the desirability and design needs
to be reached before we can even consider committing it. I suspect
Adam posted it simply because he had identified issues himself and
wanted to make others aware that things had been fixed.That said, it sounds like the primary concern has been if we want this
feature at all and there hasn't been much discussion of the design
itself.Well, why waste time on the technical details when we haven't agreed
that the feature is worthwile? Review bandwidth is a serious problem in
this community.
Fair enough, and I'm happy to discuss that (and have been..); I was
simply objecting to the implication that the desirability concerns
raised were design concerns- the only design concern raised was wrt
it being possibly too heavyweight and the PGC_SUSET GUC suggestion (at
least, based on my re-reading of the thread..).
Thanks!
Stephen
On Tue, Oct 28, 2014 at 9:24 AM, Stephen Frost <sfrost@snowman.net> wrote:
That said, it sounds like the primary concern has been if we want this
feature at all and there hasn't been much discussion of the design
itself. Comments about the technical design would be great. I
appreciate your thoughts about using a PGC_SUSER GUC, but I don't feel
like it really works as it's all-or-nothing and doesn't provide
read-vs-write, unless we extend it out to be multiple GUCs and then
there is still the question about per-role access..
It sounds to me like you've basically settled on the way that you want
to implement it - without prior discussion on the mailing list - and
you're not trying very hard to make any of the alternatives work.
It's not the community's job to come up with a design that satisfies
you; it's your job to come up with as design that satisfies the
community. That doesn't *necessarily* mean that you have to change
the design that you've come up with; convincing other people that your
design is the best one is also an option. But I don't see that you're
making any real attempt to do that.
Your previous comment on the idea of a PGC_SUSET GUC was "Hrm, perhaps
this would work though.." and then, with zero further on-list
discussion, you've arrived at "I don't feel like it really works as
it's all-or-nothing and doesn't provide read-vs-write". Those are
precisely the kinds of issues that you should be discussing here in
detail, not cogitating on in isolation and then expecting this group
of people to accept that your original design is really for the best
after all.
I also find your technical arguments - to the extent that you've
bothered to articulate them at all - to be without merit. The
"question about per-role access" is easily dealt with, so let's start
there: if you make it a GUC, ALTER USER .. SET can be used to set
different values for different users. No problem. Your other
criticism that it is "all-vs-nothing" seems to me to be totally
incomprehensible, since as far as I can see a GUC with a list of
pathnames is exactly the same functionality that you're proposing to
implement via a much more baroque syntax. It is no more or less
all-or-nothing than that. Finally, you mention "read-vs-write"
access. You haven't even attempted to argue that we need to make that
distinction - in fact, you don't seem to have convinced a
significantly majority of the people that we need this feature at all
- but if we do, the fact that it might require two GUCs instead of one
is not a fatal objection to that design. (I'd be prepared to concede
that if there are half a dozen different privileges on directories
that we might want to grant, then wedging it into a GUC might be a
stretch.)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Robert Haas (robertmhaas@gmail.com) wrote:
On Tue, Oct 28, 2014 at 9:24 AM, Stephen Frost <sfrost@snowman.net> wrote:
That said, it sounds like the primary concern has been if we want this
feature at all and there hasn't been much discussion of the design
itself. Comments about the technical design would be great. I
appreciate your thoughts about using a PGC_SUSER GUC, but I don't feel
like it really works as it's all-or-nothing and doesn't provide
read-vs-write, unless we extend it out to be multiple GUCs and then
there is still the question about per-role access..It sounds to me like you've basically settled on the way that you want
to implement it - without prior discussion on the mailing list - and
you're not trying very hard to make any of the alternatives work.
I'm happy to put more effort into alternatives, was just trying to
outline what capabilities I felt it should have and make sure others
proposing designs understood the granularity requested.
It's not the community's job to come up with a design that satisfies
you; it's your job to come up with as design that satisfies the
community. That doesn't *necessarily* mean that you have to change
the design that you've come up with; convincing other people that your
design is the best one is also an option. But I don't see that you're
making any real attempt to do that.
There was only one other design to contrast against- the rest has been
concern about the desirability, which is what I've been trying to
address by responding to Peter's request about documentation and how
this capability exists in other systems.
Your previous comment on the idea of a PGC_SUSET GUC was "Hrm, perhaps
this would work though.." and then, with zero further on-list
discussion, you've arrived at "I don't feel like it really works as
it's all-or-nothing and doesn't provide read-vs-write". Those are
precisely the kinds of issues that you should be discussing here in
detail, not cogitating on in isolation and then expecting this group
of people to accept that your original design is really for the best
after all.
Alright, I'll try and outline a more detailed proposal which uses GUCs
to achieve the level of granularity that is being sought and we can
discuss it.
I also find your technical arguments - to the extent that you've
bothered to articulate them at all - to be without merit. The
"question about per-role access" is easily dealt with, so let's start
there: if you make it a GUC, ALTER USER .. SET can be used to set
different values for different users. No problem.
No, I simply hadn't thought about that approach and I'm glad that you're
clarifying it.. I'll think about it more but my initial concern is
being able to identify everything a user has access to would then become
more complex as you'd have to consider what special GUCs they have set
in pg_config. I see how what you're proposing would work there though.
Your other
criticism that it is "all-vs-nothing" seems to me to be totally
incomprehensible, since as far as I can see a GUC with a list of
pathnames is exactly the same functionality that you're proposing to
implement via a much more baroque syntax. It is no more or less
all-or-nothing than that.
Apologies about not being clear- that 'all-or-nothing' was without
considering using a per-user GUC to control it; I had thought the
proposal was a single GUC and then a role attribute which said if a
given role could access everything in the global list or not. Using a
per-role GUC solves that.
Finally, you mention "read-vs-write"
access. You haven't even attempted to argue that we need to make that
distinction
The use-case that I had described up-thread, I had thought, made it
clear that there will be cases where a user should have only read-only
access to a directory (able to import log files) and cases where a user
should be able to write to a directory (exporting to an NFS mount or
similar).
- in fact, you don't seem to have convinced a
significantly majority of the people that we need this feature at all
That's certainly what I've been primairly focused on addressing as it's
the first hurdle to jump. As I mentioned to Bruce, I didn't realize
there was really a question about that, but evidently that was
incorrect and I'm working to rectify the situation.
- but if we do, the fact that it might require two GUCs instead of one
is not a fatal objection to that design. (I'd be prepared to concede
that if there are half a dozen different privileges on directories
that we might want to grant, then wedging it into a GUC might be a
stretch.)
There are more capabilities that I've been considering longer-term but
wasn't sure if they should be independent or just lumped into the
simpler read/write category:
read (eg: importing log files, or importing from an NFS mount)
write (eg: exporting to NFS mount)
tablespace (eg: create a tablespace in a subdir of a directory)
create directory (eg: create subdirs)
modify permissions (eg: allow users other than pg to read/write/etc)
directory listing
large-object import/export (might be same as read/write)
COPY PIPE
I can see cases where you wouldn't want to allow a directory listing,
but would want to allow read (or write), for example. There are also
cases where you would or wouldn't want a given user to be able to chmod
the files. I'm not sure that it should matter if it's a large object or
not, or if it's being used by file_fdw vs. normal COPY, but they're
certainly things to consider. The tablespace case is one which I think
really needs a lot of consideration since it could be particularly
dangerous, as well as COPY PIPE (which might not ever be able to be for
non-superuser, in the end..).
I'll discuss with Adam putting a wiki together which outlines the use
cases and rationale for them and hopefully that'll lead into a better
discussion about the possible permissions which would make sense to
exist for these and that may inform us as to if a GUC-based approach
would work. I'm still unsure about using GUCs to define permissions in
this way. That feels novel to me for PG to do, but I'll admit that I
may just be ignorant or forgetting existing cases where we do that.
Thanks for explaining the GUC-based proposal further.
Stephen
Robert,
Given that no fewer than four people - all committers - have expressed
doubts about the design of this patch, I wonder why you're bothering
to post a new version.
I understand and my intent was in no way to disregard those concerns. The
only reason that I have posted a new version was simply to address some
minor issues that I noticed when responding to Peter's earlier comment
about missing files.
It seems to me that you should be discussing
the fundamental design, not making minor updates to the code.
Ok. I'm certainly looking at the other options proposed and will work with
Stephen to put together an appropriate design for discussion here.
I really hope this is not moving in the direction of another "surprise
commit" like we had with RLS. There is absolutely NOT consensus on
this design or anything close to it.
Certainly not and I am in no way confused that consensus has not been
reached.
-Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
On Tue, Oct 28, 2014 at 11:16 AM, Stephen Frost <sfrost@snowman.net> wrote:
There are more capabilities that I've been considering longer-term but
wasn't sure if they should be independent or just lumped into the
simpler read/write category:read (eg: importing log files, or importing from an NFS mount)
write (eg: exporting to NFS mount)
tablespace (eg: create a tablespace in a subdir of a directory)
create directory (eg: create subdirs)
modify permissions (eg: allow users other than pg to read/write/etc)
directory listing
large-object import/export (might be same as read/write)
COPY PIPE
I think it would be a good idea to figure out how this fits together
and propose a design that covers all the cases you think are
important, and then see how many of them the community agrees are
important. I have no problem with incremental commits moving toward
an agreed-upon design, but it's important that we don't go off in one
directly and then have to reverse course, because it creates upgrade
problems for our users.
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created. The users are
still operating on pathnames. That's awfully strange. Number two,
every other SQL object we have has a name that is one or several
English words. DIRALIAS does not appear in any dictionary. The
second objection can be answered by renaming the facility, but the
first one is not so straightforward.
I'll discuss with Adam putting a wiki together which outlines the use
cases and rationale for them and hopefully that'll lead into a better
discussion about the possible permissions which would make sense to
exist for these and that may inform us as to if a GUC-based approach
would work. I'm still unsure about using GUCs to define permissions in
this way. That feels novel to me for PG to do, but I'll admit that I
may just be ignorant or forgetting existing cases where we do that.
Well, there's temp_file_limit, for example. That's not exactly the
same, but it bears a passing resemblance.
I'm definitely not saying that the GUC-based proposal is perfect. It
isn't, and if we're going to need a whole bunch of different
permissions that are all per-directory, that could get ugly in a
hurry. My points are (1) the community does not have to accept this
feature just because you propose it, and in fact there's a good
argument for rejecting it outright, which is that very few users are
going to get any benefit out of this, and it might end up being a
whole lot of code; and (2) the pros and cons of accepting this at all,
and of different designs, need to be debated here, on this list, in an
open way.
I think it would help, on all accounts, to explain why in the world
we're spending time on this in the first place. I have a sneaking
suspicion this is 1 of N things we need to do to meet some US
government security standard, and if something like that is the case,
that could tip the balance toward doing it, or toward a particular
implementation of the concept. From my point of view, if you made a
list of all of the annoyances of using PostgreSQL and listed them in
order of importance, you'd burn through a fair amount of paper before
reaching this one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Oct 28, 2014 at 11:33 AM, Adam Brightwell
<adam.brightwell@crunchydatasolutions.com> wrote:
Given that no fewer than four people - all committers - have expressed
doubts about the design of this patch, I wonder why you're bothering
to post a new version.I understand and my intent was in no way to disregard those concerns. The
only reason that I have posted a new version was simply to address some
minor issues that I noticed when responding to Peter's earlier comment about
missing files.It seems to me that you should be discussing
the fundamental design, not making minor updates to the code.Ok. I'm certainly looking at the other options proposed and will work with
Stephen to put together an appropriate design for discussion here.I really hope this is not moving in the direction of another "surprise
commit" like we had with RLS. There is absolutely NOT consensus on
this design or anything close to it.Certainly not and I am in no way confused that consensus has not been
reached.
OK, thanks.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> wrote:
I think it would help, on all accounts, to explain why in the world
we're spending time on this in the first place. I have a sneaking
suspicion this is 1 of N things we need to do to meet some US
government security standard, and if something like that is the case,
that could tip the balance toward doing it, or toward a particular
implementation of the concept.
Stephen my correct me on this, but I seem to remember him saying
that this was part of a general effort to avoid needing to use a
superuser login for routine tasks that don't fit into the area of
what a sysadmin would do. That seems like a laudable goal to me.
Of course, most or all of what this particular feature would allow
can be done using superuser-owned SECURITY DEFINER functions, but
that is sure a lot clumsier and error-prone than being able to say
that role x can read from directory data/input and role y can write
to directory data/output.
That said, Stephen does seem to have some additional specific use
cases in mind which he hasn't shared with the list; knowing what
problems we're talking about solving would sure help make
discussions about the possible solutions more productive. :-)
--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Kevin,
Thanks.
* Kevin Grittner (kgrittn@ymail.com) wrote:
Stephen my correct me on this, but I seem to remember him saying
that this was part of a general effort to avoid needing to use a
superuser login for routine tasks that don't fit into the area of
what a sysadmin would do. That seems like a laudable goal to me.
Right, and this is one of those things that only a superuser can do now.
I had expected to find other more complicated cases which would require
a generalized "pg_permissions" type of approach but having gone through
the superuser() checks, this is the one case where we really needed a
complex ACL scheme that, in my view at least, warranted a new catalog
table. Rather than come up with "pg_permissions" and some ugly hacks
to make that work for a variety of object types, I looked to address the
specific case of server-side directory access in a way similar to what
other databases already provide.
Of course, most or all of what this particular feature would allow
can be done using superuser-owned SECURITY DEFINER functions, but
that is sure a lot clumsier and error-prone than being able to say
that role x can read from directory data/input and role y can write
to directory data/output.
Exactly.
That said, Stephen does seem to have some additional specific use
cases in mind which he hasn't shared with the list; knowing what
problems we're talking about solving would sure help make
discussions about the possible solutions more productive. :-)
It's actually more-or-less the opposite.. As I think I mentioned at
some point earlier, the original ask was to be able to view logs as a
DBA who isn't a superuser, and without having to have those views
delayed or complex cron jobs running to set up access to them. That's a
*frequently* asked for capability and I don't think this directory type
approach will be the final solution to that specific problem, but it'll
at least get us a lot closer while also providing capabilities that
other databases have and that I've personally wanted for a long time.
In other words, I took the ask and attempted to generalize it out to
cover more use-cases that I've run into which are similar. While I have
ideas and memories about times when I've wanted this capability for
various use-cases, there's not some pre-defined list that I'm hiding
offline in hopes that no one asks for it, nor is it for some government
check-list.
Since there is evidently interest in this, I'll try to provide some
insight into the times I've run into this previously:
The first time I came across COPY and was frustrated that I had to be a
superuser to use it, period. Initially, I didn't realize it could do
STDIN/STDOUT, but even once I discovered that, I felt it was unfortunate
that only a superuser could do it server-side, unlike other databases.
This, in my view, is probably the experience of nearly every new user to
PG and COPY and, basically, it sucks.
Later on, I started writing scripts to do server-side copy to avoid
having to marshall data through whatever-client-API-I'm-using (perl,
php, python, etc) and where I couldn't do that due to not being able to
run as a superuser, I ended up doing ugly things in some cases (like
exec'ing out to psql..) because I couldn't just tell the server "pull
this file in".
In some cases, COPY wasn't even supported by the client library, as I
recall. That's better now, but new languages continue to come out and
often initially support the bare minimum (wasn't ruby initially in this
boat of lacking COPY protocol support initially..?).
Then, when working with Pentaho I came across it again- having to
marshall data through Java and over into PG, and it had to go over a
local TCP connection instead of a unix socket (still the case with our
JDBC driver, no?), primairly to get data into the DB which was out on an
NFS mount in a format that PG could have digested just fine directly or
could have made available via the file_fdw.
Next was the Amazon use-case, which wasn't obvious to me initially but
makes perfect sense now. They want to allow users to add new i/o
channels and use them but can't let users run as the normal PG
superuser, hence the idea about supporting CREATE TABLESPACE with this
same 'diralias' approach.
The thoughts around permissions related to 'diralias' (chmod, mkdir, ls,
etc) are all just based on what unix provides already. Similairly,
extending to support large-object import/export along with COPY just
makes sense, as does supporting the file_fdw with this approach, imv.
The file_fdw case is interesting as it's an extension and we'll need to
be able to provide a clear and simple interface to check if the access
is allowed or not which the file_fdw would then leverage.
Thanks,
Stephen
Robert,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Tue, Oct 28, 2014 at 11:16 AM, Stephen Frost <sfrost@snowman.net> wrote:
There are more capabilities that I've been considering longer-term but
wasn't sure if they should be independent or just lumped into the
simpler read/write category:read (eg: importing log files, or importing from an NFS mount)
write (eg: exporting to NFS mount)
tablespace (eg: create a tablespace in a subdir of a directory)
create directory (eg: create subdirs)
modify permissions (eg: allow users other than pg to read/write/etc)
directory listing
large-object import/export (might be same as read/write)
COPY PIPEI think it would be a good idea to figure out how this fits together
and propose a design that covers all the cases you think are
important, and then see how many of them the community agrees are
important. I have no problem with incremental commits moving toward
an agreed-upon design, but it's important that we don't go off in one
directly and then have to reverse course, because it creates upgrade
problems for our users.
Certainly.
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created.
I agree that this makes it feel awkward. Peter had an interesting
suggestion to make the dir aliases available as actual aliases for the
commands which they would be relevant to. I hadn't considered that- I
proposed 'diralias' because I didn't like 'directory' since we weren't
actually creating *directories* but rather defining aliases to existing
OS directories in PG. Perhaps it wasn't clear at the outset, but this
is all work-in-progress and not intended to be the one-true-solution
from on-high. Apologies if it came across that way.
The users are
still operating on pathnames. That's awfully strange. Number two,
every other SQL object we have has a name that is one or several
English words. DIRALIAS does not appear in any dictionary. The
second objection can be answered by renaming the facility, but the
first one is not so straightforward.
I do think it's important to support subdirectories (the Amazon use-case
is one where this would be required) and allowing users to specify the
specific file names, so we'd have to come up with a way to combine the
alias and the rest of the fully-qualified path. That might not be too
bad but, to me at least, it seemed more natural to just use the full
path. That was from a sysadmin perspective though, from a DBA
perspective, knowing the rest of the path is probably not all that
interesting and using the alias would be simpler for them.
I'll discuss with Adam putting a wiki together which outlines the use
cases and rationale for them and hopefully that'll lead into a better
discussion about the possible permissions which would make sense to
exist for these and that may inform us as to if a GUC-based approach
would work. I'm still unsure about using GUCs to define permissions in
this way. That feels novel to me for PG to do, but I'll admit that I
may just be ignorant or forgetting existing cases where we do that.Well, there's temp_file_limit, for example. That's not exactly the
same, but it bears a passing resemblance.
Hrm, yes, that's PG_SUSET and could be set per-user.
I'm definitely not saying that the GUC-based proposal is perfect. It
isn't, and if we're going to need a whole bunch of different
permissions that are all per-directory, that could get ugly in a
hurry. My points are (1) the community does not have to accept this
feature just because you propose it, and in fact there's a good
argument for rejecting it outright, which is that very few users are
going to get any benefit out of this, and it might end up being a
whole lot of code; and (2) the pros and cons of accepting this at all,
and of different designs, need to be debated here, on this list, in an
open way.
I'd like to think that we're doing (2) now. As for (1), I certainly
feel it's a useful capability and will argue for it, but the community
certainly has the 'final say' on it, of course. I'm optomistic that the
amount of code will be reasonable and that users will benefit from it or
I wouldn't be advocating it, but that's obviously a judgement call and
others will and are certainly entitled to have different opinions.
I think it would help, on all accounts, to explain why in the world
we're spending time on this in the first place.
Because I feel it's a valuable feature...? So does Oracle, MySQL, and
the other databases which support it. This isn't the first time it's
come up either, as I pointed out up-thread.
I have a sneaking
suspicion this is 1 of N things we need to do to meet some US
government security standard, and if something like that is the case,
that could tip the balance toward doing it, or toward a particular
implementation of the concept.
No, it hasn't got anything to do with NIST or other government
standards. Those standards are much more interested in the general
"reduce the need to be a superuser" concept but there's certainly
nothing in there about directory-level access, nor was it even part of
the original discussion that this idea came out of. If there were
specific standards about this, I'd have pointed them out (as I've done
previously...), because solving those cases are valuable to our
community, in my view.
From my point of view, if you made a
list of all of the annoyances of using PostgreSQL and listed them in
order of importance, you'd burn through a fair amount of paper before
reaching this one.
I'm not quite sure what to do with this comment. Perhaps it isn't at
the top of anyone's list (not even mine), but I didn't think we rejected
features because the community feels that some other feature is more
important. If we're going to start doing that then we should probably
come up with a list of what features the community wants, prioritize
them, and require that all committers work towards those features to the
exclusion of their own interests, or those of their employers or the
companies they own/run. I hope I've simply misunderstood the
implication here instead.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> wrote:
the original ask was to be able to view logs as a DBA who isn't a
superuser, and without having to have those views delayed or
complex cron jobs running to set up access to them.
I had kinda forgotten it, but I had to set up a cron log rsync at
Wisconsin Courts. I understand the need.
--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Oct 28, 2014 at 3:19 PM, Stephen Frost <sfrost@snowman.net> wrote:
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created.I agree that this makes it feel awkward. Peter had an interesting
suggestion to make the dir aliases available as actual aliases for the
commands which they would be relevant to. I hadn't considered that- I
proposed 'diralias' because I didn't like 'directory' since we weren't
actually creating *directories* but rather defining aliases to existing
OS directories in PG.
Right. Another way to go at this would be to just ditch the names.
This exact syntax probably wouldn't work (or might not be a good idea)
because GRANT is so badly overloaded already, but conceptually:
GRANT READ ON DIRECTORY '/home/snowman' TO sfrost;
Or maybe some variant of:
ALTER USER sfrost GRANT READ ON DIRECTORY '/home/snowman';
I'm not quite sure what to do with this comment. Perhaps it isn't at
the top of anyone's list (not even mine), but I didn't think we rejected
features because the community feels that some other feature is more
important. If we're going to start doing that then we should probably
come up with a list of what features the community wants, prioritize
them, and require that all committers work towards those features to the
exclusion of their own interests, or those of their employers or the
companies they own/run. I hope I've simply misunderstood the
implication here instead.
No, that's not what I'm saying. Come on. From my point of view what
happened is that a patch implementing a rather specific design for a
problem I personally viewed as somewhat obscure just sort of dropped
out of nowhere; and it came from people working at a company that is
also working on a bunch of other security-related features. I
wondered whether there was more to the story, but I guess not.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/27/14 7:36 PM, Stephen Frost wrote:
MySQL:
http://dev.mysql.com/doc/refman/5.1/en/privileges-provided.html#priv_file(note they provide a way to limit access also, via secure_file_priv)
They have a single privilege to allow the user to read or write any
file. I think that feature could be useful.
Oracle:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5007.htm
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9013.htm#i2125999
From the description, that CREATE DIRECTORY command looks to me more
like a tablespace, or a general BLOB space, that you reference by object
name, not by file name.
SQL Server:
http://msdn.microsoft.com/en-us/library/ms175915.aspx
(Note: they can actually run as the user connected instead of the SQL DB
server, if Windows authentication is used, which is basically doing
Kerberos proxying unless I'm mistaken; it's unclear how the security is
maintained if it's a SQL server logon user..).
That could be useful. ;-) But it's not actually the same as the feature
proposed here.
That's also more like the single capability system that MySQL has.
So while this is interesting food for thought, I don't think this really
supports that claim that other systems have a facility very much like
the proposed one.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Eisentraut (peter_e@gmx.net) wrote:
On 10/27/14 7:36 PM, Stephen Frost wrote:
MySQL:
http://dev.mysql.com/doc/refman/5.1/en/privileges-provided.html#priv_file(note they provide a way to limit access also, via secure_file_priv)
They have a single privilege to allow the user to read or write any
file. I think that feature could be useful.
... Optionally limited to a specific directory with the
secure_file_priv, as I pointed out previously.
Oracle:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5007.htm
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9013.htm#i2125999From the description, that CREATE DIRECTORY command looks to me more
like a tablespace, or a general BLOB space, that you reference by object
name, not by file name.
It also allows you to use the 'external_table_clause' which "is a
read-only table whose metadata is stored in the database but whose data
in stored outside the database. Among other capabilities, external
tables let you query data without first loading it into the database."
In other words, file_fdw. If you read further down, you'll also see
that the way the file is interpreted is based on the access drivers,
which can be ORACLE_LOADER or ORACLE_DATAPUMP, which means you can read
any file you can use with imp or SQLLoader. Basically, Oracle expects
you to use this to create a table in the DB which references the
external file rather than importing using a COPY, which I agree we want
and should make file_fdw support, but it amounts to the same thing.
SQL Server:
http://msdn.microsoft.com/en-us/library/ms175915.aspx
(Note: they can actually run as the user connected instead of the SQL DB
server, if Windows authentication is used, which is basically doing
Kerberos proxying unless I'm mistaken; it's unclear how the security is
maintained if it's a SQL server logon user..).That could be useful. ;-) But it's not actually the same as the feature
proposed here.
Huh? It's exactly the same- but done with Kerberos integration and file
shares. This proposal is essentially a poor-man's version of this where
the administrator has to go set up the allows themselves rather than
letting Kerberos and regular user permissions handle it.
The point is, they're both about giving users access to external files
for importing, exporting and querying, within certain boundaries to
avoid the user being able to trivially bypass the in-database or OS
security.
That's also more like the single capability system that MySQL has.
I agree that it's not clear from the docs for DB2 how or if you can
limit what can be done with this capability, but I don't see much point
in it if you can use it to bypass the in-database security.
So while this is interesting food for thought, I don't think this really
supports that claim that other systems have a facility very much like
the proposed one.
While the documentation for these other products isn't as good as ours,
if you look a bit closer, I think you'll see that their features are
actually very similar to the proposed one (Oracle's even has nearly the
same syntax..).
Thanks,
Stephen
Robert,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Tue, Oct 28, 2014 at 3:19 PM, Stephen Frost <sfrost@snowman.net> wrote:
I agree that this makes it feel awkward. Peter had an interesting
suggestion to make the dir aliases available as actual aliases for the
commands which they would be relevant to. I hadn't considered that- I
proposed 'diralias' because I didn't like 'directory' since we weren't
actually creating *directories* but rather defining aliases to existing
OS directories in PG.Right. Another way to go at this would be to just ditch the names.
Alright.
This exact syntax probably wouldn't work (or might not be a good idea)
because GRANT is so badly overloaded already, but conceptually:GRANT READ ON DIRECTORY '/home/snowman' TO sfrost;
Yeah, GRANT is overloaded pretty badly and has the unfortunate quality
that it's spec-driven.
Or maybe some variant of:
ALTER USER sfrost GRANT READ ON DIRECTORY '/home/snowman';
This could work though. We could add an array to pg_authid which is a
complex type that combines the permission allowed with the directory
somehow. Feels like it might get a bit clumsy though.
One other thing occured to me while I was considering Peter's idea about
using the 'DIRALIAS' name- replicas and/or database migrations.
pg_basebackup always really annoyed me that you had to have your
tablespace directories set up *exactly* the same way when doing the
restore. That stinks. If we actually used the DIRALIAS name then
sysadmins could abstract out the location and could handle migrations
and/or changes to the filesystem structure without having to bother the
DBAs to update their code to the new location. That's not something the
other RDBMS's have that I could see, but it strikes me as a nice
capability anyway and, well, we're certainly not limited to just
implementing what others have.
Thanks for continueing to help walk this forward towards a hopefully
useful feature and apologies for the confusion.
Thanks again!
Stephen
On Wed, Oct 29, 2014 at 6:50 AM, Stephen Frost <sfrost@snowman.net> wrote:
This could work though. We could add an array to pg_authid which is a
complex type that combines the permission allowed with the directory
somehow. Feels like it might get a bit clumsy though.
Sure, I'm just throwing things out to see what sticks. It would be
helpful to have more input from others on what they like and dislike,
too; I'm not pretending my input is Gospel.
One other thing occured to me while I was considering Peter's idea about
using the 'DIRALIAS' name- replicas and/or database migrations.
pg_basebackup always really annoyed me that you had to have your
tablespace directories set up *exactly* the same way when doing the
restore. That stinks. If we actually used the DIRALIAS name then
sysadmins could abstract out the location and could handle migrations
and/or changes to the filesystem structure without having to bother the
DBAs to update their code to the new location. That's not something the
other RDBMS's have that I could see, but it strikes me as a nice
capability anyway and, well, we're certainly not limited to just
implementing what others have.
Of course, any design that stores paths in the system catalogs is
going to have the problem that the standby will perforce have the same
configuration as the master.
I'm fuzzy on how you see DIRALIAS helping with tablespace migrations,
etc. There's no obvious way to make a tablespace definition reference
an alias rather than a pathname; it's just a filesystem-level symlink.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Robert Haas (robertmhaas@gmail.com) wrote:
On Wed, Oct 29, 2014 at 6:50 AM, Stephen Frost <sfrost@snowman.net> wrote:
This could work though. We could add an array to pg_authid which is a
complex type that combines the permission allowed with the directory
somehow. Feels like it might get a bit clumsy though.Sure, I'm just throwing things out to see what sticks. It would be
helpful to have more input from others on what they like and dislike,
too; I'm not pretending my input is Gospel.
Agreed- additional input from others would be great. Adam's started the
wiki to hopefully capture these thoughts and be a way to solicit input
from others also. I wonder if we could try something different to get
input from users who don't typically follow -hackers, like maybe get
someone to blog about the concept, point to the wiki, and ask for
feedback? Just a random thought.
I've only glanced at it so far myself and plan a deeper review to add my
own thoughts, but if folks want to look at what Adam's put together
so far, it's here:
https://wiki.postgresql.org/wiki/Directory_Permissions
One other thing occured to me while I was considering Peter's idea about
using the 'DIRALIAS' name- replicas and/or database migrations.
pg_basebackup always really annoyed me that you had to have your
tablespace directories set up *exactly* the same way when doing the
restore. That stinks. If we actually used the DIRALIAS name then
sysadmins could abstract out the location and could handle migrations
and/or changes to the filesystem structure without having to bother the
DBAs to update their code to the new location. That's not something the
other RDBMS's have that I could see, but it strikes me as a nice
capability anyway and, well, we're certainly not limited to just
implementing what others have.Of course, any design that stores paths in the system catalogs is
going to have the problem that the standby will perforce have the same
configuration as the master.
Yeah, that's a good point, this wouldn't address replicas (until/unless
we allow catalogs to be different some day.. though I guess you'd do
that with logical replication instead) but rather the pg_basebackup /
migration-to-new-system case.
I'm fuzzy on how you see DIRALIAS helping with tablespace migrations,
etc. There's no obvious way to make a tablespace definition reference
an alias rather than a pathname; it's just a filesystem-level symlink.
Sorry, to clarify, I wasn't thinking of tablespaces (which pg_basebackup
now deals with better by allowing you to provide a mapping from the old
to the new) but rather files referenced by a file_fdw table, though it
could be used with COPY also (possibly inside of pl/pgsql, or in client
apps).
Thanks!
Stephen
Robert Haas wrote:
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created. The users are
still operating on pathnames. That's awfully strange.
I think it would make more sense if the file-accessing command specified
the DIRALIAS (or DIRECTORY, whatever we end up calling this) and a
pathname relative to the base one. Something like
postgres=# CREATE DIRECTORY logdir ALIAS FOR '/pgsql/data/pg_log';
postgres=# GRANT READ ON DIRECTORY logdir TO logscanner;
logscanner=> COPY logtable FROM 'postgresql-2014-10-28.csv' IN DIRECTORY logdir;
The ALTER ROLE GRANT READ idea proposed downthread is nice also, but one
advantage of this is not having absolute path names in the COPY command.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Stephen Frost <sfrost@snowman.net> writes:
Agreed- additional input from others would be great.
I think this entire concept is a bad idea that will be a never-ending
source of security holes. There are too many things that a user with
filesystem access can do to get superuser-equivalent status.
Here is one trivial example: you want to let user joe import COPY
data quickly, so you give him read access in directory foo, which he
has write access on from his own account. Surely that's right in the
middle of use cases you had in mind, or even if it wasn't, it sounds
like a good idea no? The problem is he can create symlinks, not just
files, in that directory, and by pointing the symlink to the right
place he can read any file the server can read. pg_hba.conf, pg_authid,
or even just tables he shouldn't have access to. With a little luck he
can crack the superuser's password, but even without that you've given
him access to sensitive information.
If you were dumb enough to give joe *write* access in such a directory,
so that he could COPY in both directions, it's game over altogether: he
can become superuser in any number of ways, most easily by hacking
pg_hba.conf.
You could ameliorate this problem by checking to see that the read/write
target is a file not a symlink, but that's still subject to filesystem
race conditions that could be exploited by anyone with the ability to
retry it enough times.
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.
Therefore, I'm going to be against committing any feature of this sort.
If the objective is to give filesystem capabilities to someone you *do*
trust, but they'd prefer to use it from an account without full superuser
privileges, that can be solved much more simply by making access to the
existing superuser-only I/O functions more granular. That fits in just
fine with the other project you've got of breaking down superuserness into
smaller privileges. But if we build a feature designed in the way being
discussed in this thread, people will think it can be used to grant
limited filesystem access to users they don't completely trust, and we're
going to have to deal with the fallout from that.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-10-29 10:47:58 -0400, Tom Lane wrote:
Here is one trivial example: you want to let user joe import COPY
data quickly, so you give him read access in directory foo, which he
has write access on from his own account. Surely that's right in the
middle of use cases you had in mind, or even if it wasn't, it sounds
like a good idea no? The problem is he can create symlinks, not just
files, in that directory, and by pointing the symlink to the right
place he can read any file the server can read. pg_hba.conf, pg_authid,
or even just tables he shouldn't have access to. With a little luck he
can crack the superuser's password, but even without that you've given
him access to sensitive information.If you were dumb enough to give joe *write* access in such a directory,
so that he could COPY in both directions, it's game over altogether: he
can become superuser in any number of ways, most easily by hacking
pg_hba.conf.You could ameliorate this problem by checking to see that the read/write
target is a file not a symlink, but that's still subject to filesystem
race conditions that could be exploited by anyone with the ability to
retry it enough times.
I think I'd be fair to restrict this features to platforms that support
O_NOFOLLOW and O_EXCL. Those can be used to circumvent such race
conditions.
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.
... but that doesn't necessarily address this point.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 10:52 AM, Andres Freund <andres@2ndquadrant.com> wrote:
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.... but that doesn't necessarily address this point.
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?
In general, the hazard is that an untrusted user can induce the user
to read or write a file that the user in question could not have read
or written himself. It's not clear to me whether it's reasonably
possible to build a system that is robust against such attacks, or
not.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom,
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
Agreed- additional input from others would be great.
I think this entire concept is a bad idea that will be a never-ending
source of security holes. There are too many things that a user with
filesystem access can do to get superuser-equivalent status.
I'd be pretty disappointed if we are unable to implement a feature like
this which can't trivially bypass the in-database security and/or gain
superuser access.
Here is one trivial example: you want to let user joe import COPY
data quickly, so you give him read access in directory foo, which he
has write access on from his own account. Surely that's right in the
middle of use cases you had in mind, or even if it wasn't, it sounds
like a good idea no? The problem is he can create symlinks, not just
files, in that directory, and by pointing the symlink to the right
place he can read any file the server can read. pg_hba.conf, pg_authid,
or even just tables he shouldn't have access to. With a little luck he
can crack the superuser's password, but even without that you've given
him access to sensitive information.
While a use-case which would be great to support, we don't have the
ability to do it as well as SQL Server can since the backend can't
impersonate another user. I'm definitely hoping to support Kerberos
credential proxying eventually (primairly for FDWs, but it's possible we
could use it for NFS also) and therefore we'd certainly have to caution
users about these risks.
The specific use-cases which I've been describing are cases where the
user doesn't have access to modify (or possibly even to read) the
filesystem- log directories, read-only NFS mounts, etc.
If you were dumb enough to give joe *write* access in such a directory,
so that he could COPY in both directions, it's game over altogether: he
can become superuser in any number of ways, most easily by hacking
pg_hba.conf.
You make a good point, certainly, but pg_hba.conf isn't necessairly
available to be modified by the PostgreSQL user. Of course,
postgresql.auto.conf and the PG heap files could be overwritten if a
user is able to create symlinks and convince PG to access through them.
You could ameliorate this problem by checking to see that the read/write
target is a file not a symlink, but that's still subject to filesystem
race conditions that could be exploited by anyone with the ability to
retry it enough times.
As Andres already pointed out, there are ways to specifically address
these risks, I dare suggest because what we're talking about supporting
here is not new ground and others have found it to be a valuable
capability and worked to make it secure and safe to support.
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.
I certainly don't have the experience you do in this area and am quite
interested in the other attack routes you're thinking of, and how other
databases which support this capability address them. Perhaps they're
simply documented as known issues, or they aren't addressed at all and
bugs exist, but I'm not seeing these apparently obvious issues.
If the objective is to give filesystem capabilities to someone you *do*
trust, but they'd prefer to use it from an account without full superuser
privileges, that can be solved much more simply by making access to the
existing superuser-only I/O functions more granular. That fits in just
fine with the other project you've got of breaking down superuserness into
smaller privileges. But if we build a feature designed in the way being
discussed in this thread, people will think it can be used to grant
limited filesystem access to users they don't completely trust, and we're
going to have to deal with the fallout from that.
The features which I've been proposing are, generally, intended to allow
a non-superuser to do things that are limited to the superuser today
while also preventing them from trivially being able to become a
superuser. Any which can trivially be used to become a superuser would
at least need to be heavily caveat'd accordingly in the documentation,
which I'd be happy to do, but I'd like to have a better understanding of
the attack vectors under which there is such a risk- to provide
appropriate documentation, if nothing else.
Thanks!
Stephen
* Robert Haas (robertmhaas@gmail.com) wrote:
On Wed, Oct 29, 2014 at 10:52 AM, Andres Freund <andres@2ndquadrant.com> wrote:
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.... but that doesn't necessarily address this point.
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?
You can't hard link to files you don't own.
sfrost@tamriel:/home/sfrost> ln /home/archive/xx.tar.gz
ln: failed to create hard link ?./xx.tar.gz? => ?/home/archive/xx.tar.gz?: Operation not permitted
In general, the hazard is that an untrusted user can induce the user
to read or write a file that the user in question could not have read
or written himself. It's not clear to me whether it's reasonably
possible to build a system that is robust against such attacks, or
not.
There are certainly use-cases where the user executing the COPY doesn't
have any direct access to the filesystem at all but only through PG.
Taken to a bit of an extreme, you could say we already provide that
today. ;)
Thanks!
Stephen
Robert Haas wrote:
On Wed, Oct 29, 2014 at 10:52 AM, Andres Freund <andres@2ndquadrant.com> wrote:
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.... but that doesn't necessarily address this point.
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?
Users cannot create a hard link to a file they can't already access.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:
Robert Haas wrote:
On Wed, Oct 29, 2014 at 10:52 AM, Andres Freund <andres@2ndquadrant.com> wrote:
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.... but that doesn't necessarily address this point.
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?Users cannot create a hard link to a file they can't already access.
The specifics actually depend on (on Linux, at least) the value of
/proc/sys/fs/protected_hardlink, which has existed in upstream since 3.6
(not sure about the RHEL kernels, though I expect they've incorporated
it also at some point along the way).
There is a similar /proc/sys/fs/protected_symlinks control for dealing
with the same kind of time-of-check / time-of-use issues that exist with
symlinks.
At least on my Ubuntu 14.04 systems, these are both set to '1'.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
The larger point though is that this is just one of innumerable attack
routes for anyone with the ability to make the server do filesystem reads
or writes of his choosing. If you think that's something you can safely
give to people you don't trust enough to make them superusers, you are
wrong, and I don't particularly want to spend the next ten years trying
to wrap band-aids around your misjudgment.
I certainly don't have the experience you do in this area and am quite
interested in the other attack routes you're thinking of, and how other
databases which support this capability address them. Perhaps they're
simply documented as known issues, or they aren't addressed at all and
bugs exist, but I'm not seeing these apparently obvious issues.
Well, the point here is that I'm *not* an expert. I'm aware that there
are lots of nonobvious ways in which Unix filesystem security can be
subverted if you can control the actions of a process running with
privileges you don't/shouldn't have. I don't claim to have all the
details at my fingertips, and I doubt that anyone else in the PG community
does either. Therefore, I think it's inevitable that if we build a
feature like this, it's going to have multiple security holes that
we will find out about the hard way.
As for other databases, since when did we think that Oracle, Microsoft, or
mysql are reliable sources of well-designed security-hole-free software?
The fact that they advertise features of this sort doesn't impress me in
the slightest.
I'm happy to have us rearrange things so that use of the existing
filesystem access functionality can be given out to users who aren't full
superusers. What I don't believe is that it's a useful exercise to try
to give out restricted filesystem access: that will require too many
restrictions/compromises and still create too much of an attack surface.
I want to just define away the attack surface by making it clear that we
are *not* making any promises about what someone can do with filesystem
access functionality. If you give joe access to that functionality and
he does something you don't like, it's your fault not ours.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 11:34 AM, Stephen Frost <sfrost@snowman.net> wrote:
The specifics actually depend on (on Linux, at least) the value of
/proc/sys/fs/protected_hardlink, which has existed in upstream since 3.6
(not sure about the RHEL kernels, though I expect they've incorporated
it also at some point along the way).There is a similar /proc/sys/fs/protected_symlinks control for dealing
with the same kind of time-of-check / time-of-use issues that exist with
symlinks.At least on my Ubuntu 14.04 systems, these are both set to '1'.
Playing devil's advocate here for a minute, you're saying that
new-enough versions of Linux have an optional feature that prevents
this attack. I think an argument could be made that this is basically
unsecurable on any other platform, or even old Linux versions. And it
still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-10-29 11:52:43 -0400, Robert Haas wrote:
On Wed, Oct 29, 2014 at 11:34 AM, Stephen Frost <sfrost@snowman.net> wrote:
The specifics actually depend on (on Linux, at least) the value of
/proc/sys/fs/protected_hardlink, which has existed in upstream since 3.6
(not sure about the RHEL kernels, though I expect they've incorporated
it also at some point along the way).There is a similar /proc/sys/fs/protected_symlinks control for dealing
with the same kind of time-of-check / time-of-use issues that exist with
symlinks.At least on my Ubuntu 14.04 systems, these are both set to '1'.
Playing devil's advocate here for a minute, you're saying that
new-enough versions of Linux have an optional feature that prevents
this attack. I think an argument could be made that this is basically
unsecurable on any other platform, or even old Linux versions.
It's possible to do this securely by doing a fstat() and checking the
link count.
And it
still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.
Imo that's simply not a problem that we need to solve - it's much more
general and independent.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 12:00 PM, Andres Freund <andres@2ndquadrant.com> wrote:
It's possible to do this securely by doing a fstat() and checking the
link count.
Good point.
And it
still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.Imo that's simply not a problem that we need to solve - it's much more
general and independent.
I don't see how you can draw an arbitrary line there. We either
guarantee that the logged-in user can't usurp the server's
permissions, or we don't. Making it happen only sometimes in cases
we're prepared to dismiss is not real security.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-10-29 12:03:54 -0400, Robert Haas wrote:
And it
still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.Imo that's simply not a problem that we need to solve - it's much more
general and independent.I don't see how you can draw an arbitrary line there. We either
guarantee that the logged-in user can't usurp the server's
permissions, or we don't. Making it happen only sometimes in cases
we're prepared to dismiss is not real security.
I can draw the line because lowering the permissions of some file isn't
postgres' problem. If you do that, you better make sure that there's no
existing hardlinks pointing to the precious file. And that has nothing
to do with postgres.
But anyway, just refusing to work on hardlinked files would also get rid
of that problem.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Stephen Frost <sfrost@snowman.net> writes:
* Robert Haas (robertmhaas@gmail.com) wrote:
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?
You can't hard link to files you don't own.
That restriction exists on only some platforms. Current OS X for instance
seems perfectly willing to allow it (suggesting that most BSDen probably
do likewise), and I see no language supporting your claim in the POSIX
spec for link(2).
This points up the fact that platform-specific security holes are likely
to be a huge part of the problem. I won't even speculate about our odds
of building something that's secure on Windows.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Andres Freund (andres@2ndquadrant.com) wrote:
On 2014-10-29 12:03:54 -0400, Robert Haas wrote:
I don't see how you can draw an arbitrary line there. We either
guarantee that the logged-in user can't usurp the server's
permissions, or we don't. Making it happen only sometimes in cases
we're prepared to dismiss is not real security.I can draw the line because lowering the permissions of some file isn't
postgres' problem. If you do that, you better make sure that there's no
existing hardlinks pointing to the precious file. And that has nothing
to do with postgres.But anyway, just refusing to work on hardlinked files would also get rid
of that problem.
Right, I was just about to point out the same- the fstat/link-count
approach addresses the issue also.
As for the 'new-enough' versions of Linux, my point there was simply
that these are issues which people who are concerned about security have
been looking at and working to address. History shows a pretty thorny
past, certainly, but SMTP has a similar past.
Thanks,
Stephen
On 2014-10-29 12:09:00 -0400, Tom Lane wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Robert Haas (robertmhaas@gmail.com) wrote:
I think the question is "just how innumerable are those attack
routes"? So, we can prevent a symlink from being used via O_NOFOLLOW.
But what about hard links?You can't hard link to files you don't own.
That restriction exists on only some platforms.
Yea, it's nothing we can rely on. I do think checking the link count to
be 1 is safe though.
Current OS X for instance
seems perfectly willing to allow it (suggesting that most BSDen probably
do likewise), and I see no language supporting your claim in the POSIX
spec for link(2).
I'd argue that there's no point in treating OSX as a securable platform
:P
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Stephen Frost <sfrost@snowman.net> writes:
* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:
Users cannot create a hard link to a file they can't already access.
The specifics actually depend on (on Linux, at least) the value of
/proc/sys/fs/protected_hardlink, which has existed in upstream since 3.6
(not sure about the RHEL kernels, though I expect they've incorporated
it also at some point along the way).
No such file in RHEL 6.6 :-(.
What the POSIX spec for link(2) says is
[EACCES]
A component of either path prefix denies search permission, or the
requested link requires writing in a directory that denies write
permission, or the calling process does not have permission to access
the existing file and this is required by the implementation.
It's not very clear what "access" means, and in any case this wording
gives implementors permission to not enforce anything at all in that
line. Whether particular flavors of Linux do or not doesn't help us
much, because other popular platforms clearly don't enforce it.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Alvaro Herrera (alvherre@2ndquadrant.com) wrote:
Users cannot create a hard link to a file they can't already access.
The specifics actually depend on (on Linux, at least) the value of
/proc/sys/fs/protected_hardlink, which has existed in upstream since 3.6
(not sure about the RHEL kernels, though I expect they've incorporated
it also at some point along the way).No such file in RHEL 6.6 :-(.
Ouch. Although- have you tested when happens there? I wonder if
they've decided it's not worth allowing ever or if they feel that it's
not worth preventing and that security-concious software should check
the link count as Andres suggests.
What the POSIX spec for link(2) says is
[EACCES]
A component of either path prefix denies search permission, or the
requested link requires writing in a directory that denies write
permission, or the calling process does not have permission to access
the existing file and this is required by the implementation.
Yeah, I didn't mean to imply that this was provided by POSIX and you're
right to point out that we couldn't depend on this as it wouldn't be
cross-platform anyway.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
No such file in RHEL 6.6 :-(.
Ouch. Although- have you tested when happens there?
Pretty much exactly the same thing I just saw on OSX, ie, nothing.
[tgl@sss1 zzz]$ touch foo
[tgl@sss1 zzz]$ ls -l
total 0
-rw-rw-r--. 1 tgl tgl 0 Oct 29 12:23 foo
[tgl@sss1 zzz]$ ln foo bar
[tgl@sss1 zzz]$ ls -l
total 0
-rw-rw-r--. 2 tgl tgl 0 Oct 29 12:23 bar
-rw-rw-r--. 2 tgl tgl 0 Oct 29 12:23 foo
[tgl@sss1 zzz]$ chmod 000 foo
[tgl@sss1 zzz]$ sudo chown root foo
[tgl@sss1 zzz]$ ln foo baz
[tgl@sss1 zzz]$ ls -l
total 0
----------. 3 root tgl 0 Oct 29 12:23 bar
----------. 3 root tgl 0 Oct 29 12:23 baz
----------. 3 root tgl 0 Oct 29 12:23 foo
[tgl@sss1 zzz]$ uname -a
Linux sss1.sss.pgh.pa.us 2.6.32-504.el6.x86_64 #1 SMP Tue Sep 16 01:56:35 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux
I wonder if
they've decided it's not worth allowing ever or if they feel that it's
not worth preventing and that security-concious software should check
the link count as Andres suggests.
Probably it's just that it's a new feature that they've not chosen to
back-port into 2.6.x kernels. I'm sure they're following the upstream
kernels in newer release series. But even if they had chosen to back-port
it, you can be entirely darn sure it wouldn't be turned on by default in
the RHEL6 series; they'd be too worried about breaking existing
applications.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
This points up the fact that platform-specific security holes are likely
to be a huge part of the problem. I won't even speculate about our odds
of building something that's secure on Windows.
Andres' suggestion to only provide it on platforms which support
O_NOFOLLOW and O_EXCL certainly seems appropriate, along with fstat'ing
after we've opened it and checking that there's only one hard-link to
it. As for Windows, it looks like you can get a file's attributes after
opening it by using GetFileInformationByHandle and you can then check if
it's a junction point or not (which would indicate if it's either a
symbolic link or a hard link, from what I can see). Obviously, we'd
need to get input from someone more familiar with Windows than I am
before we can be confident of this approach though.
Thanks!
Stephen
Robert,
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created. The users are
still operating on pathnames. That's awfully strange.
That's an interesting point and I don't disagree that it seems a little
strange. However, isn't this approach similar if not the same (other than
operating on path names) as with some other objects, specifically rules and
policies?
-Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
Alvaro,
I think it would make more sense if the file-accessing command specified
the DIRALIAS (or DIRECTORY, whatever we end up calling this) and a
pathname relative to the base one. Something likepostgres=# CREATE DIRECTORY logdir ALIAS FOR '/pgsql/data/pg_log';
Following this, what do you think about simply expanding DIRALIAS out into
to DIRECTORY ALIAS? So instead:
CREATE DIRECTORY ALIAS <name> AS '<path>'
or...
CREATE DIRECTORY ALIAS <name> FOR '<path>'
My thought on this is towards the natural word order of the command. Also,
I think having it as CREATE DIRECTORY ALIAS minimizes confusion, as I think
Stephen mentioned, that we are creating an alias, not an actual directory.
Thoughts?
postgres=# GRANT READ ON DIRECTORY logdir TO logscanner;
I personally like this form the most, however, I think the greatest hurdle
with it is that it would require making READ (and WRITE) reserved
keywords. Obviously, I think that is a non-starter.
logscanner=> COPY logtable FROM 'postgresql-2014-10-28.csv' IN DIRECTORY
logdir;
That's an interesting thought. Would 'IN DIRECTORY' be restricted to just
the alias name? I'm not sure it would make sense to allow a directory path
there, as what would be the point? At any rate, just food for thought.
The ALTER ROLE GRANT READ idea proposed downthread is nice also,
Agreed and probably the most logical option at this point?
but one
advantage of this is not having absolute path names in the COPY command.
Pardon my ignorance, but can you help me understand the advantage of not
having absolute path names in the COPY command?
-Adam
--
Adam Brightwell - adam.brightwell@crunchydatasolutions.com
Database Engineer - www.crunchydatasolutions.com
On 29/10/14 16:11, Andres Freund wrote:
I do think checking the link count to
be 1 is safe though.
You will fail against certain styles of online-backup.
--
Cheers,
Jeremy
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Adam,
* Adam Brightwell (adam.brightwell@crunchydatasolutions.com) wrote:
Pardon my ignorance, but can you help me understand the advantage of not
having absolute path names in the COPY command?
If you're writing ETL processes and/or PL/PgSQL code which embeds the
COPY command and you migrate from one server to another, or the sysadmin
decides he wants to mount /data and /otherdata, it's nice to be able to
just update the directory alias rather than having to modify all the ETL
processes and PL/PgSQL code.
Basically, it provides an abstraction layer which would allow you to
avoid having to either create your own variable for the directory and
then track that somewhere in your own table (to get around having to
hard-code things into your ETL or PL/PgSQL code) or deal with updating
all that code when the filesystem structure changes for whatever reason.
Thanks,
Stephen
On Wed, Oct 29, 2014 at 12:36 PM, Adam Brightwell
<adam.brightwell@crunchydatasolutions.com> wrote:
Robert,
To articular my own concerns perhaps a bit better, there are two major
things I don't like about the whole DIRALIAS proposal. Number one,
you're creating this SQL object whose name is not actually used for
anything other than manipulating the alias you created. The users are
still operating on pathnames. That's awfully strange.That's an interesting point and I don't disagree that it seems a little
strange. However, isn't this approach similar if not the same (other than
operating on path names) as with some other objects, specifically rules and
policies?
Hmm. Maybe. Somehow it feels different to me. A rule or policy is
something internal to the system, and you have to identify it somehow.
A directory, though, already has a name, so giving it an additional
dummy name seems strange. But, you do have a point.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-10-29 16:38:44 +0000, Jeremy Harris wrote:
On 29/10/14 16:11, Andres Freund wrote:
I do think checking the link count to
be 1 is safe though.You will fail against certain styles of online-backup.
Meh. I don't think that's really a problem for the usecases for COPY
FROM.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Jeremy Harris (jgh@wizmail.org) wrote:
On 29/10/14 16:11, Andres Freund wrote:
I do think checking the link count to
be 1 is safe though.You will fail against certain styles of online-backup.
Fail-safe though, no? For my part, I'm not particularly bothered by
that; we'd have to document it appropriately, of course.
Thanks!
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
This points up the fact that platform-specific security holes are likely
to be a huge part of the problem. I won't even speculate about our odds
of building something that's secure on Windows.
Andres' suggestion to only provide it on platforms which support
O_NOFOLLOW and O_EXCL certainly seems appropriate, along with fstat'ing
after we've opened it and checking that there's only one hard-link to
it. As for Windows, it looks like you can get a file's attributes after
opening it by using GetFileInformationByHandle and you can then check if
it's a junction point or not (which would indicate if it's either a
symbolic link or a hard link, from what I can see). Obviously, we'd
need to get input from someone more familiar with Windows than I am
before we can be confident of this approach though.
So at this point we've decided that we must forbid access to symlinked or
hardlinked files, which is a significant usability penalty; we've also
chosen to blow off most older platforms entirely; and we've only spent
about five minutes actually looking for security issues, with no good
reason to assume there are no more.
(I can think of one more already, actually: the proposed post-open
fstat for link count has a race condition. User just has to link target
file into writable directory, attempt to open it, and concurrently unlink
from the writable directory. Repeat until success.)
So I remain of the opinion that this is a bad idea we should not pursue.
We're going to put a huge amount of work into it, it *will* cause more
than one security bug in the future (want to lay a side bet?), and we're
still going to end up with people needing to use the old-style access
facilities because the restrictions we'll have to put on this one are
unacceptable for their purposes.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
On 2014-10-29 16:38:44 +0000, Jeremy Harris wrote:
On 29/10/14 16:11, Andres Freund wrote:
I do think checking the link count to
be 1 is safe though.
You will fail against certain styles of online-backup.
Meh. I don't think that's really a problem for the usecases for COPY
FROM.
I think Jeremy's point is that if such a backup technology is in use, it
would result in random failures whenever the backup daemon happened to
have an extra hardlink at the moment you tried to access the file.
In other words, just another scenario where the proposed feature fails.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> wrote:
So at this point we've decided that we must forbid access to symlinked or
hardlinked files, which is a significant usability penalty; we've also
chosen to blow off most older platforms entirely; and we've only spent
about five minutes actually looking for security issues, with no good
reason to assume there are no more.
What's interesting and disappointing here is that not one of these
suggested vulnerabilities seems like a possibility on a database
server managed in what I would consider a sane and secure manner[1].
This feature is valuable because it is an alternative to allowing a
user you don't trust *either* an OS login to the database server
*or* a superuser database login. Can anyone suggest an exploit
which would be available if we allowed someone who has permission
to view all data in the database read permission to the pg_log
directory and the files contained therein, assuming they do *not*
have an OS login to the database server?
--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I wrote:
... and we've only spent
about five minutes actually looking for security issues, with no good
reason to assume there are no more.
Oh, here's another one: what I read in RHEL6's open(2) man page is
O_NOFOLLOW
If pathname is a symbolic link, then the open fails. This is a
FreeBSD extension, which was added to Linux in version 2.1.126.
Symbolic links in earlier components of the pathname will still
be followed.
So heaven help you if you grant user joe access in directory
/home/joe/copydata, or any other directory whose parent is writable by
him. He can just remove the directory and replace it with a symlink to
whatever directory contains files he'd like the server to read/write for
him.
Again, we could no doubt install defenses against that sort of case,
once we realize it's a threat. Maybe they'd even be bulletproof defenses
(not too sure how you'd prevent race conditions though). But whether they
are or not, we just took the usability of the feature down another notch,
because certainly that sort of directory arrangement would have been
convenient for joe ... as long as he was trustworthy.
In any case, my larger point is that I foresee a very very long line
of gotchas of this sort, and I do not think that the proposed feature
is worth it.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Kevin,
* Kevin Grittner (kgrittn@ymail.com) wrote:
Tom Lane <tgl@sss.pgh.pa.us> wrote:
So at this point we've decided that we must forbid access to symlinked or
hardlinked files, which is a significant usability penalty; we've also
chosen to blow off most older platforms entirely; and we've only spent
about five minutes actually looking for security issues, with no good
reason to assume there are no more.What's interesting and disappointing here is that not one of these
suggested vulnerabilities seems like a possibility on a database
server managed in what I would consider a sane and secure manner[1].
For my part- I agree completely with this sentiment, and I'm not sure
that Tom disagrees with it. I believe the discussion is heading towards
a blanket "use this at your own risk- if the user can modify files in
these directories outside of PG, they can probably break your system"
being added in very bold lettering in the documentation around this.
It botheres me that we'd have to have a statement like that, but if we
have to then we have to.
This feature is valuable because it is an alternative to allowing a
user you don't trust *either* an OS login to the database server
*or* a superuser database login. Can anyone suggest an exploit
which would be available if we allowed someone who has permission
to view all data in the database read permission to the pg_log
directory and the files contained therein, assuming they do *not*
have an OS login to the database server?
These are the use-cases which I've been wanting this for. Also, things
like ETL processes which run as a dedicated user and really have no
business nor need to be running as a superuser.
Thanks,
Stephen
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
So heaven help you if you grant user joe access in directory
/home/joe/copydata, or any other directory whose parent is writable by
him. He can just remove the directory and replace it with a symlink to
whatever directory contains files he'd like the server to read/write for
him.
Yeah, there's no workaround for this that I'm aware of- we'd have to
prevent subdirectories being provided by the user, I believe.
Reviewing procmail, maildrop, and other processes which do this sort of
operation, it looks like the common thread today is to setuid() instead,
but that only works if you're running as root, which we obviously don't
want to be doing either.
Kevin had a good question, I thought- are there issues if the DB user
doesn't have any access to the OS filesystem? Would it be sufficient to
say "Note that processes which can create objects in the directory
specified outside of PostgreSQL could create symlinks, hard links, or
other objects which might cause the PostgreSQL server to read files or
write files which it has access to that are outside of the directory
specified." ?
I still don't particularly like it and, frankly, the limitations we've
come up with thus far are not issues for my use-cases and I'd rather
have them and be able to say "yes, you can use this with some confidence
that it won't trivially bypass the DB security or provide a way to crash
the DB".
Again, we could no doubt install defenses against that sort of case,
once we realize it's a threat. Maybe they'd even be bulletproof defenses
(not too sure how you'd prevent race conditions though). But whether they
are or not, we just took the usability of the feature down another notch,
because certainly that sort of directory arrangement would have been
convenient for joe ... as long as he was trustworthy.
This "ad-hoc data load for Joe" use-case isn't where I had been going
with this feature, and I do trust the ETL processes that are behind the
use-case that I've proposed for the most part, but there's also no
reason for those files to be symlinks or have hard-links or have
subdirectories beyond those that I've specifically set up, and having
those protections seems, to me at least, like they'd be a good idea to
have, just in case.
If we punt on it entirely and refuse to check that the path provided by
the user hasn't got ".." in it, or that the file is an actual file and
not a symlink, etc, then we might as well make a "FILEACCESS" role
attribute and declare that you can trivially get superuser with it, if
you care to. The problem with that, as I see it, is that it'd close off
a different set of use-cases as I'm really on the fence about if I'd
want to give an ETL process that kind of access, just out of sheer
paranoia.
Thanks!
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Kevin Grittner (kgrittn@ymail.com) wrote:
What's interesting and disappointing here is that not one of these
suggested vulnerabilities seems like a possibility on a database
server managed in what I would consider a sane and secure manner[1].
For my part- I agree completely with this sentiment, and I'm not sure
that Tom disagrees with it.
Well, the issues we've thought of so far require that the attacker have
his own shell-level access to the filesystem, but I would not like to
posit that there are none that don't require it. Race conditions, for
example, could be exploited without a shell account as long as you can
fire up two backends doing your bidding. *Maybe* it's safe if we don't
expose any "create symlink" or "create hardlink" or "rename" functions;
but you can bet people will be asking for such things down the line,
and we might forget and give it to them :-(
More to the point, if you're excluding cases like "let the user use
server-side COPY for speed" as allowed use-cases for this feature,
it seems like that's a pretty severe restriction. How much is left
other than "let DBAs read the postmaster log remotely"? And do we really
need to provide allegedly-not-superuser-equivalent filesystem access in
order to satisfy that use-case? If you are letting untrustworthy people
read the postmaster log you've already got security problems, as per other
recent threads.
I believe the discussion is heading towards
a blanket "use this at your own risk- if the user can modify files in
these directories outside of PG, they can probably break your system"
being added in very bold lettering in the documentation around this.
It botheres me that we'd have to have a statement like that, but if we
have to then we have to.
If you're going to need a "use at your own risk" disclaimer, how is
that significantly different from letting people use the existing
superuser filesystem access functions?
This feature is valuable because it is an alternative to allowing a
user you don't trust *either* an OS login to the database server
*or* a superuser database login. Can anyone suggest an exploit
which would be available if we allowed someone who has permission
to view all data in the database read permission to the pg_log
directory and the files contained therein, assuming they do *not*
have an OS login to the database server?
Capture the postmaster log. Keep on capturing it till somebody
fat-fingers their login to the extent of swapping the username and
password (yeah, I've done that, haven't you?). Scrape password from
the connection-failure log entry, figure out who it belongs to from
the next successful login, away you go. Mean time to break-in might
or might not be less than time to brute-force the MD5 you could've
read from pg_authid. But in any case, I don't find the assumption
that the user can already read everything in the database to
correspond to an untrusted user, so I'm not sure what this exercise
proves.
Or in short: you really shouldn't give server-filesystem access to
a user you have no trust in, and I'm unclear on what the use case
would be for that even if we could restrict it reliably. The use
cases I can see for this are for DBAs to be able to do maintenance
things remotely without using a full no-training-wheels superuser
account. ISTM that that type of use-case would be satisfied well
enough --- not ideally, perhaps, but well enough --- by being able
to grant full filesystem read and/or write to non-superuser accounts.
I compare this to the CREATEROLE privilege: that's pretty dangerous,
all in all, but we have not felt the need to invent facilities
whereby somebody could say "joe can create new roles, but only
on alternate Tuesdays and only if their names begin with 'u'".
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/29/14, 2:33 PM, Tom Lane wrote:
Capture the postmaster log. Keep on capturing it till somebody
fat-fingers their login to the extent of swapping the username and
password (yeah, I've done that, haven't you?).
Which begs the question: why on earth do we log passwords at all? This is a problem for ALTER ROLE too.
Perhaps it would make sense if we had a dedicated security log this stuff went into, but if you're running something like pgBadger/pgFouine you're going to be copying logfiles off somewhere else and now you've got a security problem.
Let alone if you're using syslog...
--
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Well, the issues we've thought of so far require that the attacker have
his own shell-level access to the filesystem, but I would not like to
posit that there are none that don't require it. Race conditions, for
example, could be exploited without a shell account as long as you can
fire up two backends doing your bidding. *Maybe* it's safe if we don't
expose any "create symlink" or "create hardlink" or "rename" functions;
but you can bet people will be asking for such things down the line,
and we might forget and give it to them :-(
I was planning to document these concerns in our documentation around
the feature. If we manage to still provide unfettered link() access to
users despite having docs that say we had best not do that, well, that'd
be on us then, yes.
More to the point, if you're excluding cases like "let the user use
server-side COPY for speed" as allowed use-cases for this feature,
it seems like that's a pretty severe restriction. How much is left
other than "let DBAs read the postmaster log remotely"? And do we really
need to provide allegedly-not-superuser-equivalent filesystem access in
order to satisfy that use-case? If you are letting untrustworthy people
read the postmaster log you've already got security problems, as per other
recent threads.
Auditors don't need write access to the server, period. They do need to
be able to read the logs though. Additionally, being able to grant this
capability to relatively trusted processes, such as ETL, rather than
ad-hoc users, is a valuable use case for this feature.
It botheres me that we'd have to have a statement like that, but if we
have to then we have to.If you're going to need a "use at your own risk" disclaimer, how is
that significantly different from letting people use the existing
superuser filesystem access functions?
I agree that it really isn't (except for the 'you are not actually
running as superuser' bit, as discussed below) and said as much on
another sub-thread a few moment ago.
This feature is valuable because it is an alternative to allowing a
user you don't trust *either* an OS login to the database server
*or* a superuser database login. Can anyone suggest an exploit
which would be available if we allowed someone who has permission
to view all data in the database read permission to the pg_log
directory and the files contained therein, assuming they do *not*
have an OS login to the database server?Capture the postmaster log. Keep on capturing it till somebody
fat-fingers their login to the extent of swapping the username and
password (yeah, I've done that, haven't you?).
Back to the 'setting up systems sanely'- don't use password based
authentication.
Or in short: you really shouldn't give server-filesystem access to
a user you have no trust in, and I'm unclear on what the use case
would be for that even if we could restrict it reliably. The use
cases I can see for this are for DBAs to be able to do maintenance
things remotely without using a full no-training-wheels superuser
account. ISTM that that type of use-case would be satisfied well
enough --- not ideally, perhaps, but well enough --- by being able
to grant full filesystem read and/or write to non-superuser accounts.
I agree that those use-cases are useful but, even as an admin, I'd be
worried about fat-fingering a filename or similar and overwriting
something unintentionally. Still, it'd be a bit better for trusted
admins than having to run around as full superuser, but I'm still not
sure I'd want to give it to my ETL process.
My hope was specifically to *not* give full and unfettered server
filesystem access through this mechanism, to trusted users or untrusted
ones, as you could trivially become superuser or corrupt files on the
system to cause PG to crash.
I compare this to the CREATEROLE privilege: that's pretty dangerous,
all in all, but we have not felt the need to invent facilities
whereby somebody could say "joe can create new roles, but only
on alternate Tuesdays and only if their names begin with 'u'".
Having a FILEACCESS role attribute would certainly be trivial to
implement and document.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
This "ad-hoc data load for Joe" use-case isn't where I had been going
with this feature, and I do trust the ETL processes that are behind the
use-case that I've proposed for the most part, but there's also no
reason for those files to be symlinks or have hard-links or have
subdirectories beyond those that I've specifically set up, and having
those protections seems, to me at least, like they'd be a good idea to
have, just in case.
If your ETL process can be restricted that much, can't it use file_fdw or
some such to access a fixed filename set by somebody with more privilege?
Why exactly does it need freedom to specify a filename but not a directory
path?
As for the DBA-access set of use cases, ISTM that most real-world needs
for this sort of functionality are inherently a bit ad-hoc, and therefore
once you've locked it down tightly enough that it's credibly not
exploitable, it's not really going to be as useful as all that. The
nature of an admin job is dealing with unforeseen cases.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
This "ad-hoc data load for Joe" use-case isn't where I had been going
with this feature, and I do trust the ETL processes that are behind the
use-case that I've proposed for the most part, but there's also no
reason for those files to be symlinks or have hard-links or have
subdirectories beyond those that I've specifically set up, and having
those protections seems, to me at least, like they'd be a good idea to
have, just in case.If your ETL process can be restricted that much, can't it use file_fdw or
some such to access a fixed filename set by somebody with more privilege?
We currently have the ETL figure out what the filename is on a daily
basis and by contrasting where it "should" be against what has been
loaded thus far (which is tracked in tables in the DB) we can figure out
what need to be loaded. To do what you're suggesting we'd have to write
a pl/pgsql function to do the same which runs as a superuser- not ideal,
but it would be possible.
Why exactly does it need freedom to specify a filename but not a directory
path?
Because the file names change every day for daily processes, and there
can be cases (such as the system being backlogged or down for a day or
two) where it'd need to go back a few days in time. This isn't
abnormal- I've run into exactly these cases a few times. The Hadoop
system dumps the files out on the NFS server and the PG side sucks them
in. The directories are part of the API which is defined between the
Hadoop team and the PG team, along with the file names, file formats,
etc. These can go in either direction too, of course, Hadoop -> PG or
PG -> Hadoop, though each direction is always in a different directory
in my experience (as it's just sane to set things up that way), though I
suppose they wouldn't absolutely have to be.
As for the DBA-access set of use cases, ISTM that most real-world needs
for this sort of functionality are inherently a bit ad-hoc, and therefore
once you've locked it down tightly enough that it's credibly not
exploitable, it's not really going to be as useful as all that. The
nature of an admin job is dealing with unforeseen cases.
I agree that for the DBA-access set of use-cases (ad-hoc data loads,
etc), having a role attribute would be sufficient. Note that this
doesn't cover the auditor role and log file access use-case that we've
been discussing though as auditors shouldn't have write access to the
system.
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
If your ETL process can be restricted that much, can't it use file_fdw or
some such to access a fixed filename set by somebody with more privilege?
We currently have the ETL figure out what the filename is on a daily
basis and by contrasting where it "should" be against what has been
loaded thus far (which is tracked in tables in the DB) we can figure out
what need to be loaded. To do what you're suggesting we'd have to write
a pl/pgsql function to do the same which runs as a superuser- not ideal,
but it would be possible.
Well, surely there's a finite set of possible filenames. But if creating
a bunch of file_fdw servers doesn't float your boat, could we imagine a
variant of file_fdw that allows unprivileged specification of filename
within a directory set by a more-privileged user? (Directory as a foreign
server property and filename as a table property, perhaps.) Although the
superuser security definer function solution might work just as well.
As for the DBA-access set of use cases, ISTM that most real-world needs
for this sort of functionality are inherently a bit ad-hoc, and therefore
once you've locked it down tightly enough that it's credibly not
exploitable, it's not really going to be as useful as all that. The
nature of an admin job is dealing with unforeseen cases.
I agree that for the DBA-access set of use-cases (ad-hoc data loads,
etc), having a role attribute would be sufficient. Note that this
doesn't cover the auditor role and log file access use-case that we've
been discussing though as auditors shouldn't have write access to the
system.
Log access seems like a sufficiently specialized, yet important, case that
maybe we should provide bespoke features for exactly that. Aside from
having a clearer idea of the security implications of what we're doing,
specialized code could provide convenience features like automatically
reassembling a series of log files into a single stream.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 3:31 PM, Stephen Frost <sfrost@snowman.net> wrote:
I still don't particularly like it and, frankly, the limitations we've
come up with thus far are not issues for my use-cases and I'd rather
have them and be able to say "yes, you can use this with some confidence
that it won't trivially bypass the DB security or provide a way to crash
the DB".
I think it *will* trivially bypass the DB security. If trivial means
"it can be done by anyone with no work at all", then, OK, it's not
trivial. If it means "it can be done by a reasonably skilled engineer
without too much trouble", then it's trivial. To call it a security
feature, I think the bar needs to be higher than that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
Stephen Frost <sfrost@snowman.net> writes:
* Tom Lane (tgl@sss.pgh.pa.us) wrote:
If your ETL process can be restricted that much, can't it use file_fdw or
some such to access a fixed filename set by somebody with more privilege?We currently have the ETL figure out what the filename is on a daily
basis and by contrasting where it "should" be against what has been
loaded thus far (which is tracked in tables in the DB) we can figure out
what need to be loaded. To do what you're suggesting we'd have to write
a pl/pgsql function to do the same which runs as a superuser- not ideal,
but it would be possible.Well, surely there's a finite set of possible filenames. But if creating
a bunch of file_fdw servers doesn't float your boat, could we imagine a
variant of file_fdw that allows unprivileged specification of filename
within a directory set by a more-privileged user? (Directory as a foreign
server property and filename as a table property, perhaps.) Although the
superuser security definer function solution might work just as well.
Ugh, no, I wouldn't want hundreds of file_fdw tables created (and when
would you stop..?). I'm trying to figure out how what you're
suggesting with file_fdw is different from what I was trying to propose
with directory aliases? Wouldn't that have the same issues of hard
links, etc, if the user also has access to the filesystem and that
directory? And if we trust the admin to use protected directories when
setting up file_fdw, why couldn't they do the same with directory
aliases? Perhaps I've misunderstood this suggestion?
Log access seems like a sufficiently specialized, yet important, case that
maybe we should provide bespoke features for exactly that. Aside from
having a clearer idea of the security implications of what we're doing,
specialized code could provide convenience features like automatically
reassembling a series of log files into a single stream.
I agree with this, absolutely. This as a use-case for the directory
aliases concept was more as a "it happens to support this nicely too"
than a final solution to this use-case, which I agree we definitely
could and should do better with, though I don't have any specific
solutions for it.
Clearly, I'd like to provide a solution to this use-case also though, so
if the whole 'directory alias' idea is defunct then I'd love to hear
suggestions on how to provide ad-hoc log file access for auditors via
file_fdws and/or COPY, if anyone has any ideas..
Thanks,
Stephen
* Robert Haas (robertmhaas@gmail.com) wrote:
On Wed, Oct 29, 2014 at 3:31 PM, Stephen Frost <sfrost@snowman.net> wrote:
I still don't particularly like it and, frankly, the limitations we've
come up with thus far are not issues for my use-cases and I'd rather
have them and be able to say "yes, you can use this with some confidence
that it won't trivially bypass the DB security or provide a way to crash
the DB".I think it *will* trivially bypass the DB security. If trivial means
"it can be done by anyone with no work at all", then, OK, it's not
trivial. If it means "it can be done by a reasonably skilled engineer
without too much trouble", then it's trivial. To call it a security
feature, I think the bar needs to be higher than that.
ENOPARSE
I agree- to be a security feature, we need to have a bar higher than
"can be bypassed by a reasonably skilled engineer without too much
trouble" and certainly higher than "it can be done by anyone with no
work at all". I admit that I didn't realize the situation was quite
so dire today when it comes to these operations and that most utilities
which have to do this for their operations (procmail, maildrop, cron,
etc) have punted completely and gone to using setuid() instead.
Although I will note that cron, at least, does use O_NOFOLLOW and then
does do the hard-link check with fstat() after the crontab file is
opened. If we're able to identify an issue with this approach, we
should probably let them know.
Another interesting idea might be to have "owner" specified along with
the directory alias and then test that the file to be opened is owned by
that owner, as cron checks..
Perhaps I should go work on Kerberos credential proxying and then we
could at least support this kind of capability on Windows when using
Windows Authentication (eg: Kerberos) and Windows file shares, as SQL
Server does. I'll have to investigate how that works (if it does at
all) with Kerberos-based NFSv4; I haven't run into a system which uses
credential proxying to access NFS yet though I don't see any particular
reason offhand why it wouldn't work.
Thanks,
Stephen
Tom Lane <tgl@sss.pgh.pa.us> wrote:
If you are letting untrustworthy people read the postmaster log
you've already got security problems, as per other recent
threads.
That seems like a rather naive perspective. In a large operation
there are different roles. To draw some analogies, you might trust
your attorney with sensitive information, yet not trust her to
perform open heart surgery. You might trust your accountant with
sensitive financial data, yet not want to get into an airplane
piloted by him. If your electrician tried to fix your brakes you
might wind up careening down a hill out of control, while an
attempt by your mechanic to install a new circuit might see your
house in flames. A one-dimensional concept of "trustworthy" is
worse than useless; it's downright dangerous. Any security system
based on that is going to be weak.
This feature is valuable because it is an alternative to allowing a
user you don't trust *either* an OS login to the database server
*or* a superuser database login. Can anyone suggest an exploit
which would be available if we allowed someone who has permission
to view all data in the database read permission to the pg_log
directory and the files contained therein, assuming they do *not*
have an OS login to the database server?Capture the postmaster log. Keep on capturing it till somebody
fat-fingers their login to the extent of swapping the username and
password (yeah, I've done that, haven't you?). Scrape password from
the connection-failure log entry, figure out who it belongs to from
the next successful login, away you go. Mean time to break-in might
or might not be less than time to brute-force the MD5 you could've
read from pg_authid.
At Wisconsin Courts we had people authorized to see all data in the
database and who had to support applications using the database.
They very frequently needed to look at the logs to diagnose
problems, and time was often of the essence. We wound up creating
crontab jobs to copy the log files off the database servers to
directories on the file servers where they could be examined, so
that operations could be kept running properly. The expose to the
problem you describe was, I would argue, *greater* with this
approach than if they could access the logs at need through a
database connection.
The fact that we write such things to the log is a serious problem
that we should fix, rather than pretending that anyone who has
access to the logs should be trustworthy enough not to impersonate
another user. That position is sure to leave us vulnerable to
security breaches, and keep PostgreSQL out of many high-security
environments.
But in any case, I don't find the assumption that the user can
already read everything in the database to correspond to an
untrusted user, so I'm not sure what this exercise proves.
A one-dimensional trust model is not in any way appropriate for a
large-scale environment which cares about security. Heck, even a
"Principles of Accounting 101" class is sure involve a significant
discussion of the importance of the separation of duties. IMV it
becomes *more* significant in a computerized system, not less.
Or in short: you really shouldn't give server-filesystem access to
a user you have no trust in,
Again, a one-dimensional measure of "trustworthy" is naive and
without merit.
and I'm unclear on what the use case would be for that even if we
could restrict it reliably.
Auditors or application support staff are a couple.
The use cases I can see for this are for DBAs to be able to do
maintenance things remotely without using a full no-training-wheels
superuser account.
That is not the sort of use case that I feel is the primary target
of this.
ISTM that that type of use-case would be satisfied well enough --- not ideally, perhaps, but well enough --- by being able to grant full filesystem read and/or write to non-superuser accounts.
IMV, if we can't have a read-only version there's no real point.
--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 3:52 PM, Robert Haas <robertmhaas@gmail.com> wrote:
And it still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.
Fwiw that's not how hard links work, at least UFS semantics
permissions such as ext2 etc. Hard links are links to the same inode
and permissions are associated with the file. There are other
filesystems out there though. AFS for example associates permissions
with directories.
--
greg
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Oct 29, 2014 at 5:22 PM, Greg Stark <stark@mit.edu> wrote:
On Wed, Oct 29, 2014 at 3:52 PM, Robert Haas <robertmhaas@gmail.com> wrote:
And it still doesn't protect against the case where you hardlink to a file
and then the permissions on that file are later changed.Fwiw that's not how hard links work, at least UFS semantics
permissions such as ext2 etc. Hard links are links to the same inode
and permissions are associated with the file. There are other
filesystems out there though. AFS for example associates permissions
with directories.
That's exactly the point. The postgres user has owns file F and user
A has permissions on it. The DBA realizes this is bad and revokes
user A's permissions, but user A has already noticed and made a
hardlink to the file. When the DBA subsequently gives user A
permissions to have the server write to files in /home/a, a can induce
the server write to her hardlink even though she can no longer access
the file herself.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/29/14 3:41 PM, Jim Nasby wrote:
On 10/29/14, 2:33 PM, Tom Lane wrote:
Capture the postmaster log. Keep on capturing it till somebody
fat-fingers their login to the extent of swapping the username and
password (yeah, I've done that, haven't you?).Which begs the question: why on earth do we log passwords at all?
We don't.
This is a problem for ALTER ROLE too.
Only if you use the non-encrypted forms.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I'm marking this as "Rejected" in the commitfest. It's quite clear that
this isn't going to fly in its current form.
For the COPY FROM use case, I'd suggest just doing COPY FROM STDIN. Yes,
it's slower, but not much. And you probably could optimize it further -
there's some gratuitous memcpy()ing happening from buffer to buffer in
that codepath.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers