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.brightw...@crunchydatasolutions.com Database Engineer - www.crunchydatasolutions.com
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
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers