diff options
author | David Disseldorp <ddiss@samba.org> | 2012-04-10 14:32:41 +0200 |
---|---|---|
committer | Jeremy Allison <jra@samba.org> | 2015-03-31 18:40:25 +0200 |
commit | b6e034069f2a61d622c9fcacd30ff63e2fb59f92 (patch) | |
tree | 95baee07bda76e126b1727674afe3a09cff42126 | |
parent | 9f7dd07d2658ac50ea3a1db8c4541ec3f87b8d6b (diff) | |
download | samba-b6e034069f2a61d622c9fcacd30ff63e2fb59f92.tar.gz |
fsrvp: add remote snapshot RPC server
The Samba fss_agent RPC server is an implementation of the File Server
Remote VSS (Volume Shadow Copy Service) Protocol, or FSRVP for short.
FSRVP is new with Windows Server 2012, and allows authenticated clients
to remotely request the creation, exposure and deletion of share
snapshots.
The fss_agent RPC server processes requests on the FssAgentRpc named
pipe, and dispatches relevant snapshot creation and deletion requests
through to the VFS.
The registry smb.conf back-end is used to expose snapshot shares, with
configuration parameters and share ACLs cloned from the base share.
There are three FSRVP client implementations that I'm aware of:
- Samba rpcclient includes fss_X commands.
- Windows Server 2012 includes diskshadow.exe.
- System Center 2012.
FSRVP operations are only processed for users with:
- Built-in Administrators group membership, or
- Built-in Backup Operators group membership, or
- Backup Operator privileges, or
- Security token matches the initial process UID
MS-FSRVP specifies that server state should be stored persistently
during operation and retrieved on startup. Use the existing fss_srv.tdb
FSRVP state storage back-end to satisfy this requirement.
Signed-off-by: David Disseldorp <ddiss@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
-rw-r--r-- | source3/rpc_server/fss/srv_fss_agent.c | 1586 | ||||
-rw-r--r-- | source3/rpc_server/fss/srv_fss_agent.h | 26 | ||||
-rw-r--r-- | source3/rpc_server/fssd.c | 221 | ||||
-rw-r--r-- | source3/rpc_server/rpc_config.c | 4 | ||||
-rw-r--r-- | source3/rpc_server/rpc_config.h | 4 | ||||
-rwxr-xr-x | source3/rpc_server/wscript_build | 10 | ||||
-rw-r--r-- | source3/smbd/server.c | 7 | ||||
-rwxr-xr-x | source3/wscript_build | 2 |
8 files changed, 1856 insertions, 4 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 00000000000..67b12f9b822 --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_agent.c @@ -0,0 +1,1586 @@ +/* + * 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 "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/gen_ndr/srv_fsrvp.h" +#include "srv_fss_private.h" +#include "srv_fss_agent.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) +{ + int 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_vfs_conn_create(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + int snum, + struct connection_struct **conn_out) +{ + struct connection_struct *conn = NULL; + NTSTATUS status; + + status = create_conn_struct(mem_ctx, ev, msg_ctx, &conn, + snum, lp_path(mem_ctx, snum), + session_info); + 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(conn, snum); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed set force user / group\n")); + goto err_free_conn; + } + + *conn_out = conn; + + return NT_STATUS_OK; + +err_free_conn: + SMB_VFS_DISCONNECT(conn); + conn_free(conn); + return status; +} + +static void fss_vfs_conn_destroy(struct connection_struct *conn) +{ + SMB_VFS_DISCONNECT(conn); + conn_free(conn); +} + +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; +} + +void srv_fssa_cleanup(void) +{ + talloc_free(fss_global.db_path); + talloc_free(fss_global.mem_ctx); + ZERO_STRUCT(fss_global); +} + +NTSTATUS srv_fssa_start(void) +{ + 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(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. + */ + 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) +{ + if (p->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, + p->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, + p->session_info->security_token)) { + DEBUG(6, ("Granting FSRVP op, backup operators group member\n")); + return true; + } + if (security_token_has_privilege(p->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(server_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, struct fss_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) +{ + 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 *tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_out; + } + + 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(tmp_ctx, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + ret = fss_ntstatus_map(status); + goto err_tmp_free; + } + + snum = find_service(tmp_ctx, 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(tmp_ctx, snum); + if (path_name == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmp_free; + } + + status = fss_vfs_conn_create(tmp_ctx, server_event_context(), + p->msg_ctx, p->session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_tmp_free; + } + if (!become_user_by_session(conn, p->session_info)) { + DEBUG(0, ("failed to become user\n")); + fss_vfs_conn_destroy(conn); + ret = HRES_ERROR_V(HRES_E_ACCESSDENIED); + goto err_tmp_free; + } + + status = SMB_VFS_SNAP_CHECK_PATH(conn, tmp_ctx, path_name, &base_vol); + unbecome_user(); + fss_vfs_conn_destroy(conn); + 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, struct fss_sc_smap *); + sc->smaps_count++; + /* add shadow-copy to shadow-copy set */ + DLIST_ADD_END(sc_set->scs, sc, struct fss_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(tmp_ctx); + 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(tmp_ctx); +err_out: + 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) +{ + NTSTATUS status; + bool rw; + struct connection_struct *conn; + int snum; + char *service; + + snum = find_service(mem_ctx, sc->smaps->share_name, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", sc->smaps->share_name)); + return NT_STATUS_UNSUCCESSFUL; + } + + status = fss_vfs_conn_create(mem_ctx, + ev, msg_ctx, session_info, + snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!become_user_by_session(conn, session_info)) { + DEBUG(0, ("failed to become user\n")); + fss_vfs_conn_destroy(conn); + 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(); + fss_vfs_conn_destroy(conn); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("snap create failed: %s\n", nt_errstr(status))); + return status; + } + + return status; +} + +uint32_t _fss_CommitShadowCopySet(struct pipes_struct *p, + struct fss_CommitShadowCopySet *r) +{ + struct fss_sc_set *sc_set; + struct fss_sc *sc; + uint32_t commit_count; + NTSTATUS status; + NTSTATUS saved_status; + TALLOC_CTX *tmp_ctx; + + if (!fss_permitted(p)) { + status = NT_STATUS_ACCESS_DENIED; + goto err_out; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + 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(tmp_ctx, server_event_context(), + p->msg_ctx, p->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(tmp_ctx); + 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(tmp_ctx); +err_out: + 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 { + bool ok; + ok = set_share_security(sc_smap->sc_share_name, sd); + TALLOC_FREE(sd); + if (!ok) { + 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 *tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + 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(tmp_ctx, &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(tmp_ctx, "file:%s", get_dyn_CONFIGFILE()); + if (fconf_path == NULL) { + ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY); + goto err_tmr_restart; + } + cerr = smbconf_init(tmp_ctx, &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, tmp_ctx, 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(); + + message_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0, NULL); + 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(tmp_ctx); + 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(tmp_ctx); + 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) +{ + int snum; + char *service; + char *base_vol; + NTSTATUS status; + struct connection_struct *conn; + char *share; + TALLOC_CTX *tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + if (!fss_permitted(p)) { + talloc_free(tmp_ctx); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + + status = fss_unc_parse(tmp_ctx, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return fss_ntstatus_map(status); + } + + snum = find_service(tmp_ctx, share, &service); + if ((snum == -1) || (service == NULL)) { + DEBUG(0, ("share at %s not found\n", r->in.ShareName)); + talloc_free(tmp_ctx); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + status = fss_vfs_conn_create(tmp_ctx, server_event_context(), + p->msg_ctx, p->session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + if (!become_user_by_session(conn, p->session_info)) { + DEBUG(0, ("failed to become user\n")); + talloc_free(tmp_ctx); + fss_vfs_conn_destroy(conn); + return HRES_ERROR_V(HRES_E_ACCESSDENIED); + } + status = SMB_VFS_SNAP_CHECK_PATH(conn, tmp_ctx, + lp_path(tmp_ctx, snum), + &base_vol); + unbecome_user(); + fss_vfs_conn_destroy(conn); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return FSRVP_E_NOT_SUPPORTED; + } + + *r->out.OwnerMachineName = lp_netbios_name(); + *r->out.SupportedByThisProvider = 1; + talloc_free(tmp_ctx); + 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 *tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + return HRES_ERROR_V(HRES_E_OUTOFMEMORY); + } + + if (!fss_permitted(p)) { + talloc_free(tmp_ctx); + 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(tmp_ctx); + 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(tmp_ctx); + return 0x80042311; /* documented magic value */ + } + + sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId); + if (sc == NULL) { + talloc_free(tmp_ctx); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + status = fss_unc_parse(tmp_ctx, r->in.ShareName, NULL, &share); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return fss_ntstatus_map(status); + } + + sc_smap = sc_smap_lookup(sc->smaps, share); + if (sc_smap == NULL) { + talloc_free(tmp_ctx); + return HRES_ERROR_V(HRES_E_INVALIDARG); + } + + if (r->in.Level != 1) { + talloc_free(tmp_ctx); + 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(tmp_ctx); + 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(tmp_ctx); + 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(tmp_ctx); + + /* 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) +{ + NTSTATUS ret; + struct smbconf_ctx *conf_ctx; + sbcErr cerr; + TALLOC_CTX *tmp_ctx = talloc_new(sc_smap); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + cerr = smbconf_init(tmp_ctx, &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(); + + if (!smbconf_share_exists(conf_ctx, sc_smap->sc_share_name)) { + DEBUG(2, ("no such share: %s\n", sc_smap->sc_share_name)); + ret = NT_STATUS_OK; + goto done; + } + + 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; + } + + 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; + } + + 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; + } + message_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0, NULL); +done: + sc_smap->is_exposed = false; + + ret = NT_STATUS_OK; +err_conf: + talloc_free(conf_ctx); + unbecome_root(); +err_tmp: + talloc_free(tmp_ctx); + return ret; + +err_cancel: + smbconf_transaction_cancel(conf_ctx); + talloc_free(conf_ctx); + unbecome_root(); + talloc_free(tmp_ctx); + return ret; +} + +uint32_t _fss_DeleteShareMapping(struct pipes_struct *p, + struct fss_DeleteShareMapping *r) +{ + struct fss_sc_set *sc_set; + struct fss_sc *sc; + struct fss_sc_smap *sc_smap; + char *share; + NTSTATUS status; + TALLOC_CTX *tmp_ctx; + struct connection_struct *conn; + int snum; + char *service; + + if (!fss_permitted(p)) { + status = NT_STATUS_ACCESS_DENIED; + goto err_out; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + 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(tmp_ctx, 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); + 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; + } + + message_send_all(p->msg_ctx, MSG_SMB_FORCE_TDIS, sc_smap->sc_share_name, + strlen(sc_smap->sc_share_name) + 1, NULL); + + 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(tmp_ctx, 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_vfs_conn_create(tmp_ctx, server_event_context(), + p->msg_ctx, p->session_info, snum, &conn); + if (!NT_STATUS_IS_OK(status)) { + goto err_tmp_free; + } + if (!become_user_by_session(conn, p->session_info)) { + DEBUG(0, ("failed to become user\n")); + status = NT_STATUS_ACCESS_DENIED; + goto err_conn_destroy; + } + + status = SMB_VFS_SNAP_DELETE(conn, tmp_ctx, sc->volume_name, + sc->sc_path); + unbecome_user(); + if (!NT_STATUS_IS_OK(status)) { + goto err_conn_destroy; + } + + /* 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_conn_destroy: + fss_vfs_conn_destroy(conn); +err_tmp_free: + talloc_free(tmp_ctx); +err_out: + 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; +} diff --git a/source3/rpc_server/fss/srv_fss_agent.h b/source3/rpc_server/fss/srv_fss_agent.h new file mode 100644 index 00000000000..134b9c156af --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_agent.h @@ -0,0 +1,26 @@ +/* + * 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/>. + */ + +#ifndef _SRV_FSS_AGENT_H_ +#define _SRV_FSS_AGENT_H_ + +NTSTATUS srv_fssa_start(void); +void srv_fssa_cleanup(void); + +#endif /*_SRV_FSS_AGENT_H_ */ diff --git a/source3/rpc_server/fssd.c b/source3/rpc_server/fssd.c new file mode 100644 index 00000000000..96556996c44 --- /dev/null +++ b/source3/rpc_server/fssd.c @@ -0,0 +1,221 @@ +/* + * File Server Shadow-Copy Daemon + * + * Copyright (C) David Disseldorp 2012-2015 + * + * Based on epmd.c: + * Copyright (c) 2011 Andreas Schneider <asn@samba.org> + * + * 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 "serverid.h" +#include "ntdomain.h" +#include "messages.h" + +#include "librpc/rpc/dcerpc_ep.h" +#include "../librpc/gen_ndr/srv_fsrvp.h" +#include "rpc_server/rpc_server.h" +#include "rpc_server/rpc_sock_helper.h" +#include "rpc_server/fss/srv_fss_agent.h" + +#define DAEMON_NAME "fssd" + +void start_fssd(struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx); + +static void fssd_reopen_logs(void) +{ + char *lfile = lp_logfile(NULL); + int rc; + + if (lfile == NULL || lfile[0] == '\0') { + rc = asprintf(&lfile, "%s/log.%s", get_dyn_LOGFILEBASE(), DAEMON_NAME); + if (rc > 0) { + lp_set_logfile(lfile); + SAFE_FREE(lfile); + } + } else { + if (strstr(lfile, DAEMON_NAME) == NULL) { + rc = asprintf(&lfile, "%s.%s", lp_logfile(NULL), DAEMON_NAME); + if (rc > 0) { + lp_set_logfile(lfile); + SAFE_FREE(lfile); + } + } + } + + reopen_logs(); +} + +static void fssd_smb_conf_updated(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + DEBUG(10, ("Got message saying smb.conf was updated. Reloading.\n")); + change_to_root_user(); + fssd_reopen_logs(); +} + +static void fssd_sig_term_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + rpc_FileServerVssAgent_shutdown(); + + exit_server_cleanly("termination signal"); +} + +static void fssd_setup_sig_term_handler(struct tevent_context *ev_ctx) +{ + struct tevent_signal *se; + + se = tevent_add_signal(ev_ctx, + ev_ctx, + SIGTERM, 0, + fssd_sig_term_handler, + NULL); + if (se == NULL) { + exit_server("failed to setup SIGTERM handler"); + } +} + +static void fssd_sig_hup_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + change_to_root_user(); + + DEBUG(1,("reopening logs after SIGHUP\n")); + fssd_reopen_logs(); +} + +static void fssd_setup_sig_hup_handler(struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx) +{ + struct tevent_signal *se; + + se = tevent_add_signal(ev_ctx, + ev_ctx, + SIGHUP, 0, + fssd_sig_hup_handler, + msg_ctx); + if (se == NULL) { + exit_server("failed to setup SIGHUP handler"); + } +} + +static bool fss_shutdown_cb(void *ptr) +{ + srv_fssa_cleanup(); + return true; +} + +static bool fss_init_cb(void *ptr) +{ + NTSTATUS status; + status = srv_fssa_start(); + return NT_STATUS_IS_OK(status); +} + +void start_fssd(struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx) +{ + struct rpc_srv_callbacks fss_cb; + NTSTATUS status; + pid_t pid; + bool ok; + int rc; + + fss_cb.init = fss_init_cb; + fss_cb.shutdown = fss_shutdown_cb; + fss_cb.private_data = NULL; + + DEBUG(1, ("Forking File Server Shadow-copy Daemon\n")); + + pid = fork(); + + if (pid == -1) { + DEBUG(0, ("failed to fork file server shadow-copy daemon [%s], " + "aborting ...\n", strerror(errno))); + exit(1); + } + + if (pid) { + /* parent */ + return; + } + + /* child */ + status = reinit_after_fork(msg_ctx, + ev_ctx, + true); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("reinit_after_fork() failed\n")); + smb_panic("reinit_after_fork() failed"); + } + + fssd_reopen_logs(); + + fssd_setup_sig_term_handler(ev_ctx); + fssd_setup_sig_hup_handler(ev_ctx, msg_ctx); + + ok = serverid_register(procid_self(), + FLAG_MSG_GENERAL | + FLAG_MSG_PRINT_GENERAL); + if (!ok) { + DEBUG(0, ("Failed to register serverid in fssd!\n")); + exit(1); + } + + messaging_register(msg_ctx, + ev_ctx, + MSG_SMB_CONF_UPDATED, + fssd_smb_conf_updated); + + status = rpc_FileServerVssAgent_init(&fss_cb); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register fssd rpc inteface! (%s)\n", + nt_errstr(status))); + exit(1); + } + + /* case is normalized by smbd on connection */ + ok = setup_named_pipe_socket("fssagentrpc", ev_ctx, msg_ctx); + if (!ok) { + DEBUG(0, ("Failed to open fssd named pipe!\n")); + exit(1); + } + + DEBUG(1, ("File Server Shadow-copy Daemon Started (%d)\n", getpid())); + + /* loop forever */ + rc = tevent_loop_wait(ev_ctx); + + /* should not be reached */ + DEBUG(0,("tevent_loop_wait() exited with %d - %s\n", + rc, (rc == 0) ? "out of events" : strerror(errno))); + + exit(1); +} diff --git a/source3/rpc_server/rpc_config.c b/source3/rpc_server/rpc_config.c index d3a46ddc612..efc6668e91b 100644 --- a/source3/rpc_server/rpc_config.c +++ b/source3/rpc_server/rpc_config.c @@ -1,6 +1,6 @@ /* Unix SMB/Netbios implementation. - Generic infrstructure for RPC Daemons + Generic infrastructure for RPC Daemons Copyright (C) Simo Sorce 2011 Copyright (C) Andreas Schneider 2011 @@ -35,6 +35,7 @@ struct rpc_service_defaults { /* { "lsarpc", "embedded" }, */ /* { "samr", "embedded" }, */ /* { "netlogon", "embedded" }, */ + { "fssagentrpc", "external" }, { NULL, NULL } }; @@ -96,6 +97,7 @@ struct rpc_daemon_defaults { { "epmd", "disabled" }, /* { "spoolssd", "embedded" }, */ /* { "lsasd", "embedded" }, */ + { "fssd", "disabled" }, { NULL, NULL } }; diff --git a/source3/rpc_server/rpc_config.h b/source3/rpc_server/rpc_config.h index d1c26dd3667..ab5e3a4b0d1 100644 --- a/source3/rpc_server/rpc_config.h +++ b/source3/rpc_server/rpc_config.h @@ -33,7 +33,6 @@ enum rpc_service_mode_e { * @brief Get the mode in which service pipes are configured. * * @param name Name of the service - * @param def_mode The default mode for the service * * @return The actual configured mode. */ @@ -44,6 +43,7 @@ enum rpc_service_mode_e rpc_service_mode(const char *name); #define rpc_lsarpc_mode() rpc_service_mode("lsarpc") #define rpc_samr_mode() rpc_service_mode("samr") #define rpc_netlogon_mode() rpc_service_mode("netlogon") +#define rpc_fssagentrpc_mode() rpc_service_mode("fssagentrpc") @@ -57,7 +57,6 @@ enum rpc_daemon_type_e { * @brief Get the mode in which a server is started. * * @param name Name of the rpc server - * @param def_type The default type for the server * * @return The actual configured type. */ @@ -66,5 +65,6 @@ enum rpc_daemon_type_e rpc_daemon_type(const char *name); #define rpc_epmapper_daemon() rpc_daemon_type("epmd") #define rpc_spoolss_daemon() rpc_daemon_type("spoolssd") #define rpc_lsasd_daemon() rpc_daemon_type("lsasd") +#define rpc_fss_daemon() rpc_daemon_type("fssd") #endif /* _RPC_CONFIG_H */ diff --git a/source3/rpc_server/wscript_build b/source3/rpc_server/wscript_build index b7fc0f84865..25c923dd555 100755 --- a/source3/rpc_server/wscript_build +++ b/source3/rpc_server/wscript_build @@ -54,6 +54,11 @@ bld.SAMBA3_SUBSYSTEM('RPC_FSS_STATE', source='''fss/srv_fss_state.c''', deps='samba-util NDR_FSRVP_STATE') +bld.SAMBA3_SUBSYSTEM('RPC_FSS_AGENT', + source='''fss/srv_fss_agent.c + ../../librpc/gen_ndr/srv_fsrvp.c''', + deps='samba-util RPC_FSS_STATE') + bld.SAMBA3_SUBSYSTEM('RPC_EVENTLOG', source='''eventlog/srv_eventlog_nt.c eventlog/srv_eventlog_reg.c @@ -149,6 +154,7 @@ bld.SAMBA3_SUBSYSTEM('RPC_SERVICE', RPC_RPCECHO RPC_SERVER RPC_EPMAPPER + RPC_FSS_AGENT ''') # RPC_DAEMONS @@ -163,3 +169,7 @@ bld.SAMBA3_SUBSYSTEM('EPMD', bld.SAMBA3_SUBSYSTEM('LSASD', source='lsasd.c', deps='RPC_SOCK_HELPER samba-util') + +bld.SAMBA3_SUBSYSTEM('FSSD', + source='fssd.c', + deps='samba-util') diff --git a/source3/smbd/server.c b/source3/smbd/server.c index 99a6df6174f..030f760a3a9 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -86,6 +86,9 @@ extern void start_epmd(struct tevent_context *ev_ctx, extern void start_lsasd(struct tevent_context *ev_ctx, struct messaging_context *msg_ctx); +extern void start_fssd(struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx); + #ifdef WITH_DFS extern int dcelogin_atmost_once; #endif /* WITH_DFS */ @@ -1551,6 +1554,10 @@ extern void build_options(bool screen); start_lsasd(ev_ctx, msg_ctx); } + if (rpc_fss_daemon() == RPC_DAEMON_FORK) { + start_fssd(ev_ctx, msg_ctx); + } + if (!lp__disable_spoolss() && (rpc_spoolss_daemon() != RPC_DAEMON_DISABLED)) { bool bgq = lp_parm_bool(-1, "smbd", "backgroundqueue", true); diff --git a/source3/wscript_build b/source3/wscript_build index 6fa59311fba..d8b0bf35dca 100755 --- a/source3/wscript_build +++ b/source3/wscript_build @@ -855,7 +855,7 @@ bld.SAMBA3_SUBSYSTEM('LIBLSA', bld.SAMBA3_BINARY('smbd/smbd', source='smbd/server.c', - deps='smbd_base EPMD LSASD', + deps='smbd_base EPMD LSASD FSSD', install_path='${SBINDIR}') bld.SAMBA3_BINARY('nmbd/nmbd', |