| /* |
| * Copyright CEA/DAM/DIF (2008) |
| * contributeur : Philippe DENIEL philippe.deniel@cea.fr |
| * Thomas LEIBOVICI thomas.leibovici@cea.fr |
| * |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public License |
| * as published by the Free Software Foundation; either version 3 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| * --------------------------------------- |
| */ |
| |
| /** |
| * @file exports.c |
| * @brief Export parsing and management |
| */ |
| #include "config.h" |
| #include "cidr.h" |
| #include "log.h" |
| #include "fsal.h" |
| #include "nfs_core.h" |
| #include "cache_inode.h" |
| #include "cache_inode_lru.h" |
| #include "nfs_file_handle.h" |
| #include "nfs_exports.h" |
| #include "nfs_ip_stats.h" |
| #include "nfs_proto_functions.h" |
| #include "nfs_dupreq.h" |
| #include "config_parsing.h" |
| #include "common_utils.h" |
| #include "nodelist.h" |
| #include <stdlib.h> |
| #include <fnmatch.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <ctype.h> |
| #include "export_mgr.h" |
| #include "fsal_up.h" |
| #include "sal_functions.h" |
| |
| struct global_export_perms export_opt = { |
| .def.anonymous_uid = ANON_UID, |
| .def.anonymous_gid = ANON_GID, |
| /* Note: Access_Type defaults to None on purpose */ |
| .def.options = EXPORT_OPTION_ROOT_SQUASH | |
| EXPORT_OPTION_NO_ACCESS | |
| EXPORT_OPTION_AUTH_NONE | |
| EXPORT_OPTION_AUTH_UNIX | |
| EXPORT_OPTION_NFSV3 | |
| EXPORT_OPTION_NFSV4 | |
| EXPORT_OPTION_UDP | |
| EXPORT_OPTION_TCP | |
| EXPORT_OPTION_NO_DELEGATIONS, |
| .def.set = UINT32_MAX |
| }; |
| |
| static void FreeClientList(struct glist_head *clients); |
| |
| static void StrExportOptions(struct export_perms *p_perms, char *buffer) |
| { |
| char *buf = buffer; |
| |
| if ((p_perms->set & EXPORT_OPTION_SQUASH_TYPES) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_ROOT) != 0) |
| buf += sprintf(buf, "no_root_squash"); |
| |
| if ((p_perms->options & EXPORT_OPTION_ALL_ANONYMOUS) != 0) |
| buf += sprintf(buf, "all_squash "); |
| |
| if ((p_perms->options & |
| (EXPORT_OPTION_ROOT | EXPORT_OPTION_ALL_ANONYMOUS)) == 0) |
| buf += sprintf(buf, "root_squash "); |
| } else |
| buf += sprintf(buf, " "); |
| |
| if ((p_perms->set & EXPORT_OPTION_ACCESS_TYPE) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_READ_ACCESS) != 0) |
| buf += sprintf(buf, ", R"); |
| else |
| buf += sprintf(buf, ", -"); |
| if ((p_perms->options & EXPORT_OPTION_WRITE_ACCESS) != 0) |
| buf += sprintf(buf, "W"); |
| else |
| buf += sprintf(buf, "-"); |
| if ((p_perms->options & EXPORT_OPTION_MD_READ_ACCESS) != 0) |
| buf += sprintf(buf, "r"); |
| else |
| buf += sprintf(buf, "-"); |
| if ((p_perms->options & EXPORT_OPTION_MD_WRITE_ACCESS) != 0) |
| buf += sprintf(buf, "w"); |
| else |
| buf += sprintf(buf, "-"); |
| } else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_PROTOCOLS) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_NFSV3) != 0) |
| buf += sprintf(buf, ", 3"); |
| else |
| buf += sprintf(buf, ", -"); |
| if ((p_perms->options & EXPORT_OPTION_NFSV4) != 0) |
| buf += sprintf(buf, "4"); |
| else |
| buf += sprintf(buf, "-"); |
| if ((p_perms->options & EXPORT_OPTION_9P) != 0) |
| buf += sprintf(buf, "9"); |
| else |
| buf += sprintf(buf, "-"); |
| } else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_TRANSPORTS) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_UDP) != 0) |
| buf += sprintf(buf, ", UDP"); |
| else |
| buf += sprintf(buf, ", ---"); |
| if ((p_perms->options & EXPORT_OPTION_TCP) != 0) |
| buf += sprintf(buf, ", TCP"); |
| else |
| buf += sprintf(buf, ", ---"); |
| if ((p_perms->options & EXPORT_OPTION_RDMA) != 0) |
| buf += sprintf(buf, ", RDMA"); |
| else |
| buf += sprintf(buf, ", ----"); |
| } else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_MANAGE_GIDS) == 0) |
| buf += sprintf(buf, ", "); |
| else if ((p_perms->options & EXPORT_OPTION_MANAGE_GIDS) != 0) |
| buf += sprintf(buf, ", Manage_Gids "); |
| else |
| buf += sprintf(buf, ", No Manage_Gids"); |
| |
| if ((p_perms->set & EXPORT_OPTION_DELEGATIONS) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_READ_DELEG) != 0) |
| buf += sprintf(buf, ", R"); |
| else |
| buf += sprintf(buf, ", -"); |
| if ((p_perms->options & EXPORT_OPTION_WRITE_DELEG) != 0) |
| buf += sprintf(buf, "W Deleg"); |
| else |
| buf += sprintf(buf, "- Deleg"); |
| } else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_ANON_UID_SET) != 0) |
| buf += sprintf(buf, ", anon_uid=%6d", |
| (int)p_perms->anonymous_uid); |
| else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_ANON_GID_SET) != 0) |
| buf += sprintf(buf, ", anon_gid=%6d", |
| (int)p_perms->anonymous_gid); |
| else |
| buf += sprintf(buf, ", "); |
| |
| if ((p_perms->set & EXPORT_OPTION_AUTH_TYPES) != 0) { |
| if ((p_perms->options & EXPORT_OPTION_AUTH_NONE) != 0) |
| buf += sprintf(buf, ", none"); |
| if ((p_perms->options & EXPORT_OPTION_AUTH_UNIX) != 0) |
| buf += sprintf(buf, ", sys"); |
| if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_NONE) != 0) |
| buf += sprintf(buf, ", krb5"); |
| if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_INTG) != 0) |
| buf += sprintf(buf, ", krb5i"); |
| if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_PRIV) != 0) |
| buf += sprintf(buf, ", krb5p"); |
| } |
| } |
| |
| void LogClientListEntry(log_components_t component, |
| exportlist_client_entry_t *entry) |
| { |
| char perms[1024]; |
| char addr[INET6_ADDRSTRLEN]; |
| char *paddr = addr; |
| |
| StrExportOptions(&entry->client_perms, perms); |
| |
| switch (entry->type) { |
| case HOSTIF_CLIENT: |
| if (inet_ntop |
| (AF_INET, &(entry->client.hostif.clientaddr), addr, |
| sizeof(addr)) == NULL) { |
| paddr = "Invalid Host address"; |
| } |
| LogMidDebug(component, " %p HOSTIF_CLIENT: %s (%s)", entry, |
| paddr, perms); |
| return; |
| |
| case NETWORK_CLIENT: |
| if (inet_ntop |
| (AF_INET, &(entry->client.network.netaddr), addr, |
| sizeof(addr)) == NULL) { |
| paddr = "Invalid Network address"; |
| } |
| LogMidDebug(component, " %p NETWORK_CLIENT: %s (%s)", entry, |
| paddr, perms); |
| return; |
| |
| case NETGROUP_CLIENT: |
| LogMidDebug(component, " %p NETWORK_CLIENT: %s (%s)", entry, |
| entry->client.netgroup.netgroupname, perms); |
| return; |
| |
| case WILDCARDHOST_CLIENT: |
| LogMidDebug(component, " %p WILDCARDHOST_CLIENT: %s (%s)", |
| entry, entry->client.wildcard.wildcard, perms); |
| return; |
| |
| case GSSPRINCIPAL_CLIENT: |
| LogMidDebug(component, " %p NETWORK_CLIENT: %s (%s)", entry, |
| entry->client.gssprinc.princname, perms); |
| return; |
| |
| case HOSTIF_CLIENT_V6: |
| if (inet_ntop |
| (AF_INET6, &(entry->client.hostif.clientaddr6), addr, |
| sizeof(addr)) == NULL) { |
| paddr = "Invalid Host address"; |
| } |
| LogMidDebug(component, " %p HOSTIF_CLIENT_V6: %s (%s)", entry, |
| paddr, perms); |
| return; |
| |
| case MATCH_ANY_CLIENT: |
| LogMidDebug(component, " %p MATCH_ANY_CLIENT: * (%s)", entry, |
| perms); |
| return; |
| |
| case PROTO_CLIENT: |
| LogCrit(component, " %p PROTO_CLIENT: <unknown>(%s)", entry, |
| perms); |
| return; |
| case BAD_CLIENT: |
| LogCrit(component, " %p BAD_CLIENT: <unknown>(%s)", entry, |
| perms); |
| return; |
| } |
| |
| LogCrit(component, " %p UNKNOWN_CLIENT_TYPE: %08x (%s)", entry, |
| entry->type, perms); |
| } |
| |
| |
| /** |
| * @brief Expand the client name token into one or more client entries |
| * |
| * @param client_list[IN] the client list this gets linked to (in tail order) |
| * @param client_tok [IN] the name string. We modify it. |
| * @param type_hint [IN] type hint from parser for client_tok |
| * @param perms [IN] pointer to the permissions to copy into each |
| * @param cnode [IN] opaque pointer needed for config_proc_error() |
| * @param err_type [OUT] error handling ref |
| * |
| * @returns 0 on success, error count on failure |
| */ |
| |
| static int add_client(struct glist_head *client_list, |
| const char *client_tok, |
| enum term_type type_hint, |
| struct export_perms *perms, |
| void *cnode, |
| struct config_error_type *err_type) |
| { |
| struct exportlist_client_entry__ *cli; |
| int errcnt = 0; |
| struct addrinfo *info; |
| CIDR *cidr; |
| uint32_t addr; |
| int rc; |
| |
| cli = gsh_calloc(sizeof(struct exportlist_client_entry__), 1); |
| if (cli == NULL) { |
| config_proc_error(cnode, err_type, |
| "Allocate of client space failed"); |
| goto out; |
| } |
| glist_init(&cli->cle_list); |
| switch (type_hint) { |
| case TERM_V4_ANY: |
| cli->type = MATCH_ANY_CLIENT; |
| break; |
| case TERM_NETGROUP: |
| if (strlen(client_tok) > MAXHOSTNAMELEN) { |
| config_proc_error(cnode, err_type, |
| "netgroup (%s) name too long", |
| client_tok); |
| err_type->invalid = true; |
| errcnt++; |
| goto out; |
| } |
| cli->client.netgroup.netgroupname = gsh_strdup(client_tok + 1); |
| cli->type = NETGROUP_CLIENT; |
| break; |
| case TERM_V4CIDR: /* this needs to be migrated to libcidr! (no v6) */ |
| cidr = cidr_from_str(client_tok); |
| if (cidr == NULL) { |
| config_proc_error(cnode, err_type, |
| "Expected a IPv4 CIDR address, got (%s)", |
| client_tok); |
| err_type->invalid = true; |
| errcnt++; |
| goto out; |
| } |
| memcpy(&addr, &cidr->addr[12], 4); |
| cli->client.network.netaddr = ntohl(addr); |
| memcpy(&addr, &cidr->mask[12], 4); |
| cli->client.network.netmask = ntohl(addr); |
| cidr_free(cidr); |
| cli->type = NETWORK_CLIENT; |
| break; |
| case TERM_REGEX: |
| if (strlen(client_tok) > MAXHOSTNAMELEN) { |
| config_proc_error(cnode, err_type, |
| "Wildcard client (%s) name too long", |
| client_tok); |
| err_type->invalid = true; |
| errcnt++; |
| goto out; |
| } |
| cli->client.wildcard.wildcard = gsh_strdup(client_tok); |
| cli->type = WILDCARDHOST_CLIENT; |
| break; |
| case TERM_V4ADDR: |
| rc = inet_pton(AF_INET, client_tok, |
| &cli->client.hostif.clientaddr); |
| assert(rc == 1); /* this had better be grok'd by now! */ |
| cli->type = HOSTIF_CLIENT; |
| break; |
| case TERM_V6ADDR: |
| rc = inet_pton(AF_INET6, client_tok, |
| &cli->client.hostif.clientaddr6); |
| assert(rc == 1); /* this had better be grok'd by now! */ |
| cli->type = HOSTIF_CLIENT_V6; |
| break; |
| case TERM_TOKEN: /* only dns names now. */ |
| rc = getaddrinfo(client_tok, NULL, NULL, &info); |
| if (rc == 0) { |
| struct addrinfo *ap, *ap_last = NULL; |
| struct in_addr in_addr_last; |
| struct in6_addr in6_addr_last; |
| |
| for (ap = info; ap != NULL; ap = ap->ai_next) { |
| LogFullDebug(COMPONENT_CONFIG, |
| "flags=%d family=%d socktype=%d protocol=%d addrlen=%d name=%s", |
| ap->ai_flags, |
| ap->ai_family, |
| ap->ai_socktype, |
| ap->ai_protocol, |
| (int) ap->ai_addrlen, |
| ap->ai_canonname); |
| if (cli == NULL) { |
| cli = gsh_calloc( |
| sizeof(struct |
| exportlist_client_entry__), |
| 1); |
| if (cli == NULL) { |
| config_proc_error(cnode, |
| err_type, |
| "Allocate of client space failed"); |
| err_type->resource = true; |
| errcnt++; |
| break; |
| } |
| glist_init(&cli->cle_list); |
| } |
| if (ap->ai_family == AF_INET && |
| (ap->ai_socktype == SOCK_STREAM || |
| ap->ai_socktype == SOCK_DGRAM)) { |
| struct in_addr infoaddr = |
| ((struct sockaddr_in *) |
| ap->ai_addr)->sin_addr; |
| if (ap_last != NULL && |
| ap_last->ai_family |
| == ap->ai_family && |
| memcmp(&infoaddr, |
| &in_addr_last, |
| sizeof(struct in_addr)) == 0) |
| continue; |
| memcpy(&(cli->client.hostif.clientaddr), |
| &infoaddr, |
| sizeof(struct in_addr)); |
| cli->type = HOSTIF_CLIENT; |
| ap_last = ap; |
| in_addr_last = infoaddr; |
| |
| } else if (ap->ai_family == AF_INET6 && |
| (ap->ai_socktype == SOCK_STREAM || |
| ap->ai_socktype == SOCK_DGRAM)) { |
| struct in6_addr infoaddr = |
| ((struct sockaddr_in6 *) |
| ap->ai_addr)->sin6_addr; |
| |
| if (ap_last != NULL && |
| ap_last->ai_family == ap->ai_family |
| && !memcmp(&infoaddr, |
| &in6_addr_last, |
| sizeof(struct in6_addr))) |
| continue; |
| /* IPv6 address */ |
| memcpy( |
| &(cli->client.hostif.clientaddr6), |
| &infoaddr, |
| sizeof(struct in6_addr)); |
| cli->type = HOSTIF_CLIENT_V6; |
| ap_last = ap; |
| in6_addr_last = infoaddr; |
| } else |
| continue; |
| cli->client_perms = *perms; |
| LogClientListEntry(COMPONENT_CONFIG, cli); |
| glist_add_tail(client_list, &cli->cle_list); |
| cli = NULL; /* let go of it */ |
| } |
| freeaddrinfo(info); |
| goto out; |
| } else { |
| config_proc_error(cnode, err_type, |
| "Client (%s)not found because %s", |
| client_tok, gai_strerror(rc)); |
| err_type->bogus = true; |
| errcnt++; |
| } |
| break; |
| default: |
| config_proc_error(cnode, err_type, |
| "Expected a client, got a %s for (%s)", |
| config_term_desc(type_hint), |
| client_tok); |
| err_type->bogus = true; |
| errcnt++; |
| goto out; |
| } |
| cli->client_perms = *perms; |
| LogClientListEntry(COMPONENT_CONFIG, cli); |
| glist_add_tail(client_list, &cli->cle_list); |
| cli = NULL; |
| out: |
| if (cli != NULL) |
| gsh_free(cli); |
| return errcnt; |
| } |
| |
| /** |
| * @brief Commit and FSAL sub-block init/commit helpers |
| */ |
| |
| /** |
| * @brief Init for CLIENT sub-block of an export. |
| * |
| * Allocate one exportlist_client structure for parameter |
| * processing. The client_commit will allocate additional |
| * exportlist_client__ storage for each of its enumerated |
| * clients and free the initial block. We only free that |
| * resource here on errors. |
| */ |
| |
| static void *client_init(void *link_mem, void *self_struct) |
| { |
| struct exportlist_client_entry__ *cli; |
| |
| assert(link_mem != NULL || self_struct != NULL); |
| |
| if (link_mem == NULL) { |
| return self_struct; |
| } else if (self_struct == NULL) { |
| cli = gsh_calloc(sizeof(struct exportlist_client_entry__), 1); |
| if (cli == NULL) |
| return NULL; |
| glist_init(&cli->cle_list); |
| cli->type = PROTO_CLIENT; |
| return cli; |
| } else { /* free resources case */ |
| cli = self_struct; |
| |
| if (!glist_empty(&cli->cle_list)) |
| FreeClientList(&cli->cle_list); |
| assert(glist_empty(&cli->cle_list)); |
| gsh_free(cli); |
| return NULL; |
| } |
| } |
| |
| /** |
| * @brief Commit this client block |
| * |
| * Validate "clients" token(s) and perms. We enter with a client entry |
| * allocated by proc_block. Since we expand the clients token both |
| * here and in add_client, we allocate new client entries and free |
| * what was passed to us rather than try and link it in. |
| * |
| * @param node [IN] the config_node **not used** |
| * @param link_mem [IN] the exportlist entry. add_client adds to its glist. |
| * @param self_struct [IN] the filled out client entry with a PROTO_CLIENT |
| * |
| * @return 0 on success, error count for failure. |
| */ |
| |
| static int client_commit(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type) |
| { |
| struct exportlist_client_entry__ *cli; |
| struct gsh_export *export; |
| int errcnt = 0; |
| |
| export = container_of(link_mem, struct gsh_export, clients); |
| cli = self_struct; |
| assert(cli->type == PROTO_CLIENT); |
| if (glist_empty(&cli->cle_list)) { |
| LogCrit(COMPONENT_CONFIG, |
| "No clients specified"); |
| err_type->invalid = true; |
| errcnt++; |
| } else { |
| glist_splice_tail(&export->clients, &cli->cle_list); |
| } |
| if (errcnt == 0) |
| client_init(link_mem, self_struct); |
| return errcnt; |
| } |
| |
| /** |
| * @brief Commit a FSAL sub-block |
| * |
| * Use the Name parameter passed in via the link_mem to lookup the |
| * fsal. If the fsal is not loaded (yet), load it and call its init. |
| * |
| * Create an export and pass the FSAL sub-block to it so that the |
| * fsal method can process the rest of the parameters in the block |
| */ |
| |
| static int fsal_commit(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type) |
| { |
| struct fsal_export **exp_hdl = link_mem; |
| struct gsh_export *export = |
| container_of(exp_hdl, struct gsh_export, fsal_export); |
| struct fsal_args *fp = self_struct; |
| struct fsal_module *fsal; |
| struct root_op_context root_op_context; |
| uint64_t MaxRead, MaxWrite; |
| fsal_status_t status; |
| int errcnt; |
| |
| /* Initialize req_ctx */ |
| init_root_op_context(&root_op_context, export, NULL, 0, 0, |
| UNKNOWN_REQUEST); |
| |
| errcnt = fsal_load_init(node, fp->name, &fsal, err_type); |
| if (errcnt > 0) |
| goto err; |
| |
| /* Some admins stuff a '/' at the end for some reason. |
| * chomp it so we have a /dir/path/basename to work |
| * with. But only if it's a non-root path starting |
| * with /. |
| */ |
| if (export->fullpath[0] == '/') { |
| int pathlen; |
| |
| pathlen = strlen(export->fullpath); |
| while ((export->fullpath[pathlen - 1] == '/') && |
| (pathlen > 1)) |
| pathlen--; |
| export->fullpath[pathlen] = '\0'; |
| } |
| status = fsal->m_ops.create_export(fsal, |
| node, err_type, |
| &fsal_up_top); |
| if ((export->options_set & EXPORT_OPTION_EXPIRE_SET) == 0) |
| export->expire_time_attr = cache_param.expire_time_attr; |
| |
| if (FSAL_IS_ERROR(status)) { |
| fsal_put(fsal); |
| LogCrit(COMPONENT_CONFIG, |
| "Could not create export for (%s) to (%s)", |
| export->pseudopath, |
| export->fullpath); |
| err_type->export = true; |
| errcnt++; |
| goto err; |
| } |
| |
| assert(root_op_context.req_ctx.fsal_export != NULL); |
| export->fsal_export = root_op_context.req_ctx.fsal_export; |
| |
| /* We are connected up to the fsal side. Now |
| * validate maxread/write etc with fsal params |
| */ |
| MaxRead = export->fsal_export-> |
| exp_ops.fs_maxread(export->fsal_export); |
| MaxWrite = export->fsal_export-> |
| exp_ops.fs_maxwrite(export->fsal_export); |
| |
| if (export->MaxRead > MaxRead && MaxRead != 0) { |
| LogInfo(COMPONENT_CONFIG, |
| "Readjusting MaxRead to FSAL, %" PRIu64 " -> %" PRIu64, |
| export->MaxRead, |
| MaxRead); |
| export->MaxRead = MaxRead; |
| } |
| if (export->MaxWrite > MaxWrite && MaxWrite != 0) { |
| LogInfo(COMPONENT_CONFIG, |
| "Readjusting MaxWrite to FSAL, %"PRIu64" -> %"PRIu64, |
| export->MaxWrite, |
| MaxWrite); |
| export->MaxWrite = MaxWrite; |
| } |
| |
| err: |
| release_root_op_context(); |
| return errcnt; |
| } |
| |
| /** |
| * @brief EXPORT block handlers |
| */ |
| |
| /** |
| * @brief Initialize an export block |
| * |
| * There is no link_mem init required because we are allocating |
| * here and doing an insert_gsh_export at the end of export_commit |
| * to attach it to the export manager. |
| * |
| * Use free_exportlist here because in this case, we have not |
| * gotten far enough to hand it over to the export manager. |
| */ |
| |
| static void *export_init(void *link_mem, void *self_struct) |
| { |
| struct gsh_export *export; |
| |
| if (self_struct == NULL) { |
| export = alloc_export(); |
| if (export == NULL) |
| return NULL; |
| return export; |
| } else { /* free resources case */ |
| export = self_struct; |
| free_export(export); |
| return NULL; |
| } |
| } |
| |
| /** |
| * @brief Commit an export block |
| * |
| * Validate the export level parameters. fsal and client |
| * parameters are already done. |
| */ |
| |
| static int export_commit_common(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type, |
| bool add_export) |
| { |
| struct gsh_export *export, *probe_exp; |
| int errcnt = 0; |
| char perms[1024]; |
| |
| export = self_struct; |
| |
| /* validate the export now */ |
| if (export->export_perms.options & EXPORT_OPTION_NFSV4) { |
| if (export->pseudopath == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Exporting to NFSv4 but not Pseudo path defined"); |
| err_type->invalid = true; |
| errcnt++; |
| goto err_out; |
| } else if (export->export_id == 0 && |
| strcmp(export->pseudopath, "/") != 0) { |
| LogCrit(COMPONENT_CONFIG, |
| "Export id 0 can only export \"/\" not (%s)", |
| export->pseudopath); |
| err_type->invalid = true; |
| errcnt++; |
| goto err_out; |
| } |
| } |
| if (export->pseudopath != NULL && |
| export->pseudopath[0] != '/') { |
| LogCrit(COMPONENT_CONFIG, |
| "A Pseudo path must be an absolute path"); |
| err_type->invalid = true; |
| errcnt++; |
| } |
| if (export->export_id == 0) { |
| if (export->pseudopath == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Pseudo path must be \"/\" for export id 0"); |
| err_type->invalid = true; |
| errcnt++; |
| } else if (export->pseudopath[1] != '\0') { |
| LogCrit(COMPONENT_CONFIG, |
| "Pseudo path must be \"/\" for export id 0"); |
| err_type->invalid = true; |
| errcnt++; |
| } |
| if ((export->export_perms.options & |
| EXPORT_OPTION_PROTOCOLS) != EXPORT_OPTION_NFSV4) { |
| LogCrit(COMPONENT_CONFIG, |
| "Export id 0 must indicate Protocols=4"); |
| err_type->invalid = true; |
| errcnt++; |
| } |
| } |
| if (errcnt) |
| goto err_out; /* have basic errors. don't even try more... */ |
| |
| /* export->fsal_export is valid iff fsal_commit succeeds. |
| * Config code calls export_commit even if fsal_commit fails at |
| * the moment, so error out here if fsal_commit failed. |
| */ |
| if (export->fsal_export == NULL) { |
| err_type->validate = true; |
| errcnt++; |
| goto err_out; |
| } |
| |
| probe_exp = get_gsh_export(export->export_id); |
| if (probe_exp != NULL) { |
| LogDebug(COMPONENT_CONFIG, |
| "Export %d already exists", export->export_id); |
| put_gsh_export(probe_exp); |
| err_type->exists = true; |
| errcnt++; |
| } |
| if (export->FS_tag != NULL) { |
| probe_exp = get_gsh_export_by_tag(export->FS_tag); |
| if (probe_exp != NULL) { |
| put_gsh_export(probe_exp); |
| LogCrit(COMPONENT_CONFIG, |
| "Tag (%s) is a duplicate", |
| export->FS_tag); |
| if (!err_type->exists) |
| err_type->invalid = true; |
| errcnt++; |
| } |
| } |
| if (export->pseudopath != NULL) { |
| probe_exp = get_gsh_export_by_pseudo(export->pseudopath, true); |
| if (probe_exp != NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Pseudo path (%s) is a duplicate", |
| export->pseudopath); |
| if (!err_type->exists) |
| err_type->invalid = true; |
| errcnt++; |
| put_gsh_export(probe_exp); |
| } |
| } |
| probe_exp = get_gsh_export_by_path(export->fullpath, true); |
| if (probe_exp != NULL && |
| export->pseudopath == NULL && |
| export->FS_tag == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Duplicate path (%s) without unique tag or Pseudo path", |
| export->fullpath); |
| err_type->invalid = true; |
| errcnt++; |
| put_gsh_export(probe_exp); |
| } |
| if (errcnt) { |
| if (err_type->exists && !err_type->invalid) |
| LogDebug(COMPONENT_CONFIG, |
| "Duplicate export id = %d", |
| export->export_id); |
| else |
| LogCrit(COMPONENT_CONFIG, |
| "Duplicate export id = %d", |
| export->export_id); |
| goto err_out; /* have errors. don't init or load a fsal */ |
| } |
| |
| /* now probe the fsal and init it */ |
| /* pass along the block that is/was the FS_Specific */ |
| if (!insert_gsh_export(export)) { |
| LogCrit(COMPONENT_CONFIG, |
| "Export id %d already in use.", |
| export->export_id); |
| err_type->exists = true; |
| errcnt++; |
| goto err_out; |
| } |
| |
| /* add_export_commit shouldn't add this export to mount work as |
| * add_export_commit deals with creating pseudo mount directly. |
| * So add this export to mount work only if NFSv4 exported and |
| * is not a dynamically added export. |
| */ |
| if (!add_export && export->export_perms.options & EXPORT_OPTION_NFSV4) |
| export_add_to_mount_work(export); |
| |
| StrExportOptions(&export->export_perms, perms); |
| |
| LogInfo(COMPONENT_CONFIG, |
| "Export %d created at pseudo (%s) with path (%s) and tag (%s) perms (%s)", |
| export->export_id, export->pseudopath, |
| export->fullpath, export->FS_tag, perms); |
| |
| LogInfo(COMPONENT_CONFIG, |
| "Export %d has %ld defined clients", export->export_id, |
| glist_length(&export->clients)); |
| put_gsh_export(export); |
| return 0; |
| |
| err_out: |
| return errcnt; |
| } |
| |
| static int export_commit(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type) |
| { |
| bool add_export = false; /* not a dynamic add export */ |
| |
| return export_commit_common(node, link_mem, self_struct, err_type, |
| add_export); |
| } |
| |
| /** |
| * @brief Display an export block |
| * |
| * Validate the export level parameters. fsal and client |
| * parameters are already done. |
| */ |
| |
| static void export_display(const char *step, void *node, |
| void *link_mem, void *self_struct) |
| { |
| struct gsh_export *export = self_struct; |
| char perms[1024]; |
| |
| StrExportOptions(&export->export_perms, perms); |
| |
| LogMidDebug(COMPONENT_CONFIG, |
| "%s %p Export %d pseudo (%s) with path (%s) and tag (%s) perms (%s)", |
| step, export, export->export_id, export->pseudopath, |
| export->fullpath, export->FS_tag, perms); |
| } |
| |
| /** |
| * @brief Commit an add export |
| * commit the export |
| * init export root and mount it in pseudo fs |
| */ |
| |
| static int add_export_commit(void *node, void *link_mem, void *self_struct, |
| struct config_error_type *err_type) |
| { |
| struct gsh_export *export = self_struct; |
| int errcnt = 0; |
| int status; |
| bool add_export = true; /* dynamic add export */ |
| |
| errcnt = export_commit_common(node, link_mem, self_struct, err_type, |
| add_export); |
| if (errcnt != 0) |
| goto err_out; |
| |
| status = init_export_root(export); |
| if (status) { |
| export_revert(export); |
| errcnt++; |
| if (status == EINVAL) |
| err_type->invalid = true; |
| else if (status == EFAULT) |
| err_type->internal = true; |
| else |
| err_type->resource = true; |
| goto err_out; |
| } |
| |
| if (!mount_gsh_export(export)) { |
| export_revert(export); |
| err_type->internal = true; |
| errcnt++; |
| } |
| |
| err_out: |
| return errcnt; |
| } |
| |
| /** |
| * @brief Initialize an EXPORT_DEFAULTS block |
| * |
| */ |
| |
| static void *export_defaults_init(void *link_mem, void *self_struct) |
| { |
| if (self_struct == NULL) |
| return &export_opt; |
| else |
| return NULL; |
| } |
| |
| /** |
| * @brief Commit an EXPORT_DEFAULTS block |
| * |
| * Validate the export level parameters. fsal and client |
| * parameters are already done. |
| */ |
| |
| static int export_defaults_commit(void *node, void *link_mem, |
| void *self_struct, |
| struct config_error_type *err_type) |
| { |
| return 0; |
| } |
| |
| /** |
| * @brief Display an EXPORT_DEFAULTS block |
| * |
| * Validate the export level parameters. fsal and client |
| * parameters are already done. |
| */ |
| |
| static void export_defaults_display(const char *step, void *node, |
| void *link_mem, void *self_struct) |
| { |
| struct export_perms *defaults = self_struct; |
| char perms[1024]; |
| |
| StrExportOptions(defaults, perms); |
| |
| LogEvent(COMPONENT_CONFIG, |
| "%s Export Defaults (%s)", |
| step, perms); |
| } |
| |
| /** |
| * @brief Configuration processing tables for EXPORT blocks |
| */ |
| |
| /** |
| * @brief Access types list for the Access_type parameter |
| */ |
| |
| static struct config_item_list access_types[] = { |
| CONFIG_LIST_TOK("NONE", 0), |
| CONFIG_LIST_TOK("RW", (EXPORT_OPTION_RW_ACCESS | |
| EXPORT_OPTION_MD_ACCESS)), |
| CONFIG_LIST_TOK("RO", (EXPORT_OPTION_READ_ACCESS | |
| EXPORT_OPTION_MD_READ_ACCESS)), |
| CONFIG_LIST_TOK("MDONLY", EXPORT_OPTION_MD_ACCESS), |
| CONFIG_LIST_TOK("MDONLY_RO", EXPORT_OPTION_MD_READ_ACCESS), |
| CONFIG_LIST_EOL |
| }; |
| |
| /** |
| * @brief Protocols options list for NFS_Protocols parameter |
| */ |
| |
| static struct config_item_list nfs_protocols[] = { |
| CONFIG_LIST_TOK("3", EXPORT_OPTION_NFSV3), |
| CONFIG_LIST_TOK("4", EXPORT_OPTION_NFSV4), |
| CONFIG_LIST_TOK("NFS3", EXPORT_OPTION_NFSV3), |
| CONFIG_LIST_TOK("NFS4", EXPORT_OPTION_NFSV4), |
| CONFIG_LIST_TOK("V3", EXPORT_OPTION_NFSV3), |
| CONFIG_LIST_TOK("V4", EXPORT_OPTION_NFSV4), |
| CONFIG_LIST_TOK("NFSV3", EXPORT_OPTION_NFSV3), |
| CONFIG_LIST_TOK("NFSV4", EXPORT_OPTION_NFSV4), |
| CONFIG_LIST_TOK("9P", EXPORT_OPTION_9P), |
| CONFIG_LIST_EOL |
| }; |
| |
| /** |
| * @brief Transport type options list for Transport_Protocols parameter |
| */ |
| |
| static struct config_item_list transports[] = { |
| CONFIG_LIST_TOK("UDP", EXPORT_OPTION_UDP), |
| CONFIG_LIST_TOK("TCP", EXPORT_OPTION_TCP), |
| CONFIG_LIST_EOL |
| }; |
| |
| /** |
| * @brief Security options list for SecType parameter |
| */ |
| |
| static struct config_item_list sec_types[] = { |
| CONFIG_LIST_TOK("none", EXPORT_OPTION_AUTH_NONE), |
| CONFIG_LIST_TOK("sys", EXPORT_OPTION_AUTH_UNIX), |
| CONFIG_LIST_TOK("krb5", EXPORT_OPTION_RPCSEC_GSS_NONE), |
| CONFIG_LIST_TOK("krb5i", EXPORT_OPTION_RPCSEC_GSS_INTG), |
| CONFIG_LIST_TOK("krb5p", EXPORT_OPTION_RPCSEC_GSS_PRIV), |
| CONFIG_LIST_EOL |
| }; |
| |
| /** |
| * @brief Client UID squash item list for Squash parameter |
| */ |
| |
| static struct config_item_list squash_types[] = { |
| CONFIG_LIST_TOK("Root", EXPORT_OPTION_ROOT_SQUASH), |
| CONFIG_LIST_TOK("Root_Squash", EXPORT_OPTION_ROOT_SQUASH), |
| CONFIG_LIST_TOK("RootSquash", EXPORT_OPTION_ROOT_SQUASH), |
| CONFIG_LIST_TOK("All", EXPORT_OPTION_ALL_ANONYMOUS), |
| CONFIG_LIST_TOK("All_Squash", EXPORT_OPTION_ALL_ANONYMOUS), |
| CONFIG_LIST_TOK("AllSquash", EXPORT_OPTION_ALL_ANONYMOUS), |
| CONFIG_LIST_TOK("All_Anonymous", EXPORT_OPTION_ALL_ANONYMOUS), |
| CONFIG_LIST_TOK("AllAnonymous", EXPORT_OPTION_ALL_ANONYMOUS), |
| CONFIG_LIST_TOK("No_Root_Squash", EXPORT_OPTION_ROOT), |
| CONFIG_LIST_TOK("None", EXPORT_OPTION_ROOT), |
| CONFIG_LIST_TOK("NoIdSquash", EXPORT_OPTION_ROOT), |
| CONFIG_LIST_EOL |
| }; |
| |
| /** |
| * @brief Delegations types list for the Delegations parameter |
| */ |
| |
| static struct config_item_list delegations[] = { |
| CONFIG_LIST_TOK("NONE", EXPORT_OPTION_NO_DELEGATIONS), |
| CONFIG_LIST_TOK("Read", EXPORT_OPTION_READ_DELEG), |
| CONFIG_LIST_TOK("Write", EXPORT_OPTION_WRITE_DELEG), |
| CONFIG_LIST_TOK("Readwrite", EXPORT_OPTION_DELEGATIONS), |
| CONFIG_LIST_TOK("R", EXPORT_OPTION_READ_DELEG), |
| CONFIG_LIST_TOK("W", EXPORT_OPTION_WRITE_DELEG), |
| CONFIG_LIST_TOK("RW", EXPORT_OPTION_DELEGATIONS), |
| CONFIG_LIST_EOL |
| }; |
| |
| struct config_item_list deleg_types[] = { |
| CONFIG_LIST_TOK("NONE", FSAL_OPTION_NO_DELEGATIONS), |
| CONFIG_LIST_TOK("Read", FSAL_OPTION_FILE_READ_DELEG), |
| CONFIG_LIST_TOK("Write", FSAL_OPTION_FILE_WRITE_DELEG), |
| CONFIG_LIST_TOK("Readwrite", FSAL_OPTION_FILE_DELEGATIONS), |
| CONFIG_LIST_TOK("R", FSAL_OPTION_FILE_READ_DELEG), |
| CONFIG_LIST_TOK("W", FSAL_OPTION_FILE_WRITE_DELEG), |
| CONFIG_LIST_TOK("RW", FSAL_OPTION_FILE_DELEGATIONS), |
| CONFIG_LIST_EOL |
| }; |
| |
| #define CONF_EXPORT_PERMS(_struct_, _perms_) \ |
| /* Note: Access_Type defaults to None on purpose */ \ |
| CONF_ITEM_ENUM_BITS_SET("Access_Type", \ |
| EXPORT_OPTION_NO_ACCESS, \ |
| EXPORT_OPTION_ACCESS_TYPE, \ |
| access_types, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_LIST_BITS_SET("Protocols", \ |
| EXPORT_OPTION_PROTOCOLS, EXPORT_OPTION_PROTOCOLS, \ |
| nfs_protocols, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_LIST_BITS_SET("Transports", \ |
| EXPORT_OPTION_TRANSPORTS, EXPORT_OPTION_TRANSPORTS, \ |
| transports, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_ANON_ID_SET("Anonymous_uid", \ |
| ANON_UID, _struct_, _perms_.anonymous_uid, \ |
| EXPORT_OPTION_ANON_UID_SET, _perms_.set), \ |
| CONF_ITEM_ANON_ID_SET("Anonymous_gid", \ |
| ANON_GID, _struct_, _perms_.anonymous_gid, \ |
| EXPORT_OPTION_ANON_GID_SET, _perms_.set), \ |
| CONF_ITEM_LIST_BITS_SET("SecType", \ |
| EXPORT_OPTION_AUTH_NONE | EXPORT_OPTION_AUTH_UNIX, \ |
| EXPORT_OPTION_AUTH_TYPES, \ |
| sec_types, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_BOOLBIT_SET("PrivilegedPort", \ |
| false, EXPORT_OPTION_PRIVILEGED_PORT, \ |
| _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_BOOLBIT_SET("Manage_Gids", \ |
| false, EXPORT_OPTION_MANAGE_GIDS, \ |
| _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_LIST_BITS_SET("Squash", \ |
| EXPORT_OPTION_ROOT_SQUASH, EXPORT_OPTION_SQUASH_TYPES, \ |
| squash_types, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_BOOLBIT_SET("NFS_Commit", \ |
| false, EXPORT_OPTION_COMMIT, \ |
| _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_ENUM_BITS_SET("Delegations", \ |
| EXPORT_OPTION_NO_DELEGATIONS, EXPORT_OPTION_DELEGATIONS,\ |
| delegations, _struct_, _perms_.options, _perms_.set), \ |
| CONF_ITEM_BOOLBIT_SET("Disable_ACL", \ |
| false, EXPORT_OPTION_DISABLE_ACL, \ |
| _struct_, _perms_.options, _perms_.set) |
| |
| /** |
| * @brief Process a list of clients for a client block |
| * |
| * CONFIG_PROC handler that gets called for each token in the term list. |
| * Create a exportlist_client_entry__ for each token and link it into |
| * the proto client's cle_list list head. We will pass that head to the |
| * export in commit. |
| * |
| * NOTES: this is the place to expand a node list with perhaps moving the |
| * call to add_client into the expander rather than build a list there |
| * to be then walked here... |
| * |
| * @param token [IN] pointer to token string from parse tree |
| * @param type_hint [IN] a type hint from what the parser recognized |
| * @param item [IN] pointer to the config item table entry |
| * @param param_addr [IN] pointer to prototype client entry |
| * @param err_type [OUT] error handling |
| * @return error count |
| */ |
| |
| static int client_adder(const char *token, |
| enum term_type type_hint, |
| struct config_item *item, |
| void *param_addr, |
| void *cnode, |
| struct config_error_type *err_type) |
| { |
| struct exportlist_client_entry__ *proto_cli; |
| int rc; |
| |
| proto_cli = container_of(param_addr, |
| struct exportlist_client_entry__, |
| cle_list); |
| #ifdef USE_NODELIST |
| #error "Node list expansion goes here but not yet" |
| #endif |
| LogMidDebug(COMPONENT_CONFIG, "Adding client %s", token); |
| rc = add_client(&proto_cli->cle_list, |
| token, type_hint, |
| &proto_cli->client_perms, cnode, err_type); |
| return rc; |
| } |
| |
| /** |
| * @brief Table of client sub-block parameters |
| * |
| * NOTE: node discovery is ordered by this table! |
| * "Access" is last because we must have all other params processed |
| * before we walk the list of accessing clients! |
| */ |
| |
| static struct config_item client_params[] = { |
| CONF_EXPORT_PERMS(exportlist_client_entry__, client_perms), |
| CONF_ITEM_PROC("Clients", noop_conf_init, client_adder, |
| exportlist_client_entry__, cle_list), |
| CONFIG_EOL |
| }; |
| |
| /** |
| * @brief Table of DEXPORT_DEFAULTS block parameters |
| * |
| * NOTE: node discovery is ordered by this table! |
| */ |
| |
| static struct config_item export_defaults_params[] = { |
| CONF_EXPORT_PERMS(global_export_perms, conf), |
| CONFIG_EOL |
| }; |
| |
| /** |
| * @brief Table of FSAL sub-block parameters |
| * |
| * NOTE: this points to a struct that is private to |
| * fsal_commit. |
| */ |
| |
| static struct config_item fsal_params[] = { |
| CONF_ITEM_STR("Name", 1, 10, NULL, |
| fsal_args, name), /* cheater union */ |
| CONFIG_EOL |
| }; |
| |
| /** |
| * @brief Table of EXPORT block parameters |
| * |
| * NOTE: the Client and FSAL sub-blocks must be the *last* |
| * two entries in the list. This is so all other |
| * parameters have been processed before these sub-blocks |
| * are processed. |
| */ |
| |
| static struct config_item export_params[] = { |
| CONF_MAND_UI16("Export_id", 0, UINT16_MAX, 1, |
| gsh_export, export_id), |
| CONF_MAND_PATH("Path", 1, MAXPATHLEN, NULL, |
| gsh_export, fullpath), /* must chomp '/' */ |
| CONF_UNIQ_PATH("Pseudo", 1, MAXPATHLEN, NULL, |
| gsh_export, pseudopath), |
| CONF_ITEM_UI64("MaxRead", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, |
| gsh_export, MaxRead), |
| CONF_ITEM_UI64("MaxWrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, |
| gsh_export, MaxWrite), |
| CONF_ITEM_UI64("PrefRead", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, |
| gsh_export, PrefRead), |
| CONF_ITEM_UI64("PrefWrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, |
| gsh_export, PrefWrite), |
| CONF_ITEM_UI64("PrefReaddir", 512, FSAL_MAXIOSIZE, 16384, |
| gsh_export, PrefReaddir), |
| CONF_ITEM_FSID_SET("Filesystem_id", 666, 666, |
| gsh_export, filesystem_id, /* major.minor */ |
| EXPORT_OPTION_FSID_SET, options_set), |
| CONF_ITEM_STR("Tag", 1, MAXPATHLEN, NULL, |
| gsh_export, FS_tag), |
| CONF_ITEM_UI64("MaxOffsetWrite", 512, UINT64_MAX, UINT64_MAX, |
| gsh_export, MaxOffsetWrite), |
| CONF_ITEM_UI64("MaxOffsetRead", 512, UINT64_MAX, UINT64_MAX, |
| gsh_export, MaxOffsetRead), |
| CONF_ITEM_BOOLBIT_SET("UseCookieVerifier", |
| true, EXPORT_OPTION_USE_COOKIE_VERIFIER, |
| gsh_export, options, options_set), |
| CONF_ITEM_BOOLBIT_SET("DisableReaddirPlus", |
| false, EXPORT_OPTION_NO_READDIR_PLUS, |
| gsh_export, options, options_set), |
| CONF_ITEM_BOOLBIT_SET("Trust_Readdir_Negative_Cache", |
| false, EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE, |
| gsh_export, options, options_set), |
| CONF_EXPORT_PERMS(gsh_export, export_perms), |
| CONF_ITEM_BLOCK("Client", client_params, |
| client_init, client_commit, |
| gsh_export, clients), |
| CONF_ITEM_I32_SET("Attr_Expiration_Time", -1, INT32_MAX, 60, |
| gsh_export, expire_time_attr, |
| EXPORT_OPTION_EXPIRE_SET, options_set), |
| CONF_RELAX_BLOCK("FSAL", fsal_params, |
| fsal_init, fsal_commit, |
| gsh_export, fsal_export), |
| CONFIG_EOL |
| }; |
| |
| /** |
| * @brief Top level definition for an EXPORT block |
| */ |
| |
| static struct config_block export_param = { |
| .dbus_interface_name = "org.ganesha.nfsd.config.%d", |
| .blk_desc.name = "EXPORT", |
| .blk_desc.type = CONFIG_BLOCK, |
| .blk_desc.u.blk.init = export_init, |
| .blk_desc.u.blk.params = export_params, |
| .blk_desc.u.blk.commit = export_commit, |
| .blk_desc.u.blk.display = export_display |
| }; |
| |
| /** |
| * @brief Top level definition for an ADD EXPORT block |
| */ |
| |
| struct config_block add_export_param = { |
| .dbus_interface_name = "org.ganesha.nfsd.config.%d", |
| .blk_desc.name = "EXPORT", |
| .blk_desc.type = CONFIG_BLOCK, |
| .blk_desc.u.blk.init = export_init, |
| .blk_desc.u.blk.params = export_params, |
| .blk_desc.u.blk.commit = add_export_commit, |
| .blk_desc.u.blk.display = export_display |
| }; |
| |
| |
| /** |
| * @brief Top level definition for an EXPORT_DEFAULTS block |
| */ |
| |
| struct config_block export_defaults_param = { |
| .dbus_interface_name = "org.ganesha.nfsd.config.defaults", |
| .blk_desc.name = "EXPORT_DEFAULTS", |
| .blk_desc.type = CONFIG_BLOCK, |
| .blk_desc.u.blk.init = export_defaults_init, |
| .blk_desc.u.blk.params = export_defaults_params, |
| .blk_desc.u.blk.commit = export_defaults_commit, |
| .blk_desc.u.blk.display = export_defaults_display |
| }; |
| |
| /** |
| * @brief builds an export entry for '/' with default parameters |
| * |
| * If export_id = 0 has not been specified, and not other export |
| * for Pseudo "/" has been specified, build an FSAL_PSEUDO export |
| * for the root of the Pseudo FS. |
| * |
| * @return -1 on error, 0 if we already have one, 1 if created one |
| */ |
| |
| static int build_default_root(struct config_error_type *err_type) |
| { |
| struct gsh_export *export; |
| struct fsal_module *fsal_hdl = NULL; |
| struct root_op_context root_op_context; |
| |
| /* See if export_id = 0 has already been specified */ |
| export = get_gsh_export(0); |
| |
| if (export != NULL) { |
| /* export_id = 0 has already been specified */ |
| LogDebug(COMPONENT_CONFIG, |
| "Export 0 already exists"); |
| put_gsh_export(export); |
| return 0; |
| } |
| |
| /* See if another export with Pseudo = "/" has already been specified. |
| */ |
| export = get_gsh_export_by_pseudo("/", true); |
| |
| if (export != NULL) { |
| /* Pseudo = / has already been specified */ |
| LogDebug(COMPONENT_CONFIG, |
| "Pseudo root already exists"); |
| put_gsh_export(export); |
| return 0; |
| } |
| |
| /* allocate and initialize the exportlist part with the id */ |
| LogDebug(COMPONENT_CONFIG, |
| "Allocating Pseudo root export"); |
| export = alloc_export(); |
| |
| if (export == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "Could not allocate space for pseudoroot export"); |
| return -1; |
| } |
| |
| /* Initialize req_ctx */ |
| init_root_op_context(&root_op_context, export, NULL, 0, 0, |
| UNKNOWN_REQUEST); |
| |
| export->filesystem_id.major = 152; |
| export->filesystem_id.minor = 152; |
| export->MaxWrite = FSAL_MAXIOSIZE; |
| export->MaxRead = FSAL_MAXIOSIZE; |
| export->PrefWrite = FSAL_MAXIOSIZE; |
| export->PrefRead = FSAL_MAXIOSIZE; |
| export->PrefReaddir = 16384; |
| |
| /* Default anonymous uid and gid */ |
| export->export_perms.anonymous_uid = (uid_t) ANON_UID; |
| export->export_perms.anonymous_gid = (gid_t) ANON_GID; |
| |
| /* Support only NFS v4 and TCP. |
| * Root is allowed |
| * MD Read Access |
| * Allow use of default auth types |
| */ |
| export->export_perms.options = EXPORT_OPTION_ROOT | |
| EXPORT_OPTION_MD_READ_ACCESS | |
| EXPORT_OPTION_NFSV4 | |
| EXPORT_OPTION_AUTH_TYPES | |
| EXPORT_OPTION_TCP; |
| |
| export->export_perms.set = EXPORT_OPTION_SQUASH_TYPES | |
| EXPORT_OPTION_ACCESS_TYPE | |
| EXPORT_OPTION_PROTOCOLS | |
| EXPORT_OPTION_TRANSPORTS | |
| EXPORT_OPTION_AUTH_TYPES; |
| |
| export->options = EXPORT_OPTION_USE_COOKIE_VERIFIER; |
| export->options_set = EXPORT_OPTION_FSID_SET | |
| EXPORT_OPTION_USE_COOKIE_VERIFIER; |
| |
| /* Set the fullpath to "/" */ |
| export->fullpath = gsh_strdup("/"); |
| |
| /* Set Pseudo Path to "/" */ |
| export->pseudopath = gsh_strdup("/"); |
| |
| /* Assign FSAL_PSEUDO */ |
| fsal_hdl = lookup_fsal("PSEUDO"); |
| |
| if (fsal_hdl == NULL) { |
| LogCrit(COMPONENT_CONFIG, |
| "FSAL PSEUDO is not loaded!"); |
| goto err_out; |
| } else { |
| fsal_status_t rc; |
| |
| rc = fsal_hdl->m_ops.create_export(fsal_hdl, |
| NULL, |
| err_type, |
| &fsal_up_top); |
| |
| if (FSAL_IS_ERROR(rc)) { |
| fsal_put(fsal_hdl); |
| LogCrit(COMPONENT_CONFIG, |
| "Could not create FSAL export for %s", |
| export->fullpath); |
| goto err_out; |
| } |
| |
| } |
| |
| assert(root_op_context.req_ctx.fsal_export != NULL); |
| export->fsal_export = root_op_context.req_ctx.fsal_export; |
| |
| if (!insert_gsh_export(export)) { |
| export->fsal_export->exp_ops.release(export->fsal_export); |
| fsal_put(fsal_hdl); |
| LogCrit(COMPONENT_CONFIG, |
| "Failed to insert pseudo root In use??"); |
| goto err_out; |
| } |
| |
| /* This export must be mounted to the PseudoFS */ |
| export_add_to_mount_work(export); |
| |
| LogInfo(COMPONENT_CONFIG, |
| "Export 0 (/) successfully created"); |
| |
| put_gsh_export(export); /* all done, let go */ |
| release_root_op_context(); |
| return 1; |
| |
| err_out: |
| free_export(export); |
| release_root_op_context(); |
| return -1; |
| } |
| |
| /** |
| * @brief Read the export entries from the parsed configuration file. |
| * |
| * @param[in] in_config The file that contains the export list |
| * |
| * @return A negative value on error, |
| * the number of export entries else. |
| */ |
| |
| int ReadExports(config_file_t in_config, |
| struct config_error_type *err_type) |
| { |
| int rc, num_exp; |
| |
| rc = load_config_from_parse(in_config, |
| &export_defaults_param, |
| NULL, |
| false, |
| err_type); |
| if (rc < 0) { |
| LogCrit(COMPONENT_CONFIG, "Export defaults block error"); |
| return -1; |
| } |
| |
| num_exp = load_config_from_parse(in_config, |
| &export_param, |
| NULL, |
| false, |
| err_type); |
| if (num_exp < 0) { |
| LogCrit(COMPONENT_CONFIG, "Export block error"); |
| return -1; |
| } |
| |
| rc = build_default_root(err_type); |
| if (rc < 0) { |
| LogCrit(COMPONENT_CONFIG, "No pseudo root!"); |
| return -1; |
| } |
| |
| return num_exp; |
| } |
| |
| static void FreeClientList(struct glist_head *clients) |
| { |
| struct glist_head *glist; |
| struct glist_head *glistn; |
| |
| glist_for_each_safe(glist, glistn, clients) { |
| exportlist_client_entry_t *client; |
| |
| client = |
| glist_entry(glist, exportlist_client_entry_t, cle_list); |
| glist_del(&client->cle_list); |
| if (client->type == NETGROUP_CLIENT && |
| client->client.netgroup.netgroupname != NULL) |
| gsh_free(client->client.netgroup.netgroupname); |
| if (client->type == WILDCARDHOST_CLIENT && |
| client->client.wildcard.wildcard != NULL) |
| gsh_free(client->client.wildcard.wildcard); |
| if (client->type == GSSPRINCIPAL_CLIENT && |
| client->client.gssprinc.princname != NULL) |
| gsh_free(client->client.gssprinc.princname); |
| gsh_free(client); |
| } |
| } |
| |
| /** |
| * @brief Free resources attached to an export |
| * |
| * @param export [IN] pointer to export |
| * |
| * @return true if all went well |
| */ |
| |
| void free_export_resources(struct gsh_export *export) |
| { |
| FreeClientList(&export->clients); |
| if (export->fsal_export != NULL) { |
| struct fsal_module *fsal = export->fsal_export->fsal; |
| |
| export->fsal_export->exp_ops.release(export->fsal_export); |
| fsal_put(fsal); |
| } |
| export->fsal_export = NULL; |
| /* free strings here */ |
| if (export->fullpath != NULL) |
| gsh_free(export->fullpath); |
| if (export->pseudopath != NULL) |
| gsh_free(export->pseudopath); |
| if (export->FS_tag != NULL) |
| gsh_free(export->FS_tag); |
| } |
| |
| /** |
| * @brief pkginit callback to initialize exports from nfs_init |
| * |
| * Assumes being called with the export_by_id.lock held. |
| * true on success |
| */ |
| |
| static bool init_export_cb(struct gsh_export *exp, void *state) |
| { |
| return !(init_export_root(exp)); |
| } |
| |
| /** |
| * @brief Initialize exports over a live cache inode and fsal layer |
| */ |
| |
| void exports_pkginit(void) |
| { |
| foreach_gsh_export(init_export_cb, NULL); |
| } |
| |
| /** |
| * @brief Return a reference to the root cache inode entry of the export |
| * |
| * Must be called with the caller holding a reference to the export. |
| * |
| * Returns with an additional reference to the cache inode held for use by the |
| * caller. |
| * |
| * @param export [IN] the aforementioned export |
| * @param entry [IN/OUT] call by ref pointer to store cache entry |
| * |
| * @return cache inode status code |
| */ |
| |
| cache_inode_status_t nfs_export_get_root_entry(struct gsh_export *export, |
| cache_entry_t **entry) |
| { |
| cache_inode_status_t status; |
| |
| PTHREAD_RWLOCK_rdlock(&export->lock); |
| |
| status = |
| cache_inode_lru_ref(export->exp_root_cache_inode, LRU_FLAG_NONE); |
| |
| if (status == CACHE_INODE_SUCCESS) |
| *entry = export->exp_root_cache_inode; |
| |
| PTHREAD_RWLOCK_unlock(&export->lock); |
| |
| return status; |
| } |
| |
| /** |
| * @brief Initialize the root cache inode for an export. |
| * |
| * Assumes being called with the export_by_id.lock held. |
| * |
| * @param exp [IN] the export |
| * |
| * @return 0 if successful otherwise err. |
| */ |
| |
| int init_export_root(struct gsh_export *export) |
| { |
| fsal_status_t fsal_status; |
| cache_inode_status_t cache_status; |
| struct fsal_obj_handle *root_handle; |
| cache_entry_t *entry = NULL; |
| struct root_op_context root_op_context; |
| int my_status; |
| |
| /* Initialize req_ctx */ |
| init_root_op_context(&root_op_context, export, export->fsal_export, |
| 0, 0, UNKNOWN_REQUEST); |
| |
| /* Lookup for the FSAL Path */ |
| LogDebug(COMPONENT_EXPORT, |
| "About to lookup_path for ExportId=%u Path=%s", |
| export->export_id, export->fullpath); |
| fsal_status = |
| export->fsal_export->exp_ops.lookup_path(export->fsal_export, |
| export->fullpath, |
| &root_handle); |
| |
| if (FSAL_IS_ERROR(fsal_status)) { |
| my_status = EINVAL; |
| |
| LogCrit(COMPONENT_EXPORT, |
| "Lookup failed on path, ExportId=%u Path=%s FSAL_ERROR=(%s,%u)", |
| export->export_id, export->fullpath, |
| msg_fsal_err(fsal_status.major), fsal_status.minor); |
| goto out; |
| } |
| |
| /* Add this entry to the Cache Inode as a "root" entry */ |
| |
| /* Get the cache inode entry (and an LRU reference */ |
| cache_status = cache_inode_new_entry(root_handle, CACHE_INODE_FLAG_NONE, |
| &entry); |
| |
| if (entry == NULL) { |
| /* EFAULT for any internal error */ |
| my_status = EFAULT; |
| |
| LogCrit(COMPONENT_EXPORT, |
| "Error when creating root cached entry for %s, export_id=%d, cache_status=%s", |
| export->fullpath, |
| export->export_id, |
| cache_inode_err_str(cache_status)); |
| goto out; |
| } |
| |
| /* Instead of an LRU reference, we must hold a pin reference */ |
| cache_status = cache_inode_inc_pin_ref(entry); |
| |
| if (cache_status != CACHE_INODE_SUCCESS) { |
| |
| my_status = EFAULT; |
| |
| LogCrit(COMPONENT_EXPORT, |
| "Error when creating root cached entry for %s, export_id=%d, cache_status=%s", |
| export->fullpath, |
| export->export_id, |
| cache_inode_err_str(cache_status)); |
| |
| /* Release the LRU reference and return failure. */ |
| cache_inode_put(entry); |
| goto out; |
| } |
| |
| PTHREAD_RWLOCK_wrlock(&entry->attr_lock); |
| PTHREAD_RWLOCK_wrlock(&export->lock); |
| |
| export->exp_root_cache_inode = entry; |
| |
| glist_add_tail(&entry->object.dir.export_roots, |
| &export->exp_root_list); |
| |
| /* Protect this entry from removal (unlink) */ |
| atomic_inc_int32_t(&entry->exp_root_refcount); |
| |
| PTHREAD_RWLOCK_unlock(&export->lock); |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| |
| if (isDebug(COMPONENT_EXPORT)) { |
| LogDebug(COMPONENT_EXPORT, |
| "Added root entry %p FSAL %s for path %s on export_id=%d", |
| entry, |
| entry->obj_handle->fsal->name, |
| export->fullpath, export->export_id); |
| } else { |
| LogInfo(COMPONENT_EXPORT, |
| "Added root entry for path %s on export_id=%d", |
| export->fullpath, export->export_id); |
| } |
| |
| /* Release the LRU reference and return success. */ |
| cache_inode_put(entry); |
| my_status = 0; |
| out: |
| release_root_op_context(); |
| return my_status; |
| } |
| |
| /** |
| * @brief Release the root cache inode for an export. |
| * |
| * @param exp [IN] the export |
| */ |
| |
| static inline void |
| release_export_root_locked(struct gsh_export *export, cache_entry_t *entry) |
| { |
| cache_entry_t *root_entry = NULL; |
| |
| PTHREAD_RWLOCK_wrlock(&export->lock); |
| |
| glist_del(&export->exp_root_list); |
| root_entry = export->exp_root_cache_inode; |
| export->exp_root_cache_inode = NULL; |
| |
| if (root_entry != NULL) { |
| /* Allow this entry to be removed (unlink) */ |
| (void) atomic_dec_int32_t(&entry->exp_root_refcount); |
| |
| /* We must not hold entry->attr_lock across |
| * cache_inode_dec_pin_ref (LRU lane lock order) |
| */ |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| PTHREAD_RWLOCK_unlock(&export->lock); |
| |
| /* Release the pin reference */ |
| cache_inode_dec_pin_ref(root_entry, false); |
| } else { |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| PTHREAD_RWLOCK_unlock(&export->lock); |
| } |
| |
| LogDebug(COMPONENT_EXPORT, |
| "Released root entry %p for path %s on export_id=%d", |
| root_entry, export->fullpath, export->export_id); |
| } |
| |
| /** |
| * @brief Release the root cache inode for an export. |
| * |
| * @param exp [IN] the export |
| */ |
| |
| void release_export_root(struct gsh_export *export) |
| { |
| cache_entry_t *entry = NULL; |
| cache_inode_status_t status; |
| |
| /* Get a reference to the root entry */ |
| status = nfs_export_get_root_entry(export, &entry); |
| |
| if (status != CACHE_INODE_SUCCESS) { |
| /* No more root entry, bail out, this export is |
| * probably about to be destroyed. |
| */ |
| LogInfo(COMPONENT_CACHE_INODE, |
| "Export root for export id %d status %s", |
| export->export_id, cache_inode_err_str(status)); |
| return; |
| } |
| |
| PTHREAD_RWLOCK_wrlock(&entry->attr_lock); |
| |
| /* Make the export unreachable as a root cache inode */ |
| release_export_root_locked(export, entry); |
| |
| cache_inode_put(entry); |
| } |
| |
| static inline void clean_up_export(struct gsh_export *export) |
| { |
| /* Make export unreachable */ |
| pseudo_unmount_export(export); |
| remove_gsh_export(export->export_id); |
| |
| /* Release state belonging to this export */ |
| state_release_export(export); |
| |
| /* Flush cache inodes belonging to this export */ |
| cache_inode_unexport(export); |
| } |
| |
| void unexport(struct gsh_export *export) |
| { |
| /* Make the export unreachable */ |
| LogDebug(COMPONENT_EXPORT, |
| "Unexport %s, Pseduo %s", |
| export->fullpath, export->pseudopath); |
| release_export_root(export); |
| clean_up_export(export); |
| } |
| |
| /** |
| * @brief Handle killing a cache inode entry that might be an export root. |
| * |
| * @param entry [IN] the cache inode entry |
| */ |
| |
| void kill_export_root_entry(cache_entry_t *entry) |
| { |
| struct gsh_export *export; |
| |
| if (entry->type != DIRECTORY) |
| return; |
| |
| while (true) { |
| PTHREAD_RWLOCK_wrlock(&entry->attr_lock); |
| |
| export = glist_first_entry(&entry->object.dir.export_roots, |
| struct gsh_export, |
| exp_root_list); |
| |
| if (export == NULL) { |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| return; |
| } |
| |
| get_gsh_export_ref(export); |
| LogInfo(COMPONENT_CONFIG, |
| "Killing export_id %d because root entry went bad", |
| export->export_id); |
| |
| /* Make the export unreachable as a root cache inode */ |
| release_export_root_locked(export, entry); |
| |
| /* Make the export otherwise unreachable and clean it up */ |
| clean_up_export(export); |
| |
| put_gsh_export(export); |
| } |
| } |
| |
| /** |
| * @brief Handle killing a cache inode entry that is a junction to an export. |
| * |
| * @param entry [IN] the cache inode entry |
| */ |
| |
| void kill_export_junction_entry(cache_entry_t *entry) |
| { |
| struct gsh_export *export; |
| |
| if (entry->type != DIRECTORY) |
| return; |
| |
| PTHREAD_RWLOCK_wrlock(&entry->attr_lock); |
| |
| export = entry->object.dir.junction_export; |
| |
| if (export == NULL) { |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| return; |
| } |
| |
| /* Detach the export from the inode */ |
| entry->object.dir.junction_export = NULL; |
| |
| get_gsh_export_ref(export); |
| |
| LogInfo(COMPONENT_CONFIG, |
| "Unmounting export_id %d because junction entry went bad", |
| export->export_id); |
| |
| PTHREAD_RWLOCK_wrlock(&export->lock); |
| |
| /* Detach the export */ |
| export->exp_junction_inode = NULL; |
| |
| PTHREAD_RWLOCK_unlock(&export->lock); |
| PTHREAD_RWLOCK_unlock(&entry->attr_lock); |
| |
| /* Finish unmounting the export */ |
| pseudo_unmount_export(export); |
| |
| /* Don't remove the export (if export root is still valid, the |
| * export is still accessible via NFS v3. |
| */ |
| |
| put_gsh_export(export); |
| } |
| |
| static char *client_types[] = { |
| [PROTO_CLIENT] = "PROTO_CLIENT", |
| [HOSTIF_CLIENT] = "HOSTIF_CLIENT", |
| [NETWORK_CLIENT] = "NETWORK_CLIENT", |
| [NETGROUP_CLIENT] = "NETGROUP_CLIENT", |
| [WILDCARDHOST_CLIENT] = "WILDCARDHOST_CLIENT", |
| [GSSPRINCIPAL_CLIENT] = "GSSPRINCIPAL_CLIENT", |
| [HOSTIF_CLIENT_V6] = "HOSTIF_CLIENT_V6", |
| [MATCH_ANY_CLIENT] = "MATCH_ANY_CLIENT", |
| [BAD_CLIENT] = "BAD_CLIENT" |
| }; |
| |
| /** |
| * @brief Match a specific option in the client export list |
| * |
| * @param[in] hostaddr Host to search for |
| * @param[in] clients Client list to search |
| * @param[out] client_found Matching entry |
| * @param[in] export_option Option to search for |
| * |
| * @return true if found, false otherwise. |
| */ |
| static exportlist_client_entry_t *client_match(sockaddr_t *hostaddr, |
| struct gsh_export *export) |
| { |
| struct glist_head *glist; |
| in_addr_t addr = get_in_addr(hostaddr); |
| int rc; |
| int ipvalid = -1; /* -1 need to print, 0 - invalid, 1 - ok */ |
| char hostname[MAXHOSTNAMELEN + 1]; |
| char ipstring[SOCK_NAME_MAX + 1]; |
| |
| glist_for_each(glist, &export->clients) { |
| exportlist_client_entry_t *client; |
| |
| client = glist_entry(glist, exportlist_client_entry_t, |
| cle_list); |
| LogMidDebug(COMPONENT_EXPORT, |
| "Match %p, type = %s, options 0x%X", |
| client, |
| client_types[client->type], |
| client->client_perms.options); |
| LogClientListEntry(COMPONENT_EXPORT, client); |
| |
| switch (client->type) { |
| case HOSTIF_CLIENT: |
| if (client->client.hostif.clientaddr == addr) |
| return client; |
| break; |
| |
| case NETWORK_CLIENT: |
| if ((client->client.network.netmask & ntohl(addr)) == |
| client->client.network.netaddr) |
| return client; |
| break; |
| |
| case NETGROUP_CLIENT: |
| /* Try to get the entry from th IP/name cache */ |
| rc = nfs_ip_name_get(hostaddr, hostname, |
| sizeof(hostname)); |
| |
| if (rc == IP_NAME_NOT_FOUND) { |
| /* IPaddr was not cached, add it to the cache */ |
| rc = nfs_ip_name_add(hostaddr, |
| hostname, |
| sizeof(hostname)); |
| } |
| |
| if (rc != IP_NAME_SUCCESS) |
| break; /* Fatal failure */ |
| |
| /* At this point 'hostname' should contain the |
| * name that was found |
| */ |
| if (innetgr(client->client.netgroup.netgroupname, |
| hostname, NULL, NULL) == 1) { |
| return client; |
| } |
| break; |
| |
| case WILDCARDHOST_CLIENT: |
| /* Now checking for IP wildcards */ |
| if (ipvalid < 0) |
| ipvalid = sprint_sockip(hostaddr, |
| ipstring, |
| sizeof(ipstring)); |
| |
| if (ipvalid && |
| (fnmatch(client->client.wildcard.wildcard, |
| ipstring, |
| FNM_PATHNAME) == 0)) { |
| return client; |
| } |
| |
| /* Try to get the entry from th IP/name cache */ |
| rc = nfs_ip_name_get(hostaddr, hostname, |
| sizeof(hostname)); |
| |
| if (rc == IP_NAME_NOT_FOUND) { |
| /* IPaddr was not cached, add it to the cache */ |
| |
| /** @todo this change from 1.5 is not IPv6 |
| * useful. come back to this and use the |
| * string from client mgr inside req_ctx... |
| */ |
| rc = nfs_ip_name_add(hostaddr, |
| hostname, |
| sizeof(hostname)); |
| } |
| |
| if (rc != IP_NAME_SUCCESS) |
| break; |
| |
| /* At this point 'hostname' should contain the |
| * name that was found |
| */ |
| if (fnmatch |
| (client->client.wildcard.wildcard, hostname, |
| FNM_PATHNAME) == 0) { |
| return client; |
| } |
| break; |
| |
| case GSSPRINCIPAL_CLIENT: |
| /** @todo BUGAZOMEU a completer lors de l'integration de RPCSEC_GSS */ |
| LogCrit(COMPONENT_EXPORT, |
| "Unsupported type GSS_PRINCIPAL_CLIENT"); |
| break; |
| |
| case HOSTIF_CLIENT_V6: |
| break; |
| |
| case MATCH_ANY_CLIENT: |
| return client; |
| |
| case BAD_CLIENT: |
| default: |
| continue; |
| } |
| } |
| |
| /* no export found for this option */ |
| return NULL; |
| |
| } |
| |
| /** |
| * @brief Match a specific option in the client export list |
| * |
| * @param[in] paddrv6 Host to search for |
| * @param[in] clients Client list to search |
| * @param[out] client_found Matching entry |
| * @param[in] export_option Option to search for |
| * |
| * @return true if found, false otherwise. |
| */ |
| static exportlist_client_entry_t *client_matchv6(struct in6_addr *paddrv6, |
| struct gsh_export *export) |
| { |
| struct glist_head *glist; |
| |
| glist_for_each(glist, &export->clients) { |
| exportlist_client_entry_t *client; |
| |
| client = glist_entry(glist, exportlist_client_entry_t, |
| cle_list); |
| LogMidDebug(COMPONENT_EXPORT, |
| "Matchv6 %p, type = %s, options 0x%X", |
| client, |
| client_types[client->type], |
| client->client_perms.options); |
| LogClientListEntry(COMPONENT_EXPORT, client); |
| |
| switch (client->type) { |
| case HOSTIF_CLIENT: |
| case NETWORK_CLIENT: |
| case NETGROUP_CLIENT: |
| case WILDCARDHOST_CLIENT: |
| case GSSPRINCIPAL_CLIENT: |
| case BAD_CLIENT: |
| break; |
| |
| case HOSTIF_CLIENT_V6: |
| if (!memcmp(client->client.hostif.clientaddr6.s6_addr, |
| paddrv6->s6_addr, 16)) { |
| /* Remember that IPv6 address are |
| * 128 bits = 16 bytes long |
| */ |
| return client; |
| } |
| break; |
| |
| case MATCH_ANY_CLIENT: |
| return client; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* no export found for this option */ |
| return NULL; |
| } |
| |
| static exportlist_client_entry_t *client_match_any(sockaddr_t *hostaddr, |
| struct gsh_export *export) |
| { |
| if (hostaddr->ss_family == AF_INET6) { |
| struct sockaddr_in6 *psockaddr_in6 = |
| (struct sockaddr_in6 *)hostaddr; |
| return client_matchv6(&(psockaddr_in6->sin6_addr), export); |
| } else { |
| return client_match(hostaddr, export); |
| } |
| } |
| |
| /** |
| * @brief Checks if request security flavor is suffcient for the requested |
| * export |
| * |
| * @param[in] req Related RPC request. |
| * |
| * @return true if the request flavor exists in the matching export |
| * false otherwise |
| */ |
| bool export_check_security(struct svc_req *req) |
| { |
| switch (req->rq_cred.oa_flavor) { |
| case AUTH_NONE: |
| if ((op_ctx->export_perms->options & |
| EXPORT_OPTION_AUTH_NONE) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support AUTH_NONE", |
| op_ctx->export->fullpath); |
| return false; |
| } |
| break; |
| |
| case AUTH_UNIX: |
| if ((op_ctx->export_perms->options & |
| EXPORT_OPTION_AUTH_UNIX) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support AUTH_UNIX", |
| op_ctx->export->fullpath); |
| return false; |
| } |
| break; |
| |
| #ifdef _HAVE_GSSAPI |
| case RPCSEC_GSS: |
| if ((op_ctx->export_perms->options & |
| (EXPORT_OPTION_RPCSEC_GSS_NONE | |
| EXPORT_OPTION_RPCSEC_GSS_INTG | |
| EXPORT_OPTION_RPCSEC_GSS_PRIV)) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support RPCSEC_GSS", |
| op_ctx->export->fullpath); |
| return false; |
| } else { |
| struct svc_rpc_gss_data *gd; |
| rpc_gss_svc_t svc; |
| |
| gd = SVCAUTH_PRIVATE(req->rq_auth); |
| svc = gd->sec.svc; |
| LogFullDebug(COMPONENT_EXPORT, "Testing svc %d", |
| (int)svc); |
| switch (svc) { |
| case RPCSEC_GSS_SVC_NONE: |
| if ((op_ctx->export_perms->options & |
| EXPORT_OPTION_RPCSEC_GSS_NONE) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support RPCSEC_GSS_SVC_NONE", |
| op_ctx->export->fullpath); |
| return false; |
| } |
| break; |
| |
| case RPCSEC_GSS_SVC_INTEGRITY: |
| if ((op_ctx->export_perms->options & |
| EXPORT_OPTION_RPCSEC_GSS_INTG) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support RPCSEC_GSS_SVC_INTEGRITY", |
| op_ctx->export->fullpath); |
| return false; |
| } |
| break; |
| |
| case RPCSEC_GSS_SVC_PRIVACY: |
| if ((op_ctx->export_perms->options & |
| EXPORT_OPTION_RPCSEC_GSS_PRIV) == 0) { |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support RPCSEC_GSS_SVC_PRIVACY", |
| op_ctx->export->fullpath); |
| return false; |
| } |
| break; |
| |
| default: |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support unknown RPCSEC_GSS_SVC %d", |
| op_ctx->export->fullpath, |
| (int)svc); |
| return false; |
| } |
| } |
| break; |
| #endif |
| default: |
| LogInfo(COMPONENT_EXPORT, |
| "Export %s does not support unknown oa_flavor %d", |
| op_ctx->export->fullpath, |
| (int)req->rq_cred.oa_flavor); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static char ten_bytes_all_0[10]; |
| |
| sockaddr_t *convert_ipv6_to_ipv4(sockaddr_t *ipv6, sockaddr_t *ipv4) |
| { |
| struct sockaddr_in *paddr = (struct sockaddr_in *)ipv4; |
| struct sockaddr_in6 *psockaddr_in6 = (struct sockaddr_in6 *)ipv6; |
| |
| /* If the client socket is IPv4, then it is wrapped into a |
| * ::ffff:a.b.c.d IPv6 address. We check this here. |
| * This kind of adress is shaped like this: |
| * |---------------------------------------------------------------| |
| * | 80 bits = 10 bytes | 16 bits = 2 bytes | 32 bits = 4 bytes | |
| * |---------------------------------------------------------------| |
| * | 0 | FFFF | IPv4 address | |
| * |---------------------------------------------------------------| |
| */ |
| if ((ipv6->ss_family == AF_INET6) |
| && !memcmp(psockaddr_in6->sin6_addr.s6_addr, ten_bytes_all_0, 10) |
| && (psockaddr_in6->sin6_addr.s6_addr[10] == 0xFF) |
| && (psockaddr_in6->sin6_addr.s6_addr[11] == 0xFF)) { |
| void *ab; |
| |
| memset(ipv4, 0, sizeof(*ipv4)); |
| ab = &(psockaddr_in6->sin6_addr.s6_addr[12]); |
| |
| paddr->sin_port = psockaddr_in6->sin6_port; |
| paddr->sin_addr.s_addr = *(in_addr_t *) ab; |
| ipv4->ss_family = AF_INET; |
| |
| if (isFullDebug(COMPONENT_EXPORT)) { |
| char ipstring4[SOCK_NAME_MAX]; |
| char ipstring6[SOCK_NAME_MAX]; |
| |
| sprint_sockip(ipv6, ipstring6, sizeof(ipstring6)); |
| sprint_sockip(ipv4, ipstring4, sizeof(ipstring4)); |
| LogMidDebug(COMPONENT_EXPORT, |
| "Converting IPv6 encapsulated IPv4 address %s to IPv4 %s", |
| ipstring6, ipstring4); |
| } |
| |
| return ipv4; |
| } else { |
| return ipv6; |
| } |
| } |
| |
| /** |
| * @brief Checks if a machine is authorized to access an export entry |
| * |
| * Permissions in the op context get updated based on export and client |
| */ |
| |
| void export_check_access(void) |
| { |
| exportlist_client_entry_t *client; |
| sockaddr_t alt_hostaddr; |
| sockaddr_t *hostaddr; |
| |
| /* Initialize permissions to allow nothing */ |
| op_ctx->export_perms->options = 0; |
| op_ctx->export_perms->set = 0; |
| op_ctx->export_perms->anonymous_uid = (uid_t) ANON_UID; |
| op_ctx->export_perms->anonymous_gid = (gid_t) ANON_GID; |
| |
| assert(op_ctx != NULL && op_ctx->export != NULL); |
| |
| hostaddr = convert_ipv6_to_ipv4(op_ctx->caller_addr, &alt_hostaddr); |
| |
| if (isMidDebug(COMPONENT_EXPORT)) { |
| char ipstring[SOCK_NAME_MAX]; |
| |
| ipstring[0] = '\0'; |
| (void) sprint_sockip(hostaddr, |
| ipstring, sizeof(ipstring)); |
| LogMidDebug(COMPONENT_EXPORT, |
| "Check for address %s for export id %u fullpath %s", |
| ipstring, op_ctx->export->export_id, |
| op_ctx->export->fullpath); |
| } |
| |
| /* Does the client match anyone on the client list? */ |
| client = client_match_any(hostaddr, op_ctx->export); |
| if (client != NULL) { |
| /* Take client options */ |
| op_ctx->export_perms->options = client->client_perms.options & |
| client->client_perms.set; |
| |
| if (client->client_perms.set & EXPORT_OPTION_ANON_UID_SET) |
| op_ctx->export_perms->anonymous_uid = |
| client->client_perms.anonymous_uid; |
| |
| if (client->client_perms.set & EXPORT_OPTION_ANON_GID_SET) |
| op_ctx->export_perms->anonymous_gid = |
| client->client_perms.anonymous_gid; |
| |
| op_ctx->export_perms->set = client->client_perms.set; |
| } |
| |
| /* Any options not set by the client, take from the export */ |
| op_ctx->export_perms->options |= |
| op_ctx->export->export_perms.options & |
| op_ctx->export->export_perms.set & |
| ~op_ctx->export_perms->set; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0 && |
| (op_ctx->export->export_perms.set & |
| EXPORT_OPTION_ANON_UID_SET) != 0) |
| op_ctx->export_perms->anonymous_uid = |
| op_ctx->export->export_perms.anonymous_uid; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0 && |
| (op_ctx->export->export_perms.set & |
| EXPORT_OPTION_ANON_GID_SET) != 0) |
| op_ctx->export_perms->anonymous_gid = |
| op_ctx->export->export_perms.anonymous_gid; |
| |
| op_ctx->export_perms->set |= op_ctx->export->export_perms.set; |
| |
| /* Any options not set by the client or export, take from the |
| * EXPORT_DEFAULTS block. |
| */ |
| op_ctx->export_perms->options |= export_opt.conf.options & |
| export_opt.conf.set & |
| ~op_ctx->export_perms->set; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0 && |
| (export_opt.conf.set & EXPORT_OPTION_ANON_UID_SET) != 0) |
| op_ctx->export_perms->anonymous_uid = |
| export_opt.conf.anonymous_uid; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0 && |
| (export_opt.conf.set & EXPORT_OPTION_ANON_GID_SET) != 0) |
| op_ctx->export_perms->anonymous_gid = |
| export_opt.conf.anonymous_gid; |
| |
| op_ctx->export_perms->set |= export_opt.conf.set; |
| |
| /* And finally take any options not yet set from global defaults */ |
| op_ctx->export_perms->options |= export_opt.def.options & |
| ~op_ctx->export_perms->set; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_UID_SET) == 0) |
| op_ctx->export_perms->anonymous_uid = |
| export_opt.def.anonymous_uid; |
| |
| if ((op_ctx->export_perms->set & EXPORT_OPTION_ANON_GID_SET) == 0) |
| op_ctx->export_perms->anonymous_gid = |
| export_opt.def.anonymous_gid; |
| |
| op_ctx->export_perms->set |= export_opt.def.set; |
| |
| if (isMidDebug(COMPONENT_EXPORT)) { |
| char perms[1024]; |
| |
| if (client != NULL) { |
| StrExportOptions(&client->client_perms, perms); |
| LogMidDebug(COMPONENT_EXPORT, |
| "CLIENT (%s)", |
| perms); |
| } |
| StrExportOptions(&op_ctx->export->export_perms, perms); |
| LogMidDebug(COMPONENT_EXPORT, |
| "EXPORT (%s)", |
| perms); |
| StrExportOptions(&export_opt.conf, perms); |
| LogMidDebug(COMPONENT_EXPORT, |
| "EXPORT_DEFAULTS (%s)", |
| perms); |
| StrExportOptions(&export_opt.def, perms); |
| LogMidDebug(COMPONENT_EXPORT, |
| "default options (%s)", |
| perms); |
| StrExportOptions(op_ctx->export_perms, perms); |
| LogMidDebug(COMPONENT_EXPORT, |
| "Final options (%s)", |
| perms); |
| } |
| } /* nfs_export_check_access */ |