diff options
Diffstat (limited to 'source3/rpc_server/fss')
-rw-r--r-- | source3/rpc_server/fss/srv_fss_agent.c | 1776 | ||||
-rw-r--r-- | source3/rpc_server/fss/srv_fss_private.h | 92 | ||||
-rw-r--r-- | source3/rpc_server/fss/srv_fss_state.c | 698 |
3 files changed, 2566 insertions, 0 deletions
diff --git a/source3/rpc_server/fss/srv_fss_agent.c b/source3/rpc_server/fss/srv_fss_agent.c new file mode 100644 index 0000000..4de600f --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_agent.c @@ -0,0 +1,1776 @@ +/* + * File Server Remote VSS Protocol (FSRVP) server + * + * Copyright (C) David Disseldorp 2012-2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" +#include "ntdomain.h" +#include "include/messages.h" +#include "serverid.h" +#include "include/auth.h" +#include "../libcli/security/security.h" +#include "../libcli/util/hresult.h" +#include "../lib/smbconf/smbconf.h" +#include "smbd/proto.h" +#include "lib/smbconf/smbconf_init.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_fsrvp_scompat.h" +#include "librpc/gen_ndr/ndr_fsrvp.h" +#include "rpc_server/rpc_server.h" +#include "srv_fss_private.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static struct fss_global fss_global; + +/* errmap NTSTATUS->fsrvp */ +static const struct { + NTSTATUS status; + uint32_t fsrvp_err; +} ntstatus_to_fsrvp_map[] = { + {NT_STATUS_INVALID_SERVER_STATE, FSRVP_E_BAD_STATE}, + {NT_STATUS_INVALID_DISPOSITION, FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS}, + {NT_STATUS_NOT_SUPPORTED, FSRVP_E_NOT_SUPPORTED}, + {NT_STATUS_IO_TIMEOUT, FSRVP_E_WAIT_TIMEOUT}, + {NT_STATUS_CANT_WAIT, FSRVP_E_WAIT_FAILED}, + {NT_STATUS_OBJECTID_EXISTS, FSRVP_E_OBJECT_ALREADY_EXISTS}, + {NT_STATUS_OBJECTID_NOT_FOUND, FSRVP_E_OBJECT_NOT_FOUND}, + {NT_STATUS_OBJECT_NAME_INVALID, FSRVP_E_BAD_ID}, +}; + +/* errmap NTSTATUS->hresult */ +static const struct { + NTSTATUS status; + HRESULT hres; +} ntstatus_to_hres_map[] = { + {NT_STATUS_ACCESS_DENIED, HRES_E_ACCESSDENIED}, + {NT_STATUS_INVALID_PARAMETER, HRES_E_INVALIDARG}, + {NT_STATUS_NO_MEMORY, HRES_E_OUTOFMEMORY}, +}; + +static uint32_t fss_ntstatus_map(NTSTATUS status) +{ + size_t i; + + if (NT_STATUS_IS_OK(status)) + return 0; + + /* check fsrvp specific errors first */ + for (i = 0; i < ARRAY_SIZE(ntstatus_to_fsrvp_map); i++) { + if (NT_STATUS_EQUAL(status, ntstatus_to_fsrvp_map[i].status)) { + return ntstatus_to_fsrvp_map[i].fsrvp_err; + } + } + /* fall-back to generic hresult values */ + for (i = 0; i < ARRAY_SIZE(ntstatus_to_hres_map); i++) { + if (NT_STATUS_EQUAL(status, ntstatus_to_hres_map[i].status)) { + return HRES_ERROR_V(ntstatus_to_hres_map[i].hres); + } + } + + return HRES_ERROR_V(HRES_E_FAIL); +} + +static NTSTATUS fss_unc_parse(TALLOC_CTX *mem_ctx, + const char *unc, + char **_server, + char **_share) +{ + char *s; + char *server; + char *share; + + if (unc == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + s = strstr_m(unc, "\\\\"); + if (s == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + server = talloc_strdup(mem_ctx, s + 2); + if (server == NULL) { + return NT_STATUS_NO_MEMORY; + } + s = strchr_m(server, '\\'); + if ((s == NULL) || (s == server)) { + return NT_STATUS_INVALID_PARAMETER; + } + *s = '\0'; + share = s + 1; + + s = strchr_m(share, '\\'); + if (s != NULL) { + /* diskshadow.exe adds a trailing '\' to the share-name */ + *s = '\0'; + } + if (strlen(share) == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (_server != NULL) { + *_server = server; + } + if (_share != NULL) { + *_share = share; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fss_conn_create_tos(struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + int snum, + struct connection_struct **conn_out); + +/* test if system path exists */ +static bool snap_path_exists(TALLOC_CTX *ctx, struct messaging_context *msg_ctx, + struct fss_sc *sc) +{ + TALLOC_CTX *frame = talloc_stackframe(); + SMB_STRUCT_STAT st; + struct connection_struct *conn = NULL; + struct smb_filename *smb_fname = NULL; + char *service = NULL; + char *share; + int snum; + int ret; + NTSTATUS status; + bool result = false; + + ZERO_STRUCT(st); + + if ((sc->smaps_count == 0) || (sc->sc_path == NULL)) { + goto out; + } + + share = sc->smaps->share_name; + snum = find_service(frame, share, &service); + + if ((snum == -1) || (service == NULL)) { + goto out; + } + + status = fss_conn_create_tos(msg_ctx, NULL, snum, &conn); + if(!NT_STATUS_IS_OK(status)) { + goto out; + } + + smb_fname = synthetic_smb_fname(service, + sc->sc_path, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + goto out; + } + + ret = SMB_VFS_STAT(conn, smb_fname); + if ((ret == -1) && (errno == ENOENT)) { + goto out; + } + result = true; +out: + TALLOC_FREE(frame); + return result; +} + +static NTSTATUS sc_smap_unexpose(struct messaging_context *msg_ctx, + struct fss_sc_smap *sc_smap, bool delete_all); + +static NTSTATUS fss_prune_stale(struct messaging_context *msg_ctx, + const char *db_path) +{ + struct fss_sc_set *sc_sets; + uint32_t sc_sets_count = 0; + struct fss_sc_set *sc_set; + struct fss_sc_smap *prunable_sc_smaps = NULL; + bool is_modified = false; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *ctx = talloc_new(NULL); + + if (!ctx) { + return NT_STATUS_NO_MEMORY; + } + + /* work with temporary state for simple cleanup on failure */ + become_root(); + status = fss_state_retrieve(ctx, &sc_sets, &sc_sets_count, db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to retrieve fss server state: %s\n", + nt_errstr(status))); + goto out; + } + + /* walk the cache and pick up any entries to be deleted */ + sc_set = sc_sets; + DEBUG(10, ("pruning shared shadow copies\n")); + while (sc_set) { + struct fss_sc *sc; + struct fss_sc_set *sc_set_next = sc_set->next; + char *set_id = GUID_string(ctx, &sc_set->id); + if (set_id == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + DEBUGADD(10, ("\tprocessing shadow set id %s\n", set_id)); + sc = sc_set->scs; + while (sc) { + struct fss_sc_smap *sc_smap; + struct fss_sc *sc_next = sc->next; + DEBUGADD(10, ("\tprocessing shadow copy path %s\n", + sc->sc_path)); + if (snap_path_exists(ctx, msg_ctx, sc)) { + sc = sc_next; + continue; + } + + /* move missing snapshot state to purge list */ + sc_smap = sc->smaps; + while (sc_smap != NULL) { + struct fss_sc_smap *smap_next = sc_smap->next; + DLIST_REMOVE(sc->smaps, sc_smap); + DLIST_ADD_END(prunable_sc_smaps, sc_smap); + sc->smaps_count--; + sc_smap = smap_next; + } + + DLIST_REMOVE(sc_set->scs, sc); + sc_set->scs_count--; + is_modified = true; + sc = sc_next; + } + if (sc_set->scs_count == 0) { + DLIST_REMOVE(sc_sets, sc_set); + sc_sets_count--; + } + sc_set = sc_set_next; + } + + if (is_modified) { + /* unexpose all shares in a single transaction */ + status = sc_smap_unexpose(msg_ctx, prunable_sc_smaps, true); + if (!NT_STATUS_IS_OK(status)) { + /* exit without storing updated state */ + goto out; + } + + become_root(); + status = fss_state_store(ctx, sc_sets, sc_sets_count, db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("pruning failed to store fss server state: %s\n", + nt_errstr(status))); + goto out; + } + } + status = NT_STATUS_OK; +out: + TALLOC_FREE(ctx); + return status; +} + +static NTSTATUS fss_conn_create_tos(struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + int snum, + struct connection_struct **conn_out) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct conn_struct_tos *c = NULL; + NTSTATUS status; + + status = create_conn_struct_tos(msg_ctx, + snum, + lp_path(talloc_tos(), lp_sub, snum), + session_info, + &c); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("failed to create conn for vfs: %s\n", + nt_errstr(status))); + return status; + } + + status = set_conn_force_user_group(c->conn, snum); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed set force user / group\n")); + TALLOC_FREE(c); + return status; + } + + *conn_out = c->conn; + return NT_STATUS_OK; +} + +static struct fss_sc_set *sc_set_lookup(struct fss_sc_set *sc_set_head, + struct GUID *sc_set_id) +{ + + struct fss_sc_set *sc_set; + char *guid_str; + + for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) { + if (GUID_equal(&sc_set->id, sc_set_id)) { + return sc_set; + } + } + guid_str = GUID_string(sc_set_head, sc_set_id); + DEBUG(4, ("shadow copy set with GUID %s not found\n", + guid_str ? guid_str : "NO MEM")); + talloc_free(guid_str); + + return NULL; +} + +static struct fss_sc *sc_lookup(struct fss_sc *sc_head, struct GUID *sc_id) +{ + + struct fss_sc *sc; + char *guid_str; + + for (sc = sc_head; sc; sc = sc->next) { + if (GUID_equal(&sc->id, sc_id)) { + return sc; + } + } + guid_str = GUID_string(sc_head, sc_id); + DEBUG(4, ("shadow copy with GUID %s not found\n", + guid_str ? guid_str : "NO MEM")); + talloc_free(guid_str); + + return NULL; +} + +static struct fss_sc *sc_lookup_volname(struct fss_sc *sc_head, + const char *volname) +{ + struct fss_sc *sc; + + for (sc = sc_head; sc; sc = sc->next) { + if (!strcmp(sc->volume_name, volname)) { + return sc; + } + } + DEBUG(4, ("shadow copy with base volume %s not found\n", volname)); + return NULL; +} + +/* lookup is case-insensitive */ +static struct fss_sc_smap *sc_smap_lookup(struct fss_sc_smap *smaps_head, + const char *share) +{ + struct fss_sc_smap *sc_smap; + for (sc_smap = smaps_head; sc_smap; sc_smap = sc_smap->next) { + if (!strcasecmp_m(sc_smap->share_name, share)) { + return sc_smap; + } + } + DEBUG(4, ("shadow copy share mapping for %s not found\n", share)); + return NULL; +} + +static void srv_fssa_cleanup(void) +{ + talloc_free(fss_global.db_path); + talloc_free(fss_global.mem_ctx); + ZERO_STRUCT(fss_global); +} + +static NTSTATUS srv_fssa_start(struct messaging_context *msg_ctx) +{ + NTSTATUS status; + fss_global.mem_ctx = talloc_named_const(NULL, 0, + "parent fss rpc server ctx"); + if (fss_global.mem_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + fss_global.db_path = lock_path(talloc_tos(), FSS_DB_NAME); + if (fss_global.db_path == NULL) { + talloc_free(fss_global.mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + fss_global.min_vers = FSRVP_RPC_VERSION_1; + fss_global.max_vers = FSRVP_RPC_VERSION_1; + /* + * The server MUST populate the GlobalShadowCopySetTable with the + * ShadowCopySet entries read from the configuration store. + */ + if (lp_parm_bool(GLOBAL_SECTION_SNUM, "fss", "prune stale", false)) { + fss_prune_stale(msg_ctx, fss_global.db_path); + } + become_root(); + status = fss_state_retrieve(fss_global.mem_ctx, &fss_global.sc_sets, + &fss_global.sc_sets_count, + fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to retrieve fss server state: %s\n", + nt_errstr(status))); + } + return NT_STATUS_OK; +} + +/* + * Determine whether to process an FSRVP operation from connected user @p. + * Windows checks for Administrators or Backup Operators group membership. We + * also allow for the SEC_PRIV_BACKUP privilege. + */ +static bool fss_permitted(struct pipes_struct *p) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + + if (session_info->unix_token->uid == sec_initial_uid()) { + DEBUG(6, ("Granting FSRVP op, user started smbd\n")); + return true; + } + + if (nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + DEBUG(6, ("Granting FSRVP op, administrators group member\n")); + return true; + } + if (nt_token_check_sid(&global_sid_Builtin_Backup_Operators, + session_info->security_token)) { + DEBUG(6, ("Granting FSRVP op, backup operators group member\n")); + return true; + } + if (security_token_has_privilege(session_info->security_token, + SEC_PRIV_BACKUP)) { + DEBUG(6, ("Granting FSRVP op, backup privilege present\n")); + return true; + } + + DEBUG(2, ("FSRVP operation blocked due to lack of backup privilege " + "or Administrators/Backup Operators group membership\n")); + + return false; +} + +static void fss_seq_tout_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *private_data) +{ + struct GUID *sc_set_id = NULL; + struct fss_sc_set *sc_set; + + /* + * MS-FSRVP: 3.1.5 Timer Events + * Message Sequence Timer elapses: When the Message Sequence Timer + * elapses, the server MUST delete the ShadowCopySet in the + * GlobalShadowCopySetTable where ShadowCopySet.Status is not equal to + * "Recovered", ContextSet MUST be set to FALSE, and the ShadowCopySet + * object MUST be freed. + */ + DEBUG(2, ("FSRVP msg seq timeout fired\n")); + + if (private_data == NULL) { + DEBUG(4, ("timeout without sc_set\n")); + goto out_init_ctx; + } + + sc_set_id = talloc_get_type_abort(private_data, struct GUID); + sc_set = sc_set_lookup(fss_global.sc_sets, sc_set_id); + if (sc_set == NULL) { + DEBUG(0, ("timeout for unknown sc_set\n")); + goto out_init_ctx; + } else if ((sc_set->state == FSS_SC_EXPOSED) + || (sc_set->state == FSS_SC_RECOVERED)) { + DEBUG(2, ("timeout for finished sc_set %s\n", sc_set->id_str)); + goto out_init_ctx; + } + DEBUG(2, ("cleaning up sc_set %s\n", sc_set->id_str)); + SMB_ASSERT(fss_global.sc_sets_count > 0); + DLIST_REMOVE(fss_global.sc_sets, sc_set); + fss_global.sc_sets_count--; + talloc_free(sc_set); + +out_init_ctx: + fss_global.ctx_set = false; + fss_global.seq_tmr = NULL; + talloc_free(sc_set_id); +} + +static void fss_seq_tout_set(TALLOC_CTX *mem_ctx, + uint32_t timeout_s, + struct fss_sc_set *sc_set, + struct tevent_timer **tmr_out) +{ + struct tevent_timer *tmr; + struct GUID *sc_set_id = NULL; + uint32_t tout; + + /* allow changes to timeout for testing/debugging purposes */ + tout = lp_parm_int(GLOBAL_SECTION_SNUM, "fss", + "sequence timeout", timeout_s); + if (tout == 0) { + DEBUG(2, ("FSRVP message sequence timeout disabled\n")); + *tmr_out = NULL; + return; + } + + if (sc_set) { + /* don't use talloc_memdup(), need explicit type for callback */ + sc_set_id = talloc(mem_ctx, struct GUID); + if (sc_set_id == NULL) { + smb_panic("no memory"); + } + memcpy(sc_set_id, &sc_set->id, sizeof(*sc_set_id)); + } + + tmr = tevent_add_timer(global_event_context(), + mem_ctx, + timeval_current_ofs(tout, 0), + fss_seq_tout_handler, sc_set_id); + if (tmr == NULL) { + talloc_free(sc_set_id); + smb_panic("no memory"); + } + + *tmr_out = tmr; +} + +uint32_t _fss_GetSupportedVersion(struct pipes_struct *p, + struct fss_GetSupportedVersion *r) +{ + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + *r->out.MinVersion = fss_global.min_vers; + *r->out.MaxVersion = fss_global.max_vers; + + return 0; +} + +uint32_t _fss_SetContext(struct pipes_struct *p, + struct fss_SetContext *r) +{ + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + /* ATTR_AUTO_RECOVERY flag can be applied to any */ + switch (r->in.Context & (~ATTR_AUTO_RECOVERY)) { + case FSRVP_CTX_BACKUP: + DEBUG(6, ("fss ctx set backup\n")); + break; + case FSRVP_CTX_FILE_SHARE_BACKUP: + DEBUG(6, ("fss ctx set file share backup\n")); + break; + case FSRVP_CTX_NAS_ROLLBACK: + DEBUG(6, ("fss ctx set nas rollback\n")); + break; + case FSRVP_CTX_APP_ROLLBACK: + DEBUG(6, ("fss ctx set app rollback\n")); + break; + default: + DEBUG(0, ("invalid fss ctx set value: 0x%x\n", r->in.Context)); + return HRES_ERROR_V(HRES_E_INVALIDARG); + break; /* not reached */ + } + + fss_global.ctx_set = true; + fss_global.cur_ctx = r->in.Context; + + TALLOC_FREE(fss_global.seq_tmr); /* kill timer if running */ + fss_seq_tout_set(fss_global.mem_ctx, 180, NULL, &fss_global.seq_tmr); + + fss_global.cur_ctx = r->in.Context; + + return 0; +} + +static bool sc_set_active(struct fss_sc_set *sc_set_head) +{ + + struct fss_sc_set *sc_set; + + for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) { + if ((sc_set->state != FSS_SC_EXPOSED) + && (sc_set->state != FSS_SC_RECOVERED)) { + return true; + } + } + + return false; +} + +uint32_t _fss_StartShadowCopySet(struct pipes_struct *p, + struct fss_StartShadowCopySet *r) +{ + struct fss_sc_set *sc_set; + uint32_t ret; + + if (!fss_permitted(p)) { + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_out; + } + + if (!fss_global.ctx_set) { + DEBUG(3, ("invalid sequence: start sc set requested without " + "prior context set\n")); + ret = FSRVP_E_BAD_STATE; + goto err_out; + } + + /* + * At any given time, Windows servers allow only one shadow copy set to + * be going through the creation process. + */ + if (sc_set_active(fss_global.sc_sets)) { + DEBUG(3, ("StartShadowCopySet called while in progress\n")); + ret = FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS; + goto err_out; + } + + /* stop msg seq timer */ + TALLOC_FREE(fss_global.seq_tmr); + + sc_set = talloc_zero(fss_global.mem_ctx, struct fss_sc_set); + if (sc_set == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmr_restart; + } + + sc_set->id = GUID_random(); /* Windows servers ignore client ids */ + sc_set->id_str = GUID_string(sc_set, &sc_set->id); + if (sc_set->id_str == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_sc_set_free; + } + sc_set->state = FSS_SC_STARTED; + sc_set->context = fss_global.cur_ctx; + DLIST_ADD_END(fss_global.sc_sets, sc_set); + fss_global.sc_sets_count++; + DEBUG(6, ("%s: shadow-copy set %u added\n", + sc_set->id_str, fss_global.sc_sets_count)); + + /* start msg seq timer */ + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr); + + r->out.pShadowCopySetId = &sc_set->id; + + return 0; + +err_sc_set_free: + talloc_free(sc_set); +err_tmr_restart: + fss_seq_tout_set(fss_global.mem_ctx, 180, NULL, &fss_global.seq_tmr); +err_out: + return ret; +} + +static uint32_t map_share_name(struct fss_sc_smap *sc_smap, + const struct fss_sc *sc) +{ + bool hidden_base = false; + + if (*(sc_smap->share_name + strlen(sc_smap->share_name) - 1) == '$') { + /* + * If MappedShare.ShareName ends with a $ character (meaning + * that the share is hidden), then the exposed share name will + * have the $ suffix appended. + * FIXME: turns out Windows doesn't do this, contrary to docs + */ + hidden_base = true; + } + + sc_smap->sc_share_name = talloc_asprintf(sc_smap, "%s@{%s}%s", + sc_smap->share_name, + sc->id_str, + hidden_base ? "$" : ""); + if (sc_smap->sc_share_name == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + return 0; +} + +static uint32_t map_share_comment(struct fss_sc_smap *sc_smap, + const struct fss_sc *sc) +{ + char *time_str; + + time_str = http_timestring(sc_smap, sc->create_ts); + if (time_str == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + sc_smap->sc_share_comment = talloc_asprintf(sc_smap, "Shadow copy of %s taken %s", + sc_smap->share_name, time_str); + if (sc_smap->sc_share_comment == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + return 0; +} + +uint32_t _fss_AddToShadowCopySet(struct pipes_struct *p, + struct fss_AddToShadowCopySet *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + uint32_t ret; + struct fss_sc_set *sc_set; + struct fss_sc *sc; + struct fss_sc_smap *sc_smap; + int snum; + char *service; + char *base_vol; + char *share; + char *path_name; + struct connection_struct *conn; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (!fss_permitted(p)) { + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_tmp_free; + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + ret = HRES_ERROR_V(HRES_E_INVALIDARG); + goto err_tmp_free; + } + + status = fss_unc_parse(frame, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + ret = fss_ntstatus_map(status); + goto err_tmp_free; + } + + snum = find_service(frame, share, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", r->in.ShareName)); + ret = HRES_ERROR_V(HRES_E_INVALIDARG); + goto err_tmp_free; + } + + path_name = lp_path(frame, lp_sub, snum); + if (path_name == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmp_free; + } + + status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_tmp_free; + } + if (!become_user_without_service_by_session(conn, session_info)) { + DEBUG(0, ("failed to become user\n")); + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_tmp_free; + } + + status = SMB_VFS_SNAP_CHECK_PATH(conn, frame, path_name, &base_vol); + unbecome_user_without_service(); + if (!NT_STATUS_IS_OK(status)) { + ret = FSRVP_E_NOT_SUPPORTED; + goto err_tmp_free; + } + + if ((sc_set->state != FSS_SC_STARTED) + && (sc_set->state != FSS_SC_ADDED)) { + ret = FSRVP_E_BAD_STATE; + goto err_tmp_free; + } + + /* stop msg seq timer */ + TALLOC_FREE(fss_global.seq_tmr); + + /* + * server MUST look up the ShadowCopy in ShadowCopySet.ShadowCopyList + * where ShadowCopy.VolumeName matches the file store on which the + * share identified by ShareName is hosted. If an entry is found, the + * server MUST fail the call with FSRVP_E_OBJECT_ALREADY_EXISTS. + * If no entry is found, the server MUST create a new ShadowCopy + * object + * XXX Windows appears to allow multiple mappings for the same vol! + */ + sc = sc_lookup_volname(sc_set->scs, base_vol); + if (sc != NULL) { + ret = FSRVP_E_OBJECT_ALREADY_EXISTS; + goto err_tmr_restart; + } + + sc = talloc_zero(sc_set, struct fss_sc); + if (sc == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmr_restart; + } + talloc_steal(sc, base_vol); + sc->volume_name = base_vol; + sc->sc_set = sc_set; + sc->create_ts = time(NULL); + + sc->id = GUID_random(); /* Windows servers ignore client ids */ + sc->id_str = GUID_string(sc, &sc->id); + if (sc->id_str == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_sc_free; + } + + sc_smap = talloc_zero(sc, struct fss_sc_smap); + if (sc_smap == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_sc_free; + } + + talloc_steal(sc_smap, service); + sc_smap->share_name = service; + sc_smap->is_exposed = false; + /* + * generate the sc_smap share name now. It is a unique identifier for + * the smap used as a tdb key for state storage. + */ + ret = map_share_name(sc_smap, sc); + if (ret) { + goto err_sc_free; + } + + /* add share map to shadow-copy */ + DLIST_ADD_END(sc->smaps, sc_smap); + sc->smaps_count++; + /* add shadow-copy to shadow-copy set */ + DLIST_ADD_END(sc_set->scs, sc); + sc_set->scs_count++; + DEBUG(4, ("added volume %s to shadow copy set with GUID %s\n", + sc->volume_name, sc_set->id_str)); + + /* start the Message Sequence Timer with timeout of 1800 seconds */ + fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr); + + sc_set->state = FSS_SC_ADDED; + r->out.pShadowCopyId = &sc->id; + + TALLOC_FREE(frame); + return 0; + +err_sc_free: + talloc_free(sc); +err_tmr_restart: + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr); +err_tmp_free: + TALLOC_FREE(frame); + return ret; +} + +static NTSTATUS commit_sc_with_conn(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + struct fss_sc *sc, + char **base_path, + char **snap_path) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + bool rw; + struct connection_struct *conn; + int snum; + char *service; + + snum = find_service(frame, sc->smaps->share_name, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", sc->smaps->share_name)); + TALLOC_FREE(frame); + return NT_STATUS_UNSUCCESSFUL; + } + + status = fss_conn_create_tos(msg_ctx, session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + if (!become_user_without_service_by_session(conn, session_info)) { + DEBUG(0, ("failed to become user\n")); + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + rw = ((sc->sc_set->context & ATTR_AUTO_RECOVERY) == ATTR_AUTO_RECOVERY); + status = SMB_VFS_SNAP_CREATE(conn, mem_ctx, + sc->volume_name, + &sc->create_ts, rw, + base_path, snap_path); + unbecome_user_without_service(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("snap create failed: %s\n", nt_errstr(status))); + TALLOC_FREE(frame); + return status; + } + + TALLOC_FREE(frame); + return status; +} + +uint32_t _fss_CommitShadowCopySet(struct pipes_struct *p, + struct fss_CommitShadowCopySet *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct fss_sc_set *sc_set; + struct fss_sc *sc; + uint32_t commit_count; + NTSTATUS status; + NTSTATUS saved_status; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!fss_permitted(p)) { + status = NT_STATUS_ACCESS_DENIED; + goto err_tmp_free; + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_tmp_free; + } + + if (sc_set->state != FSS_SC_ADDED) { + status = NT_STATUS_INVALID_SERVER_STATE; + goto err_tmp_free; + } + + /* stop Message Sequence Timer */ + TALLOC_FREE(fss_global.seq_tmr); + sc_set->state = FSS_SC_CREATING; + commit_count = 0; + saved_status = NT_STATUS_OK; + for (sc = sc_set->scs; sc; sc = sc->next) { + char *base_path; + char *snap_path; + status = commit_sc_with_conn(frame, global_event_context(), + p->msg_ctx, session_info, sc, + &base_path, &snap_path); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("snap create failed for shadow copy of " + "%s\n", sc->volume_name)); + /* dispatch all scs in set, but retain last error */ + saved_status = status; + continue; + } + /* XXX set timeout r->in.TimeOutInMilliseconds */ + commit_count++; + DEBUG(10, ("good snap create %d\n", + commit_count)); + sc->sc_path = talloc_steal(sc, snap_path); + } + if (!NT_STATUS_IS_OK(saved_status)) { + status = saved_status; + goto err_state_revert; + } + + sc_set->state = FSS_SC_COMMITED; + become_root(); + status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets, + fss_global.sc_sets_count, + fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to store fss server state: %s\n", + nt_errstr(status))); + } + + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, + &fss_global.seq_tmr); + TALLOC_FREE(frame); + return 0; + +err_state_revert: + sc_set->state = FSS_SC_ADDED; + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, + &fss_global.seq_tmr); +err_tmp_free: + TALLOC_FREE(frame); + return fss_ntstatus_map(status); +} + +static sbcErr fss_conf_get_share_def(struct smbconf_ctx *fconf_ctx, + struct smbconf_ctx *rconf_ctx, + TALLOC_CTX *mem_ctx, + char *share, + struct smbconf_service **service_def) +{ + sbcErr cerr; + struct smbconf_service *def; + + *service_def = NULL; + cerr = smbconf_get_share(fconf_ctx, mem_ctx, share, &def); + if (SBC_ERROR_IS_OK(cerr)) { + *service_def = def; + return SBC_ERR_OK; + } + + cerr = smbconf_get_share(rconf_ctx, mem_ctx, share, &def); + if (SBC_ERROR_IS_OK(cerr)) { + *service_def = def; + return SBC_ERR_OK; + } + return cerr; +} + +/* + * Expose a new share using libsmbconf, cloning the existing configuration + * from the base share. The base share may be defined in either the registry + * or smb.conf. + * XXX this is called as root + */ +static uint32_t fss_sc_expose(struct smbconf_ctx *fconf_ctx, + struct smbconf_ctx *rconf_ctx, + TALLOC_CTX *mem_ctx, + struct fss_sc *sc) +{ + struct fss_sc_smap *sc_smap; + uint32_t err = 0; + + for (sc_smap = sc->smaps; sc_smap; sc_smap = sc_smap->next) { + sbcErr cerr; + struct smbconf_service *base_service = NULL; + struct security_descriptor *sd; + size_t sd_size; + + cerr = fss_conf_get_share_def(fconf_ctx, rconf_ctx, mem_ctx, + sc_smap->share_name, &base_service); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed to get base share %s definition: " + "%s\n", sc_smap->share_name, + sbcErrorString(cerr))); + err = HRES_ERROR_V(HRES_E_FAIL); + break; + } + + /* smap share name already defined when added */ + err = map_share_comment(sc_smap, sc); + if (err) { + DEBUG(0, ("failed to map share comment\n")); + break; + } + + base_service->name = sc_smap->sc_share_name; + + cerr = smbconf_create_set_share(rconf_ctx, base_service); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed to create share %s: %s\n", + base_service->name, sbcErrorString(cerr))); + err = HRES_ERROR_V(HRES_E_FAIL); + break; + } + cerr = smbconf_set_parameter(rconf_ctx, sc_smap->sc_share_name, + "path", sc->sc_path); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed to set path param: %s\n", + sbcErrorString(cerr))); + err = HRES_ERROR_V(HRES_E_FAIL); + break; + } + if (sc_smap->sc_share_comment != NULL) { + cerr = smbconf_set_parameter(rconf_ctx, + sc_smap->sc_share_name, + "comment", + sc_smap->sc_share_comment); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed to set comment param: %s\n", + sbcErrorString(cerr))); + err = HRES_ERROR_V(HRES_E_FAIL); + break; + } + } + talloc_free(base_service); + + /* + * Obtain the base share SD, which also needs to be cloned. + * Share SDs are stored in share_info.tdb, so are not covered by + * the registry transaction. + * The base share SD should be cloned at the time of exposure, + * rather than when the snapshot is taken. This matches Windows + * Server 2012 behaviour. + */ + sd = get_share_security(mem_ctx, sc_smap->share_name, &sd_size); + if (sd == NULL) { + DEBUG(2, ("no share SD to clone for %s snapshot\n", + sc_smap->share_name)); + } else { + NTSTATUS status; + status = set_share_security(sc_smap->sc_share_name, sd); + TALLOC_FREE(sd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to set %s share SD\n", + sc_smap->sc_share_name)); + err = HRES_ERROR_V(HRES_E_FAIL); + break; + } + } + } + + return err; +} + +uint32_t _fss_ExposeShadowCopySet(struct pipes_struct *p, + struct fss_ExposeShadowCopySet *r) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + struct fss_sc *sc; + uint32_t ret; + struct smbconf_ctx *fconf_ctx; + struct smbconf_ctx *rconf_ctx; + sbcErr cerr; + char *fconf_path; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!fss_permitted(p)) { + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_out; + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + ret = HRES_ERROR_V(HRES_E_INVALIDARG); + goto err_out; + } + + if (sc_set->state != FSS_SC_COMMITED) { + ret = FSRVP_E_BAD_STATE; + goto err_out; + } + + /* stop message sequence timer */ + TALLOC_FREE(fss_global.seq_tmr); + + /* + * Prepare to clone the base share definition for the snapshot share. + * Create both registry and file conf contexts, as the base share + * definition may be located in either. The snapshot share definition + * is always written to the registry. + */ + cerr = smbconf_init(frame, &rconf_ctx, "registry"); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed registry smbconf init: %s\n", + sbcErrorString(cerr))); + ret = HRES_ERROR_V(HRES_E_FAIL); + goto err_tmr_restart; + } + fconf_path = talloc_asprintf(frame, "file:%s", get_dyn_CONFIGFILE()); + if (fconf_path == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmr_restart; + } + cerr = smbconf_init(frame, &fconf_ctx, fconf_path); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed %s smbconf init: %s\n", + fconf_path, sbcErrorString(cerr))); + ret = HRES_ERROR_V(HRES_E_FAIL); + goto err_tmr_restart; + } + + /* registry IO must be done as root */ + become_root(); + cerr = smbconf_transaction_start(rconf_ctx); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("error starting transaction: %s\n", + sbcErrorString(cerr))); + ret = HRES_ERROR_V(HRES_E_FAIL); + unbecome_root(); + goto err_tmr_restart; + } + + for (sc = sc_set->scs; sc; sc = sc->next) { + ret = fss_sc_expose(fconf_ctx, rconf_ctx, frame, sc); + if (ret) { + DEBUG(0,("failed to expose shadow copy of %s\n", + sc->volume_name)); + goto err_cancel; + } + } + + cerr = smbconf_transaction_commit(rconf_ctx); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("error committing transaction: %s\n", + sbcErrorString(cerr))); + ret = HRES_ERROR_V(HRES_E_FAIL); + goto err_cancel; + } + unbecome_root(); + + messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + for (sc = sc_set->scs; sc; sc = sc->next) { + struct fss_sc_smap *sm; + for (sm = sc->smaps; sm; sm = sm->next) + sm->is_exposed = true; + } + sc_set->state = FSS_SC_EXPOSED; + become_root(); + status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets, + fss_global.sc_sets_count, fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to store fss server state: %s\n", + nt_errstr(status))); + } + /* start message sequence timer */ + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr); + TALLOC_FREE(frame); + return 0; + +err_cancel: + smbconf_transaction_cancel(rconf_ctx); + unbecome_root(); +err_tmr_restart: + fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr); +err_out: + TALLOC_FREE(frame); + return ret; +} + +uint32_t _fss_RecoveryCompleteShadowCopySet(struct pipes_struct *p, + struct fss_RecoveryCompleteShadowCopySet *r) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + if (sc_set->state != FSS_SC_EXPOSED) { + return FSRVP_E_BAD_STATE; + } + + /* stop msg sequence timer */ + TALLOC_FREE(fss_global.seq_tmr); + + if (sc_set->context & ATTR_NO_AUTO_RECOVERY) { + /* TODO set read-only */ + } + + sc_set->state = FSS_SC_RECOVERED; + fss_global.cur_ctx = 0; + fss_global.ctx_set = false; + + become_root(); + status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets, + fss_global.sc_sets_count, fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to store fss server state: %s\n", + nt_errstr(status))); + } + + return 0; +} + +uint32_t _fss_AbortShadowCopySet(struct pipes_struct *p, + struct fss_AbortShadowCopySet *r) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + DEBUG(6, ("%s: aborting shadow-copy set\n", sc_set->id_str)); + + if ((sc_set->state == FSS_SC_COMMITED) + || (sc_set->state == FSS_SC_EXPOSED) + || (sc_set->state == FSS_SC_RECOVERED)) { + return 0; + } + + if (sc_set->state == FSS_SC_CREATING) { + return FSRVP_E_BAD_STATE; + } + + DLIST_REMOVE(fss_global.sc_sets, sc_set); + talloc_free(sc_set); + fss_global.sc_sets_count--; + become_root(); + status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets, + fss_global.sc_sets_count, fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to store fss server state: %s\n", + nt_errstr(status))); + } + + return 0; +} + +uint32_t _fss_IsPathSupported(struct pipes_struct *p, + struct fss_IsPathSupported *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + int snum; + char *service; + char *base_vol; + NTSTATUS status; + struct connection_struct *conn; + char *share; + TALLOC_CTX *frame = talloc_stackframe(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (!fss_permitted(p)) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + status = fss_unc_parse(frame, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return fss_ntstatus_map(status); + } + + snum = find_service(frame, share, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", r->in.ShareName)); + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + if (!become_user_without_service_by_session(conn, session_info)) { + DEBUG(0, ("failed to become user\n")); + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + status = SMB_VFS_SNAP_CHECK_PATH(conn, frame, + lp_path(frame, lp_sub, snum), + &base_vol); + unbecome_user_without_service(); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return FSRVP_E_NOT_SUPPORTED; + } + + *r->out.OwnerMachineName = lp_netbios_name(); + *r->out.SupportedByThisProvider = 1; + TALLOC_FREE(frame); + return 0; +} + +uint32_t _fss_IsPathShadowCopied(struct pipes_struct *p, + struct fss_IsPathShadowCopied *r) +{ + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + /* not yet supported */ + return FSRVP_E_NOT_SUPPORTED; +} + +uint32_t _fss_GetShareMapping(struct pipes_struct *p, + struct fss_GetShareMapping *r) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + struct fss_sc *sc; + struct fss_sc_smap *sc_smap; + char *share; + struct fssagent_share_mapping_1 *sm_out; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!fss_permitted(p)) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + /* + * If ShadowCopySet.Status is not "Exposed", the server SHOULD<9> fail + * the call with FSRVP_E_BAD_STATE. + * <9> If ShadowCopySet.Status is "Started", "Added", + * "CreationInProgress", or "Committed", Windows Server 2012 FSRVP + * servers return an error value of 0x80042311. + */ + if ((sc_set->state == FSS_SC_STARTED) + || (sc_set->state == FSS_SC_ADDED) + || (sc_set->state == FSS_SC_CREATING) + || (sc_set->state == FSS_SC_COMMITED)) { + TALLOC_FREE(frame); + return 0x80042311; /* documented magic value */ + } + + sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId); + if (sc == NULL) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + status = fss_unc_parse(frame, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return fss_ntstatus_map(status); + } + + sc_smap = sc_smap_lookup(sc->smaps, share); + if (sc_smap == NULL) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + if (r->in.Level != 1) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + sm_out = talloc_zero(p->mem_ctx, struct fssagent_share_mapping_1); + if (sm_out == NULL) { + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + sm_out->ShadowCopySetId = sc_set->id; + sm_out->ShadowCopyId = sc->id; + sm_out->ShareNameUNC = talloc_asprintf(sm_out, "\\\\%s\\%s", + lp_netbios_name(), + sc_smap->share_name); + if (sm_out->ShareNameUNC == NULL) { + talloc_free(sm_out); + TALLOC_FREE(frame); + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + sm_out->ShadowCopyShareName = sc_smap->sc_share_name; + unix_to_nt_time(&sm_out->tstamp, sc->create_ts); + r->out.ShareMapping->ShareMapping1 = sm_out; + TALLOC_FREE(frame); + + /* reset msg sequence timer */ + TALLOC_FREE(fss_global.seq_tmr); + fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr); + + return 0; +} + +static NTSTATUS sc_smap_unexpose(struct messaging_context *msg_ctx, + struct fss_sc_smap *sc_smap, bool delete_all) +{ + NTSTATUS ret; + struct smbconf_ctx *conf_ctx; + sbcErr cerr; + bool is_modified = false; + TALLOC_CTX *frame = talloc_stackframe(); + + cerr = smbconf_init(frame, &conf_ctx, "registry"); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("failed registry smbconf init: %s\n", + sbcErrorString(cerr))); + ret = NT_STATUS_UNSUCCESSFUL; + goto err_tmp; + } + + /* registry IO must be done as root */ + become_root(); + + cerr = smbconf_transaction_start(conf_ctx); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("error starting transaction: %s\n", + sbcErrorString(cerr))); + ret = NT_STATUS_UNSUCCESSFUL; + goto err_conf; + } + + while (sc_smap) { + struct fss_sc_smap *sc_map_next = sc_smap->next; + if (!smbconf_share_exists(conf_ctx, sc_smap->sc_share_name)) { + DEBUG(2, ("no such share: %s\n", sc_smap->sc_share_name)); + if (!delete_all) { + ret = NT_STATUS_OK; + goto err_cancel; + } + sc_smap = sc_map_next; + continue; + } + + cerr = smbconf_delete_share(conf_ctx, sc_smap->sc_share_name); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("error deleting share: %s\n", + sbcErrorString(cerr))); + ret = NT_STATUS_UNSUCCESSFUL; + goto err_cancel; + } + is_modified = true; + sc_smap->is_exposed = false; + if (delete_all) { + sc_smap = sc_map_next; + } else { + sc_smap = NULL; /* only process single sc_map entry */ + } + } + if (is_modified) { + cerr = smbconf_transaction_commit(conf_ctx); + if (!SBC_ERROR_IS_OK(cerr)) { + DEBUG(0, ("error committing transaction: %s\n", + sbcErrorString(cerr))); + ret = NT_STATUS_UNSUCCESSFUL; + goto err_cancel; + } + messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + } else { + ret = NT_STATUS_OK; + goto err_cancel; + } + ret = NT_STATUS_OK; + +err_conf: + talloc_free(conf_ctx); + unbecome_root(); +err_tmp: + TALLOC_FREE(frame); + return ret; + +err_cancel: + smbconf_transaction_cancel(conf_ctx); + talloc_free(conf_ctx); + unbecome_root(); + TALLOC_FREE(frame); + return ret; +} + +uint32_t _fss_DeleteShareMapping(struct pipes_struct *p, + struct fss_DeleteShareMapping *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct fss_sc_set *sc_set; + struct fss_sc *sc; + struct fss_sc_smap *sc_smap; + char *share; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + struct connection_struct *conn; + int snum; + char *service; + + if (!fss_permitted(p)) { + status = NT_STATUS_ACCESS_DENIED; + goto err_tmp_free; + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + /* docs say HRES_E_INVALIDARG */ + status = NT_STATUS_OBJECTID_NOT_FOUND; + goto err_tmp_free; + } + + if ((sc_set->state != FSS_SC_EXPOSED) + && (sc_set->state != FSS_SC_RECOVERED)) { + status = NT_STATUS_INVALID_SERVER_STATE; + goto err_tmp_free; + } + + sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId); + if (sc == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_tmp_free; + } + + status = fss_unc_parse(frame, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + goto err_tmp_free; + } + + sc_smap = sc_smap_lookup(sc->smaps, share); + if (sc_smap == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err_tmp_free; + } + + status = sc_smap_unexpose(p->msg_ctx, sc_smap, false); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to remove share %s: %s\n", + sc_smap->sc_share_name, nt_errstr(status))); + goto err_tmp_free; + } + + messaging_send_all(p->msg_ctx, MSG_SMB_FORCE_TDIS, + sc_smap->sc_share_name, + strlen(sc_smap->sc_share_name) + 1); + + if (sc->smaps_count > 1) { + /* do not delete the underlying snapshot - still in use */ + status = NT_STATUS_OK; + goto err_tmp_free; + } + + snum = find_service(frame, sc_smap->share_name, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", sc_smap->share_name)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_tmp_free; + } + + status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + goto err_tmp_free; + } + if (!become_user_without_service_by_session(conn, session_info)) { + DEBUG(0, ("failed to become user\n")); + status = NT_STATUS_ACCESS_DENIED; + goto err_tmp_free; + } + + status = SMB_VFS_SNAP_DELETE(conn, frame, sc->volume_name, + sc->sc_path); + unbecome_user_without_service(); + if (!NT_STATUS_IS_OK(status)) { + goto err_tmp_free; + } + + /* XXX set timeout r->in.TimeOutInMilliseconds */ + DEBUG(6, ("good snap delete\n")); + DLIST_REMOVE(sc->smaps, sc_smap); + sc->smaps_count--; + talloc_free(sc_smap); + if (sc->smaps_count == 0) { + DLIST_REMOVE(sc_set->scs, sc); + sc_set->scs_count--; + talloc_free(sc); + + if (sc_set->scs_count == 0) { + DLIST_REMOVE(fss_global.sc_sets, sc_set); + fss_global.sc_sets_count--; + talloc_free(sc_set); + } + } + + become_root(); + status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets, + fss_global.sc_sets_count, fss_global.db_path); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("failed to store fss server state: %s\n", + nt_errstr(status))); + } + + status = NT_STATUS_OK; +err_tmp_free: + TALLOC_FREE(frame); + return fss_ntstatus_map(status); +} + +uint32_t _fss_PrepareShadowCopySet(struct pipes_struct *p, + struct fss_PrepareShadowCopySet *r) +{ + struct fss_sc_set *sc_set; + + if (!fss_permitted(p)) { + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId); + if (sc_set == NULL) { + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + if (sc_set->state != FSS_SC_ADDED) { + return FSRVP_E_BAD_STATE; + } + + /* stop msg sequence timer */ + TALLOC_FREE(fss_global.seq_tmr); + + /* + * Windows Server "8" Beta takes ~60s here, presumably flushing + * everything to disk. We may want to do something similar. + */ + + /* start msg sequence timer, 1800 on success */ + fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr); + + return 0; +} + +static NTSTATUS FileServerVssAgent__op_init_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +static NTSTATUS FileServerVssAgent__op_shutdown_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_FILESERVERVSSAGENT_INIT_SERVER \ + fileservervssagent_init_server + +#define DCESRV_INTERFACE_FILESERVERVSSAGENT_SHUTDOWN_SERVER \ + fileservervssagent_shutdown_server + +static NTSTATUS fileservervssagent_shutdown_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + srv_fssa_cleanup(); + return FileServerVssAgent__op_shutdown_server(dce_ctx, ep_server); +} + +static NTSTATUS fileservervssagent_init_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + NTSTATUS status; + struct messaging_context *msg_ctx = global_messaging_context(); + + status = srv_fssa_start(msg_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return FileServerVssAgent__op_init_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_fsrvp_scompat.c" diff --git a/source3/rpc_server/fss/srv_fss_private.h b/source3/rpc_server/fss/srv_fss_private.h new file mode 100644 index 0000000..4db9f98 --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_private.h @@ -0,0 +1,92 @@ +/* + * File Server Remote VSS Protocol (FSRVP) server state + * + * Copyright (C) David Disseldorp 2012-2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _SRV_FSS_PRIVATE_H_ +#define _SRV_FSS_PRIVATE_H_ + +#define FSS_DB_NAME "srv_fss.tdb" + +struct fss_sc_smap { + struct fss_sc_smap *next, *prev; + char *share_name; /* name of the base file share */ + char *sc_share_name; /* share exposing the shadow copy */ + char *sc_share_comment; + bool is_exposed; /* whether shadow copy is exposed */ +}; + +struct fss_sc { + struct fss_sc *next, *prev; + struct GUID id; /* GUID of the shadow copy */ + char *id_str; + char *volume_name; /* name uniquely identifying on the + * server object store on which this + * shadow copy is created. */ + char *sc_path; /* path exposing the shadow copy */ + time_t create_ts; /* timestamp of client initiation */ + struct fss_sc_smap *smaps; /* shares mapped to this shadow copy */ + uint32_t smaps_count; + struct fss_sc_set *sc_set; /* parent shadow copy set */ +}; + +/* + * 3.1.1.2: Per ShadowCopySet + * The status of the shadow copy set. This MUST be one of "Started", "Added", + * "CreationInProgress", "Committed", "Exposed", or "Recovered". + */ +enum fss_sc_state { + FSS_SC_STARTED, + FSS_SC_ADDED, + FSS_SC_CREATING, + FSS_SC_COMMITED, + FSS_SC_EXPOSED, + FSS_SC_RECOVERED, +}; +struct fss_sc_set { + struct fss_sc_set *next, *prev; + struct GUID id; /* GUID of the shadow copy set. */ + char *id_str; + enum fss_sc_state state; /* status of the shadow copy set */ + uint32_t context; /* attributes used for set creation */ + struct fss_sc *scs; /* list of ShadowCopy objects */ + uint32_t scs_count; +}; + +struct fss_global { + TALLOC_CTX *mem_ctx; /* parent mem ctx for sc sets */ + char *db_path; + uint32_t min_vers; + uint32_t max_vers; + bool ctx_set; /* whether client has set context */ + uint32_t cur_ctx; + struct fss_sc_set *sc_sets; + uint32_t sc_sets_count; + struct tevent_timer *seq_tmr; /* time to wait between client reqs */ +}; + +NTSTATUS fss_state_store(TALLOC_CTX *mem_ctx, + struct fss_sc_set *sc_sets, + uint32_t sc_sets_count, + const char *db_path); + +NTSTATUS fss_state_retrieve(TALLOC_CTX *mem_ctx, + struct fss_sc_set **sc_sets, + uint32_t *sc_sets_count, + const char *db_path); + +#endif /*_SRV_FSS_PRIVATE_H_ */ diff --git a/source3/rpc_server/fss/srv_fss_state.c b/source3/rpc_server/fss/srv_fss_state.c new file mode 100644 index 0000000..8597c36 --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_state.c @@ -0,0 +1,698 @@ +/* + * File Server Remote VSS Protocol (FSRVP) persistent server state + * + * Copyright (C) David Disseldorp 2012-2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "source3/include/includes.h" +#include <fcntl.h> +#include "source3/include/util_tdb.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/ndr_fsrvp_state.h" +#include "srv_fss_private.h" + +#define FSS_DB_KEY_VERSION "db_version" +#define FSS_DB_KEY_CONTEXT "context" +#define FSS_DB_KEY_SC_SET_COUNT "sc_set_count" +#define FSS_DB_KEY_PFX_SC_SET "sc_set/" +#define FSS_DB_KEY_PFX_SC "sc/" +#define FSS_DB_KEY_PFX_SMAP "smap/" + +static NTSTATUS fss_state_smap_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + const char *sc_key_str, + struct fss_sc_smap *smap) +{ + NTSTATUS status; + TDB_DATA val; + const char *smap_key_str; + struct fsrvp_state_smap smap_state; + enum ndr_err_code ndr_ret; + DATA_BLOB smap_state_blob; + + /* becomes sc_set/@sc_set_id/sc/@sc_id/smap/@sc_share_name */ + smap_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_key_str, + FSS_DB_KEY_PFX_SMAP, + smap->sc_share_name); + if (smap_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + smap_state.share_name = smap->share_name; + smap_state.sc_share_name = smap->sc_share_name; + /* @smap->sc_share_comment may be null if not exposed. */ + if (smap->sc_share_comment != NULL) { + smap_state.sc_share_comment = smap->sc_share_comment; + } else { + smap_state.sc_share_comment = ""; + } + smap_state.is_exposed = smap->is_exposed; + + ndr_ret = ndr_push_struct_blob(&smap_state_blob, mem_ctx, + &smap_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_smap); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = smap_state_blob.length; + val.dptr = smap_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(smap_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + const char *sc_set_key_str, + struct fss_sc *sc) +{ + NTSTATUS status; + TDB_DATA val; + const char *sc_key_str; + struct fsrvp_state_sc sc_state; + struct fss_sc_smap *smap; + enum ndr_err_code ndr_ret; + DATA_BLOB sc_state_blob; + + /* becomes sc_set/@sc_set.id/sc/@sc_id */ + sc_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_set_key_str, + FSS_DB_KEY_PFX_SC, sc->id_str); + if (sc_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc_state.id_str = sc->id_str; + sc_state.volume_name = sc->volume_name; + /* @sc->sc_path may be null if not committed, store empty str */ + sc_state.sc_path = (sc->sc_path ? sc->sc_path : ""); + sc_state.create_ts = sc->create_ts; + sc_state.smaps_count = sc->smaps_count; + + ndr_ret = ndr_push_struct_blob(&sc_state_blob, mem_ctx, + &sc_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = sc_state_blob.length; + val.dptr = sc_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(sc_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (smap = sc->smaps; smap; smap = smap->next) { + status = fss_state_smap_store(mem_ctx, db, sc_key_str, smap); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_set_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + struct fss_sc_set *sc_set) +{ + NTSTATUS status; + TDB_DATA val; + const char *sc_set_key_str; + struct fss_sc *sc; + struct fsrvp_state_sc_set sc_set_state; + DATA_BLOB sc_set_state_blob; + enum ndr_err_code ndr_ret; + + sc_set_key_str = talloc_asprintf(mem_ctx, "%s%s", + FSS_DB_KEY_PFX_SC_SET, + sc_set->id_str); + if (sc_set_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc_set_state.id_str = sc_set->id_str; + sc_set_state.state = sc_set->state; + sc_set_state.context = sc_set->context; + sc_set_state.scs_count = sc_set->scs_count; + + ndr_ret = ndr_push_struct_blob(&sc_set_state_blob, mem_ctx, + &sc_set_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc_set); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = sc_set_state_blob.length; + val.dptr = sc_set_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(sc_set_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (sc = sc_set->scs; sc; sc = sc->next) { + status = fss_state_sc_store(mem_ctx, db, sc_set_key_str, sc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +/* + * write out the current fsrvp server state to a TDB. This clears any content + * currently written to the TDB. + */ +_PRIVATE_ NTSTATUS fss_state_store(TALLOC_CTX *mem_ctx, + struct fss_sc_set *sc_sets, + uint32_t sc_sets_count, + const char *db_path) +{ + TALLOC_CTX *tmp_ctx; + struct db_context *db; + NTSTATUS status; + int ret; + struct fss_sc_set *sc_set; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + db = db_open(tmp_ctx, db_path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, + 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + if (db == NULL) { + DEBUG(0, ("Failed to open fss state database %s\n", db_path)); + status = NT_STATUS_ACCESS_DENIED; + goto err_ctx_free; + } + + ret = dbwrap_wipe(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_VERSION, + FSRVP_STATE_DB_VERSION); + if (!NT_STATUS_IS_OK(status)) { + goto err_db_free; + } + + ret = dbwrap_transaction_start(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_SC_SET_COUNT, + sc_sets_count); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_trans_cancel; + } + + for (sc_set = sc_sets; sc_set; sc_set = sc_set->next) { + status = fss_state_sc_set_store(tmp_ctx, db, sc_set); + if (!NT_STATUS_IS_OK(status)) { + goto err_trans_cancel; + } + } + + ret = dbwrap_transaction_commit(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_trans_cancel; + } + + talloc_free(db); + talloc_free(tmp_ctx); + return NT_STATUS_OK; + +err_trans_cancel: + dbwrap_transaction_cancel(db); +err_db_free: + talloc_free(db); +err_ctx_free: + talloc_free(tmp_ctx); + return status; +} + +static NTSTATUS fss_state_smap_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_smap **smap_out) +{ + struct fss_sc_smap *smap; + struct fsrvp_state_smap smap_state; + DATA_BLOB smap_state_blob; + enum ndr_err_code ndr_ret; + + smap_state_blob.length = val->dsize; + smap_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&smap_state_blob, mem_ctx, &smap_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_smap); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + smap = talloc_zero(mem_ctx, struct fss_sc_smap); + if (smap == NULL) { + return NT_STATUS_NO_MEMORY; + } + + smap->share_name = talloc_strdup(smap, smap_state.share_name); + if (smap->share_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + smap->sc_share_name = talloc_strdup(smap, (char *)key->dptr); + if (smap->sc_share_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* sc_share_comment may be empty, keep null in such a case */ + if (strlen(smap_state.sc_share_comment) > 0) { + smap->sc_share_comment = talloc_strdup(smap, + smap_state.sc_share_comment); + if (smap->sc_share_comment == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + smap->is_exposed = smap_state.is_exposed; + + *smap_out = smap; + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc **sc_out) +{ + struct fss_sc *sc; + struct fsrvp_state_sc sc_state; + DATA_BLOB sc_state_blob; + enum ndr_err_code ndr_ret; + + sc_state_blob.length = val->dsize; + sc_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&sc_state_blob, mem_ctx, &sc_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + sc = talloc_zero(mem_ctx, struct fss_sc); + if (sc == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + sc->id_str = talloc_strdup(sc, (char *)key->dptr); + if (sc->id_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc->volume_name = talloc_strdup(sc, sc_state.volume_name); + if (sc->volume_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* sc_path may be empty, keep null in such a case */ + if (strlen(sc_state.sc_path) > 0) { + sc->sc_path = talloc_strdup(sc, sc_state.sc_path); + if (sc->sc_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + sc->create_ts = sc_state.create_ts; + sc->smaps_count = sc_state.smaps_count; + + *sc_out = sc; + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_set_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_set **sc_set_out) +{ + struct fss_sc_set *sc_set; + struct fsrvp_state_sc_set sc_set_state; + DATA_BLOB sc_set_state_blob; + enum ndr_err_code ndr_ret; + + sc_set_state_blob.length = val->dsize; + sc_set_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&sc_set_state_blob, mem_ctx, + &sc_set_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc_set); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + sc_set = talloc_zero(mem_ctx, struct fss_sc_set); + if (sc_set == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + sc_set->id_str = talloc_strdup(sc_set, (char *)key->dptr); + if (sc_set->id_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + sc_set->state = sc_set_state.state; + sc_set->context = sc_set_state.context; + sc_set->scs_count = sc_set_state.scs_count; + + *sc_set_out = sc_set; + return NT_STATUS_OK; +} + +struct fss_traverse_state { + TALLOC_CTX *mem_ctx; + struct fss_sc_smap *smaps; + uint32_t smaps_count; + struct fss_sc *scs; + uint32_t scs_count; + struct fss_sc_set *sc_sets; + uint32_t sc_sets_count; + NTSTATUS (*smap_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_smap **smap_out); + NTSTATUS (*sc_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc **sc_out); + NTSTATUS (*sc_set_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_set **sc_set_out); +}; + +static int fss_state_retrieve_traverse(struct db_record *rec, + void *private_data) +{ + NTSTATUS status; + struct fss_traverse_state *trv_state + = (struct fss_traverse_state *)private_data; + TDB_DATA key = dbwrap_record_get_key(rec); + TDB_DATA val = dbwrap_record_get_value(rec); + + /* order of checking is important here */ + if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SMAP) != NULL) { + struct fss_sc_smap *smap; + status = trv_state->smap_retrieve(trv_state->mem_ctx, + &key, &val, &smap); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->smaps, smap); + trv_state->smaps_count++; + } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC) != NULL) { + struct fss_sc *sc; + status = trv_state->sc_retrieve(trv_state->mem_ctx, + &key, &val, &sc); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->scs, sc); + trv_state->scs_count++; + } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC_SET) != NULL) { + struct fss_sc_set *sc_set; + status = trv_state->sc_set_retrieve(trv_state->mem_ctx, + &key, &val, &sc_set); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->sc_sets, sc_set); + trv_state->sc_sets_count++; + } else { + /* global context and db vers */ + DEBUG(4, ("Ignoring fss srv db entry with key %s\n", key.dptr)); + } + + return 0; +} + +static bool fss_state_smap_is_child(struct fss_sc *sc, + struct fss_sc_smap *smap) +{ + return (strstr(smap->sc_share_name, sc->id_str) != NULL); +} + +static NTSTATUS fss_state_hierarchize_smaps(struct fss_traverse_state *trv_state, + struct fss_sc *sc) +{ + struct fss_sc_smap *smap; + struct fss_sc_smap *smap_n; + uint32_t smaps_moved = 0; + + for (smap = trv_state->smaps; smap; smap = smap_n) { + smap_n = smap->next; + if (!fss_state_smap_is_child(sc, smap)) + continue; + + /* smap mem should be owned by parent sc */ + talloc_steal(sc, smap); + DLIST_REMOVE(trv_state->smaps, smap); + trv_state->smaps_count--; + DLIST_ADD_END(sc->smaps, smap); + smaps_moved++; + + /* last component of the tdb key path is the sc share name */ + SMB_ASSERT(strrchr(smap->sc_share_name, '/') != NULL); + smap->sc_share_name = strrchr(smap->sc_share_name, '/') + 1; + } + + if (sc->smaps_count != smaps_moved) { + DEBUG(0, ("Inconsistent smaps_count, expected %u, moved %u\n", + sc->smaps_count, smaps_moved)); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static bool fss_state_sc_is_child(struct fss_sc_set *sc_set, + struct fss_sc *sc) +{ + return (strstr(sc->id_str, sc_set->id_str) != NULL); +} + +static NTSTATUS fss_state_hierarchize_scs(struct fss_traverse_state *trv_state, + struct fss_sc_set *sc_set) +{ + NTSTATUS status; + struct fss_sc *sc; + struct fss_sc *sc_n; + uint32_t scs_moved = 0; + + for (sc = trv_state->scs; sc; sc = sc_n) { + sc_n = sc->next; + if (!fss_state_sc_is_child(sc_set, sc)) + continue; + + /* sc mem should be owned by parent sc_set */ + talloc_steal(sc_set, sc); + DLIST_REMOVE(trv_state->scs, sc); + trv_state->scs_count--; + DLIST_ADD_END(sc_set->scs, sc); + scs_moved++; + + sc->sc_set = sc_set; + + /* last component of the tdb key path is the sc GUID str */ + SMB_ASSERT(strrchr(sc->id_str, '/') != NULL); + sc->id_str = strrchr(sc->id_str, '/') + 1; + + status = GUID_from_string(sc->id_str, &sc->id); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = fss_state_hierarchize_smaps(trv_state, sc); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + } + + if (sc_set->scs_count != scs_moved) { + DEBUG(0, ("Inconsistent scs_count, expected %u, moved %u\n", + sc_set->scs_count, scs_moved)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + return NT_STATUS_OK; + +err_out: + return status; +} + +static NTSTATUS fss_state_hierarchize(struct fss_traverse_state *trv_state, + struct fss_sc_set **sc_sets, + uint32_t *sc_sets_count) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + struct fss_sc_set *sc_set_n; + uint32_t i = 0; + + *sc_sets = NULL; + for (sc_set = trv_state->sc_sets; sc_set; sc_set = sc_set_n) { + sc_set_n = sc_set->next; + /* sc_set mem already owned by trv_state->mem_ctx */ + DLIST_REMOVE(trv_state->sc_sets, sc_set); + trv_state->sc_sets_count--; + DLIST_ADD_END(*sc_sets, sc_set); + i++; + + /* last component of the tdb key path is the sc_set GUID str */ + SMB_ASSERT(strrchr(sc_set->id_str, '/') != NULL); + sc_set->id_str = strrchr(sc_set->id_str, '/') + 1; + + status = GUID_from_string(sc_set->id_str, &sc_set->id); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = fss_state_hierarchize_scs(trv_state, sc_set); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + } + *sc_sets_count = i; + return NT_STATUS_OK; + +err_out: + return status; +} + +_PRIVATE_ NTSTATUS fss_state_retrieve(TALLOC_CTX *mem_ctx, + struct fss_sc_set **sc_sets, + uint32_t *sc_sets_count, + const char *db_path) +{ + struct db_context *db; + NTSTATUS status; + struct fss_traverse_state trv_state; + int err; + int rec_count; + int vers; + *sc_sets = NULL; + *sc_sets_count = 0; + + memset(&trv_state, 0, sizeof(trv_state)); + trv_state.mem_ctx = talloc_new(mem_ctx); + if (trv_state.mem_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + /* set callbacks for unmarshalling on-disk structures */ + trv_state.smap_retrieve = fss_state_smap_retrieve; + trv_state.sc_retrieve = fss_state_sc_retrieve; + trv_state.sc_set_retrieve = fss_state_sc_set_retrieve; + + db = db_open(trv_state.mem_ctx, db_path, 0, TDB_DEFAULT, + O_RDONLY, 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + err = errno; + if ((db == NULL) && (err == ENOENT)) { + DEBUG(4, ("fss state TDB does not exist for retrieval\n")); + status = NT_STATUS_OK; + goto err_ts_free; + } else if (db == NULL) { + DEBUG(0, ("Failed to open fss state TDB: %s\n", + strerror(err))); + status = NT_STATUS_ACCESS_DENIED; + goto err_ts_free; + } + + status = dbwrap_fetch_int32_bystring(db, FSS_DB_KEY_VERSION, + &vers); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to fetch version from fss state tdb: %s\n", + nt_errstr(status))); + goto err_db_free; + } else if (vers != FSRVP_STATE_DB_VERSION) { + DEBUG(0, ("Unsupported fss tdb version %d, expected %d\n", + vers, FSRVP_STATE_DB_VERSION)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_traverse_read(db, + fss_state_retrieve_traverse, + &trv_state, + &rec_count); + if (!NT_STATUS_IS_OK(status)) { + goto err_db_free; + } + + status = fss_state_hierarchize(&trv_state, sc_sets, sc_sets_count); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to form fss state hierarchy\n")); + goto err_db_free; + } + + /* check whether anything was left without a parent */ + if (trv_state.sc_sets_count != 0) { + DEBUG(0, ("%d shadow copy set orphans in %s tdb\n", + trv_state.sc_sets_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + if (trv_state.scs_count != 0) { + DEBUG(0, ("%d shadow copy orphans in %s tdb\n", + trv_state.scs_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + if (trv_state.smaps_count != 0) { + DEBUG(0, ("%d share map orphans in %s tdb\n", + trv_state.smaps_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + talloc_free(db); + + return NT_STATUS_OK; + +err_db_free: + talloc_free(db); +err_ts_free: + talloc_free(trv_state.mem_ctx); +err_out: + return status; +} |