diff options
Diffstat (limited to 'source3/rpc_server')
89 files changed, 67913 insertions, 0 deletions
diff --git a/source3/rpc_server/dfs/srv_dfs_nt.c b/source3/rpc_server/dfs/srv_dfs_nt.c new file mode 100644 index 0000000..8eaa59a --- /dev/null +++ b/source3/rpc_server/dfs/srv_dfs_nt.c @@ -0,0 +1,606 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines for Dfs + * Copyright (C) Shirish Kalele 2000. + * Copyright (C) Jeremy Allison 2001-2007. + * Copyright (C) Jelmer Vernooij 2005-2006. + * + * 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/>. + */ + +/* This is the implementation of the dfs pipe. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_dfs.h" +#include "librpc/gen_ndr/ndr_dfs_scompat.h" +#include "msdfs.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_MSDFS + +/* This function does not return a WERROR or NTSTATUS code but rather 1 if + dfs exists, or 0 otherwise. */ + +void _dfs_GetManagerVersion(struct pipes_struct *p, struct dfs_GetManagerVersion *r) +{ + if (lp_host_msdfs()) { + *r->out.version = DFS_MANAGER_VERSION_NT4; + } else { + *r->out.version = (enum dfs_ManagerVersion)0; + } +} + +WERROR _dfs_Add(struct pipes_struct *p, struct dfs_Add *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct junction_map *jn = NULL; + struct referral *old_referral_list = NULL; + bool self_ref = False; + size_t consumedcnt = 0; + char *altpath = NULL; + NTSTATUS status; + TALLOC_CTX *ctx = talloc_tos(); + const char *pathnamep = r->in.path; + + if (session_info->unix_token->uid != sec_initial_uid()) { + DEBUG(10,("_dfs_add: uid != 0. Access denied.\n")); + return WERR_ACCESS_DENIED; + } + + jn = talloc_zero(ctx, struct junction_map); + if (!jn) { + return WERR_NOT_ENOUGH_MEMORY; + } + + DEBUG(5,("init_reply_dfs_add: Request to add %s -> %s\\%s.\n", + r->in.path, r->in.server, r->in.share)); + + altpath = talloc_asprintf(ctx, "%s\\%s", + r->in.server, + r->in.share); + if (!altpath) { + return WERR_NOT_ENOUGH_MEMORY; + } + + while (IS_DIRECTORY_SEP(pathnamep[0]) && + IS_DIRECTORY_SEP(pathnamep[1])) { + pathnamep++; + } + + /* The following call can change the cwd. */ + status = get_referred_path(ctx, + session_info, + pathnamep, + remote_address, + local_address, + jn, &consumedcnt, &self_ref); + if(!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + jn->referral_count += 1; + old_referral_list = jn->referral_list; + + if (jn->referral_count < 1) { + return WERR_NOT_ENOUGH_MEMORY; + } + + jn->referral_list = talloc_array(ctx, struct referral, jn->referral_count); + if(jn->referral_list == NULL) { + DEBUG(0,("init_reply_dfs_add: talloc failed for referral list!\n")); + return WERR_NERR_DFSINTERNALERROR; + } + + if(old_referral_list && jn->referral_list) { + memcpy(jn->referral_list, old_referral_list, + sizeof(struct referral)*jn->referral_count-1); + } + + jn->referral_list[jn->referral_count-1].proximity = 0; + jn->referral_list[jn->referral_count-1].ttl = REFERRAL_TTL; + jn->referral_list[jn->referral_count-1].alternate_path = altpath; + + if (!create_msdfs_link(jn, session_info)) { + return WERR_NERR_DFSCANTCREATEJUNCTIONPOINT; + } + + return WERR_OK; +} + +WERROR _dfs_Remove(struct pipes_struct *p, struct dfs_Remove *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct junction_map *jn = NULL; + bool self_ref = False; + size_t consumedcnt = 0; + bool found = False; + TALLOC_CTX *ctx = talloc_tos(); + char *altpath = NULL; + NTSTATUS status; + const char *pathnamep = r->in.dfs_entry_path; + + if (session_info->unix_token->uid != sec_initial_uid()) { + DEBUG(10,("_dfs_remove: uid != 0. Access denied.\n")); + return WERR_ACCESS_DENIED; + } + + jn = talloc_zero(ctx, struct junction_map); + if (!jn) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (r->in.servername && r->in.sharename) { + altpath = talloc_asprintf(ctx, "%s\\%s", + r->in.servername, + r->in.sharename); + if (!altpath) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (!strlower_m(altpath)) { + return WERR_INVALID_PARAMETER; + } + DEBUG(5,("init_reply_dfs_remove: Request to remove %s -> %s\\%s.\n", + r->in.dfs_entry_path, r->in.servername, r->in.sharename)); + } + + while (IS_DIRECTORY_SEP(pathnamep[0]) && + IS_DIRECTORY_SEP(pathnamep[1])) { + pathnamep++; + } + + status = get_referred_path(ctx, + session_info, + pathnamep, + remote_address, + local_address, + jn, &consumedcnt, &self_ref); + if(!NT_STATUS_IS_OK(status)) { + return WERR_NERR_DFSNOSUCHVOLUME; + } + + /* if no server-share pair given, remove the msdfs link completely */ + if(!r->in.servername && !r->in.sharename) { + if(!remove_msdfs_link(jn, session_info)) { + return WERR_NERR_DFSNOSUCHVOLUME; + } + } else { + size_t i = 0; + /* compare each referral in the list with the one to remove */ + DBG_DEBUG("altpath: .%s. refcnt: %zu\n", + altpath, + jn->referral_count); + for(i=0;i<jn->referral_count;i++) { + char *refpath = talloc_strdup(ctx, + jn->referral_list[i].alternate_path); + if (!refpath) { + return WERR_NOT_ENOUGH_MEMORY; + } + trim_char(refpath, '\\', '\\'); + DEBUG(10,("_dfs_remove: refpath: .%s.\n", refpath)); + if(strequal(refpath, altpath)) { + *(jn->referral_list[i].alternate_path)='\0'; + DEBUG(10,("_dfs_remove: Removal request matches referral %s\n", + refpath)); + found = True; + } + } + + if(!found) { + return WERR_NERR_DFSNOSUCHSHARE; + } + + /* Only one referral, remove it */ + if(jn->referral_count == 1) { + if(!remove_msdfs_link(jn, session_info)) { + return WERR_NERR_DFSNOSUCHVOLUME; + } + } else { + if(!create_msdfs_link(jn, session_info)) { + return WERR_NERR_DFSCANTCREATEJUNCTIONPOINT; + } + } + } + + return WERR_OK; +} + +static bool init_reply_dfs_info_1(TALLOC_CTX *mem_ctx, struct junction_map* j,struct dfs_Info1* dfs1) +{ + dfs1->path = talloc_asprintf(mem_ctx, + "\\\\%s\\%s\\%s", lp_netbios_name(), + j->service_name, j->volume_name); + if (dfs1->path == NULL) + return False; + + DEBUG(5,("init_reply_dfs_info_1: initing entrypath: %s\n",dfs1->path)); + return True; +} + +static bool init_reply_dfs_info_2(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info2* dfs2) +{ + dfs2->path = talloc_asprintf(mem_ctx, + "\\\\%s\\%s\\%s", lp_netbios_name(), j->service_name, j->volume_name); + if (dfs2->path == NULL) + return False; + dfs2->comment = talloc_strdup(mem_ctx, j->comment); + dfs2->state = 1; /* set up state of dfs junction as OK */ + dfs2->num_stores = j->referral_count; + return True; +} + +static bool init_reply_dfs_info_3(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info3* dfs3) +{ + size_t ii; + if (j->volume_name[0] == '\0') + dfs3->path = talloc_asprintf(mem_ctx, "\\\\%s\\%s", + lp_netbios_name(), j->service_name); + else + dfs3->path = talloc_asprintf(mem_ctx, "\\\\%s\\%s\\%s", lp_netbios_name(), + j->service_name, j->volume_name); + + if (dfs3->path == NULL) + return False; + + dfs3->comment = talloc_strdup(mem_ctx, j->comment); + dfs3->state = 1; + dfs3->num_stores = j->referral_count; + + /* also enumerate the stores */ + if (j->referral_count) { + dfs3->stores = talloc_array(mem_ctx, struct dfs_StorageInfo, j->referral_count); + if (!dfs3->stores) + return False; + memset(dfs3->stores, '\0', j->referral_count * sizeof(struct dfs_StorageInfo)); + } else { + dfs3->stores = NULL; + } + + for(ii=0;ii<j->referral_count;ii++) { + char* p; + char *path = NULL; + struct dfs_StorageInfo* stor = &(dfs3->stores[ii]); + struct referral* ref = &(j->referral_list[ii]); + + path = talloc_strdup(mem_ctx, ref->alternate_path); + if (!path) { + return False; + } + trim_char(path,'\\','\0'); + p = strrchr_m(path,'\\'); + if(p==NULL) { + DEBUG(4,("init_reply_dfs_info_3: invalid path: no \\ found in %s\n",path)); + continue; + } + *p = '\0'; + DBG_INFO("storage %zu: %s.%s\n",ii,path,p+1); + stor->state = 2; /* set all stores as ONLINE */ + stor->server = talloc_strdup(mem_ctx, path); + stor->share = talloc_strdup(mem_ctx, p+1); + } + return True; +} + +static bool init_reply_dfs_info_100(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info100* dfs100) +{ + dfs100->comment = talloc_strdup(mem_ctx, j->comment); + return True; +} + +WERROR _dfs_Enum(struct pipes_struct *p, struct dfs_Enum *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct junction_map *jn = NULL; + size_t num_jn = 0; + size_t i; + TALLOC_CTX *ctx = talloc_tos(); + + jn = enum_msdfs_links(ctx, session_info, &num_jn); + if (!jn || num_jn == 0) { + num_jn = 0; + jn = NULL; + } + + DEBUG(5,("_dfs_Enum: %u junctions found in Dfs, doing level %d\n", + (unsigned int)num_jn, r->in.level)); + + *r->out.total = num_jn; + + /* Create the return array */ + switch (r->in.level) { + case 1: + if (num_jn) { + if ((r->out.info->e.info1->s = talloc_array(ctx, struct dfs_Info1, num_jn)) == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + r->out.info->e.info1->s = NULL; + } + r->out.info->e.info1->count = num_jn; + break; + case 2: + if (num_jn) { + if ((r->out.info->e.info2->s = talloc_array(ctx, struct dfs_Info2, num_jn)) == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + r->out.info->e.info2->s = NULL; + } + r->out.info->e.info2->count = num_jn; + break; + case 3: + if (num_jn) { + if ((r->out.info->e.info3->s = talloc_array(ctx, struct dfs_Info3, num_jn)) == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + r->out.info->e.info3->s = NULL; + } + r->out.info->e.info3->count = num_jn; + break; + default: + return WERR_INVALID_PARAMETER; + } + + for (i = 0; i < num_jn; i++) { + switch (r->in.level) { + case 1: + init_reply_dfs_info_1(ctx, &jn[i], &r->out.info->e.info1->s[i]); + break; + case 2: + init_reply_dfs_info_2(ctx, &jn[i], &r->out.info->e.info2->s[i]); + break; + case 3: + init_reply_dfs_info_3(ctx, &jn[i], &r->out.info->e.info3->s[i]); + break; + default: + return WERR_INVALID_PARAMETER; + } + } + + return WERR_OK; +} + +WERROR _dfs_GetInfo(struct pipes_struct *p, struct dfs_GetInfo *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + size_t consumedcnt = 0; + struct junction_map *jn = NULL; + bool self_ref = False; + TALLOC_CTX *ctx = talloc_tos(); + bool ret; + NTSTATUS status; + const char *pathnamep = r->in.dfs_entry_path; + + jn = talloc_zero(ctx, struct junction_map); + if (!jn) { + return WERR_NOT_ENOUGH_MEMORY; + } + + while (IS_DIRECTORY_SEP(pathnamep[0]) && + IS_DIRECTORY_SEP(pathnamep[1])) { + pathnamep++; + } + + ret = create_junction(ctx, pathnamep, jn); + if (!ret) { + return WERR_NERR_DFSNOSUCHSERVER; + } + + /* The following call can change the cwd. */ + status = get_referred_path(ctx, + session_info, + pathnamep, + remote_address, + local_address, + jn, &consumedcnt, &self_ref); + if(!NT_STATUS_IS_OK(status) || consumedcnt < strlen(pathnamep)) { + return WERR_NERR_DFSNOSUCHVOLUME; + } + + switch (r->in.level) { + case 1: + r->out.info->info1 = talloc_zero(ctx,struct dfs_Info1); + if (!r->out.info->info1) { + return WERR_NOT_ENOUGH_MEMORY; + } + ret = init_reply_dfs_info_1(ctx, jn, r->out.info->info1); + break; + case 2: + r->out.info->info2 = talloc_zero(ctx,struct dfs_Info2); + if (!r->out.info->info2) { + return WERR_NOT_ENOUGH_MEMORY; + } + ret = init_reply_dfs_info_2(ctx, jn, r->out.info->info2); + break; + case 3: + r->out.info->info3 = talloc_zero(ctx,struct dfs_Info3); + if (!r->out.info->info3) { + return WERR_NOT_ENOUGH_MEMORY; + } + ret = init_reply_dfs_info_3(ctx, jn, r->out.info->info3); + break; + case 100: + r->out.info->info100 = talloc_zero(ctx,struct dfs_Info100); + if (!r->out.info->info100) { + return WERR_NOT_ENOUGH_MEMORY; + } + ret = init_reply_dfs_info_100(ctx, jn, r->out.info->info100); + break; + default: + r->out.info->info1 = NULL; + return WERR_INVALID_PARAMETER; + } + + if (!ret) + return WERR_INVALID_PARAMETER; + + return WERR_OK; +} + +WERROR _dfs_SetInfo(struct pipes_struct *p, struct dfs_SetInfo *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_Rename(struct pipes_struct *p, struct dfs_Rename *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_Move(struct pipes_struct *p, struct dfs_Move *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_ManagerGetConfigInfo(struct pipes_struct *p, struct dfs_ManagerGetConfigInfo *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_ManagerSendSiteInfo(struct pipes_struct *p, struct dfs_ManagerSendSiteInfo *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_AddFtRoot(struct pipes_struct *p, struct dfs_AddFtRoot *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_RemoveFtRoot(struct pipes_struct *p, struct dfs_RemoveFtRoot *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_AddStdRoot(struct pipes_struct *p, struct dfs_AddStdRoot *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_RemoveStdRoot(struct pipes_struct *p, struct dfs_RemoveStdRoot *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_ManagerInitialize(struct pipes_struct *p, struct dfs_ManagerInitialize *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_AddStdRootForced(struct pipes_struct *p, struct dfs_AddStdRootForced *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_GetDcAddress(struct pipes_struct *p, struct dfs_GetDcAddress *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_SetDcAddress(struct pipes_struct *p, struct dfs_SetDcAddress *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_FlushFtTable(struct pipes_struct *p, struct dfs_FlushFtTable *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_Add2(struct pipes_struct *p, struct dfs_Add2 *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_Remove2(struct pipes_struct *p, struct dfs_Remove2 *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_EnumEx(struct pipes_struct *p, struct dfs_EnumEx *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _dfs_SetInfo2(struct pipes_struct *p, struct dfs_SetInfo2 *r) +{ + /* FIXME: Implement your code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_dfs_scompat.c" diff --git a/source3/rpc_server/dssetup/srv_dssetup_nt.c b/source3/rpc_server/dssetup/srv_dssetup_nt.c new file mode 100644 index 0000000..932452b --- /dev/null +++ b/source3/rpc_server/dssetup/srv_dssetup_nt.c @@ -0,0 +1,232 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997. + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997. + * Copyright (C) Paul Ashton 1997. + * Copyright (C) Jeremy Allison 2001. + * Copyright (C) Gerald Carter 2002. + * Copyright (C) Guenther Deschner 2008. + * + * 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 "librpc/gen_ndr/ndr_dssetup.h" +#include "librpc/gen_ndr/ndr_dssetup_scompat.h" +#include "secrets.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/******************************************************************** + Fill in a dssetup_DsRolePrimaryDomInfoBasic structure + ********************************************************************/ + +static WERROR fill_dsrole_dominfo_basic(TALLOC_CTX *ctx, + struct dssetup_DsRolePrimaryDomInfoBasic **info) +{ + struct dssetup_DsRolePrimaryDomInfoBasic *basic = NULL; + char *dnsdomain = NULL; + + DEBUG(10,("fill_dsrole_dominfo_basic: enter\n")); + + basic = talloc_zero(ctx, struct dssetup_DsRolePrimaryDomInfoBasic); + if (!basic) { + DEBUG(0,("fill_dsrole_dominfo_basic: out of memory\n")); + return WERR_NOT_ENOUGH_MEMORY; + } + + switch (lp_server_role()) { + case ROLE_STANDALONE: + basic->role = DS_ROLE_STANDALONE_SERVER; + basic->domain = get_global_sam_name(); + break; + case ROLE_DOMAIN_MEMBER: + basic->role = DS_ROLE_MEMBER_SERVER; + basic->domain = lp_workgroup(); + break; + case ROLE_DOMAIN_BDC: + basic->role = DS_ROLE_BACKUP_DC; + basic->domain = get_global_sam_name(); + break; + case ROLE_DOMAIN_PDC: + case ROLE_IPA_DC: + basic->role = DS_ROLE_PRIMARY_DC; + basic->domain = get_global_sam_name(); + break; + } + + if (secrets_fetch_domain_guid(lp_workgroup(), &basic->domain_guid)) { + basic->flags |= DS_ROLE_PRIMARY_DOMAIN_GUID_PRESENT; + } + + /* fill in some additional fields if we are a member of an AD domain */ + + if (lp_security() == SEC_ADS) { + dnsdomain = talloc_strdup(ctx, lp_realm()); + if (!dnsdomain) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (!strlower_m(dnsdomain)) { + return WERR_INVALID_PARAMETER; + } + basic->dns_domain = dnsdomain; + + /* FIXME!! We really should fill in the correct forest + name. Should get this information from winbindd. */ + basic->forest = dnsdomain; + } else { + /* security = domain should not fill in the dns or + forest name */ + basic->dns_domain = NULL; + basic->forest = NULL; + } + + *info = basic; + + return WERR_OK; +} + +/******************************************************************** + Implement the _dssetup_DsRoleGetPrimaryDomainInformation() call + ********************************************************************/ + +WERROR _dssetup_DsRoleGetPrimaryDomainInformation(struct pipes_struct *p, + struct dssetup_DsRoleGetPrimaryDomainInformation *r) +{ + WERROR werr = WERR_OK; + + switch (r->in.level) { + + case DS_ROLE_BASIC_INFORMATION: { + struct dssetup_DsRolePrimaryDomInfoBasic *basic = NULL; + werr = fill_dsrole_dominfo_basic(p->mem_ctx, &basic); + if (W_ERROR_IS_OK(werr)) { + r->out.info->basic = *basic; + } + break; + } + default: + DEBUG(0,("_dssetup_DsRoleGetPrimaryDomainInformation: " + "Unknown info level [%d]!\n", r->in.level)); + werr = WERR_INVALID_LEVEL; + } + + return werr; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleDnsNameToFlatName(struct pipes_struct *p, + struct dssetup_DsRoleDnsNameToFlatName *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleDcAsDc(struct pipes_struct *p, + struct dssetup_DsRoleDcAsDc *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleDcAsReplica(struct pipes_struct *p, + struct dssetup_DsRoleDcAsReplica *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleDemoteDc(struct pipes_struct *p, + struct dssetup_DsRoleDemoteDc *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleGetDcOperationProgress(struct pipes_struct *p, + struct dssetup_DsRoleGetDcOperationProgress *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleGetDcOperationResults(struct pipes_struct *p, + struct dssetup_DsRoleGetDcOperationResults *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleCancel(struct pipes_struct *p, + struct dssetup_DsRoleCancel *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleServerSaveStateForUpgrade(struct pipes_struct *p, + struct dssetup_DsRoleServerSaveStateForUpgrade *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleUpgradeDownlevelServer(struct pipes_struct *p, + struct dssetup_DsRoleUpgradeDownlevelServer *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _dssetup_DsRoleAbortDownlevelServerUpgrade(struct pipes_struct *p, + struct dssetup_DsRoleAbortDownlevelServerUpgrade *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_dssetup_scompat.c" diff --git a/source3/rpc_server/echo/srv_echo_nt.c b/source3/rpc_server/echo/srv_echo_nt.c new file mode 100644 index 0000000..d5b3ddc --- /dev/null +++ b/source3/rpc_server/echo/srv_echo_nt.c @@ -0,0 +1,126 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines for rpcecho + * Copyright (C) Tim Potter 2003 + * Copyright (C) Jelmer Vernooij 2006 + * Copyright (C) Gerald (Jerry) Carter 2007 + * + * 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/>. + */ + +/* This is the interface to the rpcecho pipe. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/gen_ndr/ndr_echo.h" +#include "librpc/gen_ndr/ndr_echo_scompat.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/* Add one to the input and return it */ + +void _echo_AddOne(struct pipes_struct *p, struct echo_AddOne *r ) +{ + DEBUG(10, ("_echo_AddOne\n")); + + *r->out.out_data = r->in.in_data + 1; +} + +/* Echo back an array of data */ + +void _echo_EchoData(struct pipes_struct *p, struct echo_EchoData *r) +{ + DEBUG(10, ("_echo_EchoData\n")); + + if ( r->in.len == 0 ) { + r->out.out_data = NULL; + return; + } + + r->out.out_data = talloc_array(p->mem_ctx, uint8_t, r->in.len); + memcpy( r->out.out_data, r->in.in_data, r->in.len ); + return; +} + +/* Sink an array of data */ + +void _echo_SinkData(struct pipes_struct *p, struct echo_SinkData *r) +{ + DEBUG(10, ("_echo_SinkData\n")); + + /* My that was some yummy data! */ + return; +} + +/* Source an array of data */ + +void _echo_SourceData(struct pipes_struct *p, struct echo_SourceData *r) +{ + uint32_t i; + + DEBUG(10, ("_echo_SourceData\n")); + + if ( r->in.len == 0 ) { + r->out.data = NULL; + return; + } + + r->out.data = talloc_array(p->mem_ctx, uint8_t, r->in.len ); + + for (i = 0; i < r->in.len; i++ ) { + r->out.data[i] = i & 0xff; + } + + return; +} + +void _echo_TestCall(struct pipes_struct *p, struct echo_TestCall *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return; +} + +NTSTATUS _echo_TestCall2(struct pipes_struct *p, struct echo_TestCall2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_OK; +} + +uint32_t _echo_TestSleep(struct pipes_struct *p, struct echo_TestSleep *r) +{ + smb_msleep(r->in.seconds * 1000); + return 0; +} + +void _echo_TestEnum(struct pipes_struct *p, struct echo_TestEnum *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return; +} + +void _echo_TestSurrounding(struct pipes_struct *p, struct echo_TestSurrounding *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return; +} + +uint16_t _echo_TestDoublePointer(struct pipes_struct *p, struct echo_TestDoublePointer *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return 0; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_echo_scompat.c" diff --git a/source3/rpc_server/epmapper/srv_epmapper.c b/source3/rpc_server/epmapper/srv_epmapper.c new file mode 100644 index 0000000..cf6b268 --- /dev/null +++ b/source3/rpc_server/epmapper/srv_epmapper.c @@ -0,0 +1,1063 @@ +/* + Unix SMB/CIFS implementation. + + Endpoint server for the epmapper pipe + + Copyright (C) 2010-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 "ntdomain.h" +#include "../libcli/security/security.h" +#include "../lib/tsocket/tsocket.h" +#include "auth.h" + +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_epmapper.h" +#include "librpc/gen_ndr/ndr_epmapper_scompat.h" +#include "rpc_server/rpc_server.h" +#include "lib/tdb_wrap/tdb_wrap.h" +#include "lib/util/util_tdb.h" +#include "lib/util/strv.h" + +static struct tdb_wrap *epmdb = NULL; + +/* handle types for this module */ +enum handle_types {HTYPE_LOOKUP}; + +typedef uint32_t error_status_t; + +/* An endpoint combined with an interface description */ +struct dcesrv_ep_iface { + const char *name; + struct ndr_syntax_id syntax_id; + struct epm_tower ep; +}; + +/* A rpc service interface like samr, lsarpc or netlogon */ +struct dcesrv_iface { + const char *name; + struct ndr_syntax_id syntax_id; +}; + +struct dcesrv_iface_list { + struct dcesrv_iface_list *next, *prev; + struct dcesrv_iface *iface; +}; + +/* + * An endpoint can serve multiple rpc services interfaces. + * For example \\pipe\netlogon can be used by lsarpc and netlogon. + */ +struct dcesrv_epm_endpoint { + struct dcesrv_epm_endpoint *next, *prev; + + /* The type and the location of the endpoint */ + struct dcerpc_binding *ep_description; + + /* A list of rpc services able to connect to the endpoint */ + struct dcesrv_iface_list *iface_list; +}; + +struct rpc_eps { + struct dcesrv_ep_iface *e; + uint32_t count; +}; + +struct build_ep_list_state { + const struct GUID *uuid; + const char *srv_addr; + TALLOC_CTX *mem_ctx; + struct dcesrv_ep_iface *ifaces; +}; + +static bool build_ep_list_fill_iface( + TALLOC_CTX *mem_ctx, + const struct ndr_syntax_id *syntax_id, + const char *endpoint, + const char *name, + const char *srv_addr, + struct dcesrv_ep_iface *dst) +{ + struct dcesrv_ep_iface iface = { + .syntax_id = *syntax_id, + }; + struct dcerpc_binding *binding = NULL; + enum dcerpc_transport_t transport; + char *name_dup = NULL; + const char *host_addr = NULL; + NTSTATUS status; + + /* copy without const for error path TALLOC_FREE */ + name_dup = talloc_strdup(mem_ctx, name); + if (name_dup == NULL) { + goto fail; + } + iface.name = name_dup; + + status = dcerpc_parse_binding(mem_ctx, endpoint, &binding); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_parse_binding failed: %s\n", + nt_errstr(status)); + goto fail; + } + + status = dcerpc_binding_set_abstract_syntax(binding, syntax_id); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_binding_set_abstract_syntax failed: %s\n", + nt_errstr(status)); + goto fail; + } + + transport = dcerpc_binding_get_transport(binding); + if (transport == NCACN_IP_TCP) { + const char *host = NULL; + + host = dcerpc_binding_get_string_option(binding, "host"); + if (host == NULL) { + host_addr = srv_addr; + } else if (!is_ipaddress_v4(host)) { + host_addr = srv_addr; + } else if (strcmp(host, "0.0.0.0") == 0) { + host_addr = srv_addr; + } + } + + if (host_addr != NULL) { + status = dcerpc_binding_set_string_option( + binding, "host", host_addr); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_binding_set_string_option " + "failed: %s\n", + nt_errstr(status)); + goto fail; + } + } + + status = dcerpc_binding_build_tower(mem_ctx, binding, &iface.ep); + TALLOC_FREE(binding); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_binding_build_tower failed: %s\n", + nt_errstr(status)); + goto fail; + } + + *dst = iface; + return true; + +fail: + TALLOC_FREE(binding); + TALLOC_FREE(name_dup); + TALLOC_FREE(iface.ep.floors); + return false; +} + +static int build_ep_list_fn( + struct tdb_context *tdb, + TDB_DATA key, + TDB_DATA value, + void *private_data) +{ + struct build_ep_list_state *state = private_data; + struct ndr_syntax_id syntax_id = { .if_version = 0 }; + const char *name = NULL; + char *endpoints = NULL; + const char *endpoint = NULL; + bool ok; + + if ((key.dsize == 0) || (key.dptr[key.dsize-1] != '\0') || + (value.dsize == 0) || (value.dptr[value.dsize-1] != '\0')) { + DBG_DEBUG("Invalid record\n"); + return 0; + } + + ok = ndr_syntax_id_from_string((char *)key.dptr, &syntax_id); + if (!ok) { + DBG_DEBUG("Invalid interface: %s\n", (char *)key.dptr); + return 0; + } + + endpoints = (char *)value.dptr; + endpoint = endpoints; + name = endpoints; + + while ((endpoint = strv_len_next(endpoints, value.dsize, endpoint))) { + size_t num_ifaces = talloc_array_length(state->ifaces); + struct dcesrv_ep_iface *tmp = NULL; + + if (num_ifaces+1 < num_ifaces) { + return 1; + } + + tmp = talloc_realloc( + state->mem_ctx, + state->ifaces, + struct dcesrv_ep_iface, + num_ifaces+1); + if (tmp == NULL) { + return 1; + } + state->ifaces = tmp; + + ok = build_ep_list_fill_iface( + state->ifaces, + &syntax_id, + endpoint, + name, + state->srv_addr, + &state->ifaces[num_ifaces]); + if (!ok) { + state->ifaces = talloc_realloc( + state->mem_ctx, + state->ifaces, + struct dcesrv_ep_iface, + num_ifaces); + } + } + + return 0; +} + +/* + * Build a list of all interfaces handled by all endpoint servers. + */ +static uint32_t build_ep_list(TALLOC_CTX *mem_ctx, + const struct GUID *uuid, + const char *srv_addr, + struct dcesrv_ep_iface **peps) +{ + struct build_ep_list_state state = { + .mem_ctx = mem_ctx, .uuid = uuid, .srv_addr = srv_addr, + }; + int ret; + + ret = tdb_traverse_read(epmdb->tdb, build_ep_list_fn, &state); + if (ret == -1) { + DBG_DEBUG("tdb_traverse_read failed\n"); + return 0; + } + + *peps = state.ifaces; + return talloc_array_length(*peps); +} + +/* + * epm_Insert + * + * Add the specified entries to an endpoint map. + */ +error_status_t _epm_Insert(struct pipes_struct *p, + struct epm_Insert *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return EPMAPPER_STATUS_CANT_PERFORM_OP; +} + +/* + * epm_Delete + * + * Delete the specified entries from an endpoint map. + */ +error_status_t _epm_Delete(struct pipes_struct *p, + struct epm_Delete *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return EPMAPPER_STATUS_CANT_PERFORM_OP; +} + +/* + * epm_Lookup + * + * Lookup entries in an endpoint map. + */ +error_status_t _epm_Lookup(struct pipes_struct *p, + struct epm_Lookup *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + struct policy_handle *entry_handle; + struct rpc_eps *eps; + TALLOC_CTX *tmp_ctx; + error_status_t rc; + uint32_t count = 0; + uint32_t num_ents = 0; + uint32_t i; + bool match = false; + bool ok; + NTSTATUS status; + + *r->out.num_ents = 0; + r->out.entries = NULL; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return EPMAPPER_STATUS_NO_MEMORY; + } + + DEBUG(5, ("_epm_Lookup: Trying to lookup max. %u entries.\n", + r->in.max_ents)); + + if (r->in.entry_handle == NULL || + ndr_policy_handle_empty(r->in.entry_handle)) { + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + char *srv_addr = NULL; + + DEBUG(7, ("_epm_Lookup: No entry_handle found, creating it.\n")); + + eps = talloc_zero(tmp_ctx, struct rpc_eps); + if (eps == NULL) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + + if (local_address != NULL && + tsocket_address_is_inet(local_address, "ipv4")) + { + srv_addr = tsocket_address_inet_addr_string( + local_address, tmp_ctx); + } + + switch (r->in.inquiry_type) { + case RPC_C_EP_ALL_ELTS: + /* + * Return all elements from the endpoint map. The + * interface_id, vers_option, and object parameters MUST + * be ignored. + */ + eps->count = build_ep_list(eps, + NULL, + srv_addr, + &eps->e); + break; + case RPC_C_EP_MATCH_BY_IF: + /* + * Return endpoint map elements that contain the + * interface identifier specified by the interface_id + * and vers_option values. + * + * RPC_C_EP_MATCH_BY_IF and RPC_C_EP_MATCH_BY_BOTH + * need both the same endpoint list. There is a second + * check for the inquiry_type below which differentiates + * between them. + */ + case RPC_C_EP_MATCH_BY_BOTH: + /* + * Return endpoint map elements that contain the + * interface identifier and object UUID specified by + * interface_id, vers_option, and object. + */ + eps->count = build_ep_list(eps, + &r->in.interface_id->uuid, + srv_addr, + &eps->e); + break; + case RPC_C_EP_MATCH_BY_OBJ: + /* + * Return endpoint map elements that contain the object + * UUID specified by object. + */ + eps->count = build_ep_list(eps, + r->in.object, + srv_addr, + &eps->e); + break; + default: + rc = EPMAPPER_STATUS_CANT_PERFORM_OP; + goto done; + } + + if (eps->count == 0) { + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + ok = create_policy_hnd(p, r->out.entry_handle, HTYPE_LOOKUP, eps); + if (!ok) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + + eps = find_policy_by_hnd(p, + r->out.entry_handle, + HTYPE_LOOKUP, + struct rpc_eps, + &status); + if (!NT_STATUS_IS_OK(status)) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + entry_handle = r->out.entry_handle; + } else { + DEBUG(7, ("_epm_Lookup: Trying to find entry_handle.\n")); + + eps = find_policy_by_hnd(p, + r->in.entry_handle, + HTYPE_LOOKUP, + struct rpc_eps, + &status); + if (!NT_STATUS_IS_OK(status)) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + entry_handle = r->in.entry_handle; + } + + if (eps == NULL || eps->e == NULL) { + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + /* return the next N elements */ + count = r->in.max_ents; + if (count > eps->count) { + count = eps->count; + } + + DEBUG(5, ("_epm_Lookup: Find %u entries\n", count)); + + if (count == 0) { + close_policy_hnd(p, entry_handle); + ZERO_STRUCTP(r->out.entry_handle); + + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + r->out.entries = talloc_array(p->mem_ctx, struct epm_entry_t, count); + if (r->out.entries == NULL) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + + for (i = 0; i < count; i++) { + match = false; + + switch (r->in.inquiry_type) { + case RPC_C_EP_ALL_ELTS: + /* + * Return all elements from the endpoint map. The + * interface_id, vers_option, and object parameters MUST + * be ignored. + */ + match = true; + break; + case RPC_C_EP_MATCH_BY_IF: + /* + * Return endpoint map elements that contain the + * interface identifier specified by the interface_id + * and vers_option values. + */ + if (GUID_equal(&r->in.interface_id->uuid, + &eps->e[i].syntax_id.uuid)) { + match = true; + } + break; + case RPC_C_EP_MATCH_BY_OBJ: + /* + * Return endpoint map elements that contain the object + * UUID specified by object. + */ + if (GUID_equal(r->in.object, + &eps->e[i].syntax_id.uuid)) { + match = true; + } + break; + case RPC_C_EP_MATCH_BY_BOTH: + /* + * Return endpoint map elements that contain the + * interface identifier and object UUID specified by + * interface_id, vers_option, and object. + */ + if (GUID_equal(&r->in.interface_id->uuid, + &eps->e[i].syntax_id.uuid) && + GUID_equal(r->in.object, &eps->e[i].syntax_id.uuid)) { + match = true; + } + break; + default: + return EPMAPPER_STATUS_CANT_PERFORM_OP; + } + + if (match) { + if (r->in.inquiry_type == RPC_C_EP_MATCH_BY_IF || + r->in.inquiry_type == RPC_C_EP_MATCH_BY_OBJ) { + /* Check interface version */ + + match = false; + switch (r->in.vers_option) { + case RPC_C_VERS_ALL: + /* + * Return endpoint map elements that + * contain the specified interface UUID, + * regardless of the version numbers. + */ + match = true; + break; + case RPC_C_VERS_COMPATIBLE: + /* + * Return the endpoint map elements that + * contain the same major versions of + * the specified interface UUID and a + * minor version greater than or equal + * to the minor version of the specified + * UUID. + */ + if (r->in.interface_id->vers_major == + (eps->e[i].syntax_id.if_version >> 16) && + r->in.interface_id->vers_minor <= + (eps->e[i].syntax_id.if_version & 0xFFFF)) { + match = true; + } + break; + case RPC_C_VERS_EXACT: + /* + * Return endpoint map elements that + * contain the specified version of the + * specified interface UUID. + */ + if (r->in.interface_id->vers_major == + (eps->e[i].syntax_id.if_version >> 16) && + r->in.interface_id->vers_minor == + (eps->e[i].syntax_id.if_version & 0xFFFF)) { + match = true; + } + match = true; + break; + case RPC_C_VERS_MAJOR_ONLY: + /* + * Return endpoint map elements that + * contain the same version of the + * specified interface UUID and ignore + * the minor version. + */ + if (r->in.interface_id->vers_major == + (eps->e[i].syntax_id.if_version >> 16)) { + match = true; + } + match = true; + break; + case RPC_C_VERS_UPTO: + /* + * Return endpoint map elements that + * contain a version of the specified + * interface UUID less than or equal to + * the specified major and minor + * version. + */ + if (r->in.interface_id->vers_major > + eps->e[i].syntax_id.if_version >> 16) { + match = true; + } else { + if (r->in.interface_id->vers_major == + (eps->e[i].syntax_id.if_version >> 16) && + r->in.interface_id->vers_minor >= + (eps->e[i].syntax_id.if_version & 0xFFFF)) { + match = true; + } + } + break; + default: + return EPMAPPER_STATUS_CANT_PERFORM_OP; + } + } + } + + if (match) { + ZERO_STRUCT(r->out.entries[num_ents].object); + + DEBUG(10, ("_epm_Lookup: Adding tower for '%s'\n", + eps->e[i].name)); + r->out.entries[num_ents].annotation = talloc_strdup(r->out.entries, + eps->e[i].name); + r->out.entries[num_ents].tower = talloc(r->out.entries, + struct epm_twr_t); + if (r->out.entries[num_ents].tower == NULL) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + r->out.entries[num_ents].tower->tower.floors = talloc_move(r->out.entries[num_ents].tower, &eps->e[i].ep.floors); + r->out.entries[num_ents].tower->tower.num_floors = eps->e[i].ep.num_floors; + r->out.entries[num_ents].tower->tower_length = 0; + + num_ents++; + } + } /* end for loop */ + + *r->out.num_ents = num_ents; + + eps->count -= count; + eps->e += count; + if (eps->count == 0) { + close_policy_hnd(p, entry_handle); + ZERO_STRUCTP(r->out.entry_handle); + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + rc = EPMAPPER_STATUS_OK; +done: + talloc_free(tmp_ctx); + + return rc; +} + +static struct rpc_eps *epm_map_get_towers( + TALLOC_CTX *mem_ctx, + const struct ndr_syntax_id *iface, + enum dcerpc_transport_t transport, + const char *local_address) +{ + struct ndr_syntax_id_buf idbuf; + char *iface_string = ndr_syntax_id_buf_string(iface, &idbuf); + struct rpc_eps *eps = NULL; + uint8_t *buf = NULL; + size_t buflen; + char *bindings = NULL; + char *binding = NULL; + char *name = NULL; + NTSTATUS status; + int ret; + + DBG_DEBUG("Mapping interface %s\n", iface_string); + + eps = talloc_zero(mem_ctx, struct rpc_eps); + if (eps == NULL) { + goto fail; + } + + ret = tdb_fetch_talloc( + epmdb->tdb, string_term_tdb_data(iface_string), eps, &buf); + if (ret != 0) { + DBG_DEBUG("Could not find epm entry for %s: %s\n", + iface_string, + strerror(ret)); + goto fail; + } + buflen = talloc_array_length(buf); + + if ((buflen < 1) || (buf[buflen-1] != '\0')) { + DBG_DEBUG("epm entry for %s invalid\n", iface_string); + goto fail; + } + bindings = (char *)buf; + + name = bindings; /* name comes first */ + binding = name; /* strv_next will skip name */ + + while ((binding = strv_next(bindings, binding)) != NULL) { + struct dcerpc_binding *b = NULL; + enum dcerpc_transport_t found_transport; + struct dcesrv_ep_iface *tmp = NULL, *new_ep = NULL; + + DBG_DEBUG("Found %s for %s\n", binding, name); + + status = dcerpc_parse_binding(mem_ctx, binding, &b); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_parse_binding() for %s failed: %s\n", + binding, + nt_errstr(status)); + goto fail; + } + + found_transport = dcerpc_binding_get_transport(b); + if (found_transport != transport) { + DBG_DEBUG("Transport %d does not match %d\n", + (int)found_transport, + (int)transport); + TALLOC_FREE(b); + continue; + } + + if (found_transport == NCACN_IP_TCP) { + status = dcerpc_binding_set_string_option( + b, "host", local_address); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not set host: %s\n", + nt_errstr(status)); + goto fail; + } + } + + status = dcerpc_binding_set_abstract_syntax(b, iface); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not set abstract syntax: %s\n", + nt_errstr(status)); + goto fail; + } + + tmp = talloc_realloc( + eps, + eps->e, + struct dcesrv_ep_iface, + eps->count+1); + if (tmp == NULL) { + goto fail; + } + eps->e = tmp; + + new_ep = &eps->e[eps->count]; + + new_ep->name = talloc_strdup(eps->e, name); + if (new_ep->name == NULL) { + goto fail; + } + new_ep->syntax_id = *iface; + + status = dcerpc_binding_build_tower(eps->e, b, &new_ep->ep); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_binding_build_tower failed: %s\n", + nt_errstr(status)); + goto fail; + } + + eps->count += 1; + + TALLOC_FREE(b); + } + return eps; + +fail: + TALLOC_FREE(eps); + return NULL; +} + +/* + * epm_Map + * + * Apply some algorithm (using the fields in the map_tower) to an endpoint map + * to produce a list of protocol towers. + */ +error_status_t _epm_Map(struct pipes_struct *p, + struct epm_Map *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + struct policy_handle *entry_handle; + enum dcerpc_transport_t transport; + struct ndr_syntax_id ifid; + struct epm_floor *floors; + struct rpc_eps *eps; + TALLOC_CTX *tmp_ctx; + error_status_t rc; + uint32_t count = 0; + uint32_t num_towers = 0; + uint32_t i; + bool ok; + NTSTATUS status; + + *r->out.num_towers = 0; + r->out.towers = NULL; + + if (r->in.map_tower == NULL || r->in.max_towers == 0 || + r->in.map_tower->tower.num_floors < 3) { + return EPMAPPER_STATUS_NO_MORE_ENTRIES; + } + + tmp_ctx = talloc_stackframe(); + + ZERO_STRUCTP(r->out.entry_handle); + + DEBUG(5, ("_epm_Map: Trying to map max. %u towers.\n", + r->in.max_towers)); + + /* + * A tower has normally up to 6 floors + * + * +-----------------------------------------------------------------+ + * | Floor 1 | Provides the RPC interface identifier. (e.g. UUID for | + * | | netlogon) | + * +---------+-------------------------------------------------------+ + * | Floor 2 | Transfer syntax (NDR encoded) | + * +---------+-------------------------------------------------------+ + * | Floor 3 | RPC protocol identifier (ncacn_tcp_ip, ncacn_np, ...) | + * +---------+-------------------------------------------------------+ + * | Floor 4 | Port address (e.g. TCP Port: 49156) | + * +---------+-------------------------------------------------------+ + * | Floor 5 | Transport (e.g. IP:192.168.51.10) | + * +---------+-------------------------------------------------------+ + * | Floor 6 | Routing | + * +---------+-------------------------------------------------------+ + */ + floors = r->in.map_tower->tower.floors; + + /* We accept NDR as the transfer syntax */ + status = dcerpc_floor_get_uuid_full(&floors[1], &ifid); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_floor_get_uuid_full() failed: %s\n", + nt_errstr(status)); + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + if (floors[1].lhs.protocol != EPM_PROTOCOL_UUID || + !ndr_syntax_id_equal(&ifid, &ndr_transfer_syntax_ndr)) { + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + /* We only talk to sane transports */ + transport = dcerpc_transport_by_tower(&r->in.map_tower->tower); + if (transport == NCA_UNKNOWN) { + DEBUG(2, ("epm_Map: Client requested unknown transport with " + "levels: ")); + for (i = 2; i < r->in.map_tower->tower.num_floors; i++) { + DEBUG(2, ("%d, ", r->in.map_tower->tower.floors[i].lhs.protocol)); + } + DEBUG(2, ("\n")); + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + if (r->in.entry_handle == NULL || + ndr_policy_handle_empty(r->in.entry_handle)) { + const struct tsocket_address *local_addr = + dcesrv_connection_get_local_address(dcesrv_conn); + char *local_address = NULL; + struct ndr_syntax_id_buf buf; + char *if_string = NULL; + + DEBUG(7, ("_epm_Map: No entry_handle found, creating it.\n")); + + status = dcerpc_floor_get_uuid_full(&floors[0], &ifid); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_floor_get_uuid_full() failed: %s\n", + nt_errstr(status)); + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + if_string = ndr_syntax_id_buf_string(&ifid, &buf); + + DBG_INFO("Mapping interface %s\n", if_string); + + if ((transport == NCACN_IP_TCP) && + tsocket_address_is_inet(local_addr, "ip")) { + /* + * We don't have the host ip in the epm + * database. For NCACN_IP_TCP, add the IP that + * the client connected to. + */ + local_address = tsocket_address_inet_addr_string( + local_addr, tmp_ctx); + } + + eps = epm_map_get_towers( + tmp_ctx, &ifid, transport, local_address); + if (eps == NULL) { + DBG_DEBUG("No bindings found\n"); + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + ok = create_policy_hnd(p, r->out.entry_handle, HTYPE_LOOKUP, eps); + if (!ok) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + + eps = find_policy_by_hnd(p, + r->out.entry_handle, + HTYPE_LOOKUP, + struct rpc_eps, + &status); + if (!NT_STATUS_IS_OK(status)) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + entry_handle = r->out.entry_handle; + } else { + DEBUG(7, ("_epm_Map: Trying to find entry_handle.\n")); + + eps = find_policy_by_hnd(p, + r->in.entry_handle, + HTYPE_LOOKUP, + struct rpc_eps, + &status); + if (!NT_STATUS_IS_OK(status)) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + entry_handle = r->in.entry_handle; + } + + if (eps == NULL || eps->e == NULL) { + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + /* return the next N elements */ + count = r->in.max_towers; + if (count > eps->count) { + count = eps->count; + } + + if (count == 0) { + close_policy_hnd(p, entry_handle); + ZERO_STRUCTP(r->out.entry_handle); + + rc = EPMAPPER_STATUS_NO_MORE_ENTRIES; + goto done; + } + + r->out.towers = talloc_array(p->mem_ctx, struct epm_twr_p_t, count); + if (r->out.towers == NULL) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + + for (i = 0; i < count; i++) { + DEBUG(7, ("_epm_Map: Map tower for '%s'\n", + eps->e[i].name)); + + r->out.towers[num_towers].twr = talloc(r->out.towers, + struct epm_twr_t); + if (r->out.towers[num_towers].twr == NULL) { + rc = EPMAPPER_STATUS_NO_MEMORY; + goto done; + } + r->out.towers[num_towers].twr->tower.floors = talloc_move(r->out.towers[num_towers].twr, &eps->e[i].ep.floors); + r->out.towers[num_towers].twr->tower.num_floors = eps->e[i].ep.num_floors; + r->out.towers[num_towers].twr->tower_length = 0; + + num_towers++; + } + + *r->out.num_towers = num_towers; + + eps->count -= count; + eps->e += count; + if (eps->count == 0) { + close_policy_hnd(p, entry_handle); + ZERO_STRUCTP(r->out.entry_handle); + } + + rc = EPMAPPER_STATUS_OK; +done: + talloc_free(tmp_ctx); + + return rc; +} + +/* + * epm_LookupHandleFree + */ +error_status_t _epm_LookupHandleFree(struct pipes_struct *p, + struct epm_LookupHandleFree *r) +{ + if (r->in.entry_handle == NULL) { + return EPMAPPER_STATUS_OK; + } + + if (is_valid_policy_hnd(r->in.entry_handle)) { + close_policy_hnd(p, r->in.entry_handle); + } + + r->out.entry_handle = r->in.entry_handle; + + return EPMAPPER_STATUS_OK; +} + + +/* + * epm_InqObject + * + * A client implementation SHOULD NOT call this method. These extensions do not + * provide an alternative method. + */ +error_status_t _epm_InqObject(struct pipes_struct *p, + struct epm_InqObject *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return EPMAPPER_STATUS_CANT_PERFORM_OP; +} + + +/* + * epm_MgmtDelete + * + * A client implementation SHOULD NOT call this method. These extensions do not + * provide an alternative method. +*/ +error_status_t _epm_MgmtDelete(struct pipes_struct *p, + struct epm_MgmtDelete *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return EPMAPPER_STATUS_CANT_PERFORM_OP; +} + + +/* + epm_MapAuth +*/ +error_status_t _epm_MapAuth(struct pipes_struct *p, + struct epm_MapAuth *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return EPMAPPER_STATUS_CANT_PERFORM_OP; +} + +static NTSTATUS epmapper__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_EPMAPPER_SHUTDOWN_SERVER \ + epmapper_shutdown_server + +static NTSTATUS epmapper_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + return epmapper__op_shutdown_server(dce_ctx, ep_server); +} + +static NTSTATUS epmapper__op_init_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +static NTSTATUS epmapper_init_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + char *epmdb_path = NULL; + NTSTATUS status; + + epmdb_path = lock_path(dce_ctx, "epmdb.tdb"); + if (epmdb_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + + epmdb = tdb_wrap_open( + dce_ctx, + epmdb_path, + 0, + TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH, + O_RDONLY, + 0644); + if (epmdb == NULL) { + DBG_DEBUG("Could not open epmdb.tdb: %s\n", strerror(errno)); + return map_nt_error_from_unix(errno); + } + TALLOC_FREE(epmdb_path); + + status = epmapper__op_init_server(dce_ctx, ep_server); + return status; +} + +#define DCESRV_INTERFACE_EPMAPPER_INIT_SERVER epmapper_init_server + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_epmapper_scompat.c" + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/eventlog/srv_eventlog_nt.c b/source3/rpc_server/eventlog/srv_eventlog_nt.c new file mode 100644 index 0000000..a946c57 --- /dev/null +++ b/source3/rpc_server/eventlog/srv_eventlog_nt.c @@ -0,0 +1,1047 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Marcin Krzysztof Porwit 2005, + * Copyright (C) Brian Moran 2005, + * Copyright (C) Gerald (Jerry) Carter 2005. + * Copyright (C) Guenther Deschner 2009. + * + * 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 "system/passwd.h" /* uid_wrapper */ +#include "ntdomain.h" +#include "lib/eventlog/eventlog.h" +#include "../libcli/security/security.h" +#include "../librpc/gen_ndr/ndr_winreg_c.h" +#include "rpc_client/cli_winreg_int.h" +#include "rpc_client/cli_winreg.h" +#include "smbd/smbd.h" +#include "auth.h" +#include "util_tdb.h" + +#include "rpc_server/rpc_server.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_eventlog_scompat.h" +#include "rpc_server/eventlog/srv_eventlog_reg.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define TOP_LEVEL_EVENTLOG_KEY "SYSTEM\\CurrentControlSet\\Services\\Eventlog" + +typedef struct { + char *logname; + ELOG_TDB *etdb; + uint32_t current_record; + uint32_t num_records; + uint32_t oldest_entry; + uint32_t flags; + uint32_t access_granted; +} EVENTLOG_INFO; + +/******************************************************************** + ********************************************************************/ + +static int eventlog_info_destructor(EVENTLOG_INFO *elog) +{ + if (elog->etdb) { + elog_close_tdb(elog->etdb, false); + } + return 0; +} + +/******************************************************************** + ********************************************************************/ + +static EVENTLOG_INFO *find_eventlog_info_by_hnd( struct pipes_struct * p, + struct policy_handle * handle ) +{ + EVENTLOG_INFO *info; + NTSTATUS status; + + info = find_policy_by_hnd(p, + handle, + DCESRV_HANDLE_ANY, + EVENTLOG_INFO, + &status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG( 2, + ( "find_eventlog_info_by_hnd: eventlog not found.\n" ) ); + return NULL; + } + + return info; +} + +/******************************************************************** + Pull the NT ACL from a file on disk or the OpenEventlog() access + check. Caller is responsible for freeing the returned security + descriptor via TALLOC_FREE(). This is designed for dealing with + user space access checks in smbd outside of the VFS. For example, + checking access rights in OpenEventlog() or from python. + +********************************************************************/ + +static NTSTATUS get_nt_acl_no_snum(TALLOC_CTX *ctx, + struct auth_session_info *session_info, + const char *fname, + uint32_t security_info_wanted, + struct security_descriptor **sd) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct conn_struct_tos *c = NULL; + NTSTATUS status = NT_STATUS_OK; + struct smb_filename *pathref_fname = NULL; + + if (!posix_locking_init(false)) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = create_conn_struct_tos(global_messaging_context(), + -1, + "/", + session_info, + &c); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("create_conn_struct_tos() returned %s.\n", + nt_errstr(status)); + TALLOC_FREE(frame); + return status; + } + + status = synthetic_pathref(talloc_tos(), + c->conn->cwd_fsp, + fname, + NULL, + NULL, + 0, + 0, + &pathref_fname); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("synthetic_pathref for file %s returned %s.\n", + fname, nt_errstr(status)); + TALLOC_FREE(frame); + return status; + } + status = SMB_VFS_FGET_NT_ACL(pathref_fname->fsp, + security_info_wanted, + ctx, + sd); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("SMB_VFS_FGET_NT_ACL for file %s returned %s.\n", + fname, nt_errstr(status)); + } + + TALLOC_FREE(frame); + + return status; +} + +/******************************************************************** +********************************************************************/ + +static bool elog_check_access(EVENTLOG_INFO *info, + struct auth_session_info *session_info) +{ + const struct security_token *token = session_info->security_token; + char *tdbname = elog_tdbname(talloc_tos(), info->logname ); + struct security_descriptor *sec_desc; + struct security_ace *ace; + NTSTATUS status; + + if ( !tdbname ) + return False; + + /* get the security descriptor for the file */ + + status = get_nt_acl_no_snum( info, + session_info, + tdbname, + SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL, + &sec_desc); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5,("elog_check_access: Unable to get NT ACL for %s: %s\n", + tdbname, nt_errstr(status))); + TALLOC_FREE(tdbname); + return False; + } + TALLOC_FREE(tdbname); + + ace = talloc_zero(sec_desc, struct security_ace); + if (ace == NULL) { + TALLOC_FREE(sec_desc); + return false; + } + + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->flags = 0; + ace->access_mask = REG_KEY_ALL; + ace->trustee = global_sid_System; + + status = security_descriptor_dacl_add(sec_desc, ace); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sec_desc); + return false; + } + + /* root free pass */ + + if ( geteuid() == sec_initial_uid() ) { + DEBUG(5,("elog_check_access: running as root, using system token\n")); + token = get_system_token(); + } + + /* run the check, try for the max allowed */ + + status = se_access_check( sec_desc, token, MAXIMUM_ALLOWED_ACCESS, + &info->access_granted); + + TALLOC_FREE(sec_desc); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(8,("elog_check_access: se_access_check() return %s\n", + nt_errstr(status))); + return False; + } + + /* we have to have READ permission for a successful open */ + + return ( info->access_granted & SEC_FILE_READ_DATA ); +} + +/******************************************************************** + ********************************************************************/ + +static bool elog_validate_logname( const char *name ) +{ + int i; + const char **elogs = lp_eventlog_list(); + + if (!elogs) { + return False; + } + + for ( i=0; elogs[i]; i++ ) { + if ( strequal( name, elogs[i] ) ) + return True; + } + + return False; +} + +/******************************************************************** +********************************************************************/ + +static bool get_num_records_hook( EVENTLOG_INFO * info ) +{ + int next_record; + int oldest_record; + + if ( !info->etdb ) { + DEBUG( 10, ( "No open tdb for %s\n", info->logname ) ); + return False; + } + + /* lock the tdb since we have to get 2 records */ + + tdb_lock_bystring_with_timeout( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD, 1 ); + next_record = tdb_fetch_int32( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD); + oldest_record = tdb_fetch_int32( ELOG_TDB_CTX(info->etdb), EVT_OLDEST_ENTRY); + tdb_unlock_bystring( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD); + + DEBUG( 8, + ( "Oldest Record %d; Next Record %d\n", oldest_record, + next_record ) ); + + info->num_records = ( next_record - oldest_record ); + info->oldest_entry = oldest_record; + + return True; +} + +/******************************************************************** + ********************************************************************/ + +static bool get_oldest_entry_hook( EVENTLOG_INFO * info ) +{ + /* it's the same thing */ + return get_num_records_hook( info ); +} + +/******************************************************************** + ********************************************************************/ + +static NTSTATUS elog_open( struct pipes_struct * p, const char *logname, struct policy_handle *hnd ) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + EVENTLOG_INFO *elog; + + /* first thing is to validate the eventlog name */ + + if ( !elog_validate_logname( logname ) ) + return NT_STATUS_OBJECT_PATH_INVALID; + + if ( !(elog = talloc_zero( NULL, EVENTLOG_INFO )) ) + return NT_STATUS_NO_MEMORY; + talloc_set_destructor(elog, eventlog_info_destructor); + + elog->logname = talloc_strdup( elog, logname ); + + /* Open the tdb first (so that we can create any new tdbs if necessary). + We have to do this as root and then use an internal access check + on the file permissions since you can only have a tdb open once + in a single process */ + + become_root(); + elog->etdb = elog_open_tdb( elog->logname, False, False ); + unbecome_root(); + + if ( !elog->etdb ) { + /* according to MSDN, if the logfile cannot be found, we should + default to the "Application" log */ + + if ( !strequal( logname, ELOG_APPL ) ) { + + TALLOC_FREE( elog->logname ); + + elog->logname = talloc_strdup( elog, ELOG_APPL ); + + /* do the access check */ + if ( !elog_check_access( elog, session_info) ) { + TALLOC_FREE( elog ); + return NT_STATUS_ACCESS_DENIED; + } + + become_root(); + elog->etdb = elog_open_tdb( elog->logname, False, False ); + unbecome_root(); + } + + if ( !elog->etdb ) { + TALLOC_FREE( elog ); + return NT_STATUS_ACCESS_DENIED; /* ??? */ + } + } + + /* now do the access check. Close the tdb if we fail here */ + + if ( !elog_check_access( elog, session_info) ) { + TALLOC_FREE( elog ); + return NT_STATUS_ACCESS_DENIED; + } + + /* create the policy handle */ + + if ( !create_policy_hnd( p, hnd, 0, elog ) ) { + TALLOC_FREE(elog); + return NT_STATUS_NO_MEMORY; + } + + /* set the initial current_record pointer */ + + if ( !get_oldest_entry_hook( elog ) ) { + DEBUG(3,("elog_open: Successfully opened eventlog but can't " + "get any information on internal records!\n")); + } + + elog->current_record = elog->oldest_entry; + + return NT_STATUS_OK; +} + +/******************************************************************** + ********************************************************************/ + +static NTSTATUS elog_close( struct pipes_struct *p, struct policy_handle *hnd ) +{ + if ( !( close_policy_hnd( p, hnd ) ) ) { + return NT_STATUS_INVALID_HANDLE; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + *******************************************************************/ + +static int elog_size( EVENTLOG_INFO *info ) +{ + if ( !info || !info->etdb ) { + DEBUG(0,("elog_size: Invalid info* structure!\n")); + return 0; + } + + return elog_tdb_size( ELOG_TDB_CTX(info->etdb), NULL, NULL ); +} + +/******************************************************************** + note that this can only be called AFTER the table is constructed, + since it uses the table to find the tdb handle + ********************************************************************/ + +static bool sync_eventlog_params(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + EVENTLOG_INFO *info) +{ + struct dcerpc_binding_handle *h = NULL; + uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + struct policy_handle hive_hnd, key_hnd; + uint32_t uiMaxSize = 0; + uint32_t uiRetention = 0; + char *path = NULL; + NTSTATUS status; + WERROR wresult = WERR_OK; + char *elogname = info->logname; + TALLOC_CTX *ctx; + bool ret = false; + + ctx = talloc_stackframe(); + if (ctx == NULL) { + return false; + } + + DEBUG( 4, ( "sync_eventlog_params with %s\n", elogname ) ); + + if ( !info->etdb ) { + DEBUG( 4, ( "No open tdb! (%s)\n", info->logname ) ); + goto done; + } + /* set reasonable defaults. 512Kb on size and 1 week on time */ + + uiMaxSize = 0x80000; + uiRetention = 604800; + + /* the general idea is to internally open the registry + key and retrieve the values. That way we can continue + to use the same fetch/store api that we use in + srv_reg_nt.c */ + path = talloc_asprintf(ctx, "%s\\%s", TOP_LEVEL_EVENTLOG_KEY, elogname); + if (!path) { + goto done; + } + + status = dcerpc_winreg_int_hklm_openkey(ctx, + get_session_info_system(), + msg_ctx, + &h, + path, + false, + access_mask, + &hive_hnd, + &key_hnd, + &wresult); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(4,("sync_eventlog_params: Failed to open key [%s] (%s)\n", + path, nt_errstr(status))); + goto done; + } + if ( !W_ERROR_IS_OK( wresult ) ) { + DEBUG( 4, + ( "sync_eventlog_params: Failed to open key [%s] (%s)\n", + path, win_errstr( wresult ) ) ); + goto done; + } + + status = dcerpc_winreg_query_dword(ctx, + h, + &key_hnd, + "Retention", + &uiRetention, + &wresult); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(4, ("Failed to query value \"Retention\": %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(wresult)) { + DEBUG(4, ("Failed to query value \"Retention\": %s\n", + win_errstr(wresult))); + goto done; + } + + status = dcerpc_winreg_query_dword(ctx, + h, + &key_hnd, + "MaxSize", + &uiMaxSize, + &wresult); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(4, ("Failed to query value \"Retention\": %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(wresult)) { + DEBUG(4, ("Failed to query value \"MaxSize\": %s\n", + win_errstr(wresult))); + goto done; + } + + tdb_store_int32( ELOG_TDB_CTX(info->etdb), EVT_MAXSIZE, uiMaxSize ); + tdb_store_int32( ELOG_TDB_CTX(info->etdb), EVT_RETENTION, uiRetention ); + + ret = true; + +done: + if (h != NULL) { + WERROR ignore; + + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, ctx, &key_hnd, &ignore); + } + if (is_valid_policy_hnd(&hive_hnd)) { + dcerpc_winreg_CloseKey(h, ctx, &hive_hnd, &ignore); + } + } + + TALLOC_FREE(ctx); + return ret; +} + +/******************************************************************** + _eventlog_OpenEventLogW + ********************************************************************/ + +NTSTATUS _eventlog_OpenEventLogW(struct pipes_struct *p, + struct eventlog_OpenEventLogW *r) +{ + EVENTLOG_INFO *info; + NTSTATUS result; + + DEBUG( 10,("_eventlog_OpenEventLogW: Server [%s], Log [%s]\n", + r->in.servername->string, r->in.logname->string )); + + /* according to MSDN, if the logfile cannot be found, we should + default to the "Application" log */ + + if ( !NT_STATUS_IS_OK( result = elog_open( p, r->in.logname->string, r->out.handle )) ) + return result; + + if ( !(info = find_eventlog_info_by_hnd( p, r->out.handle )) ) { + DEBUG(0,("_eventlog_OpenEventLogW: eventlog (%s) opened but unable to find handle!\n", + r->in.logname->string )); + elog_close( p, r->out.handle ); + return NT_STATUS_INVALID_HANDLE; + } + + DEBUG(10,("_eventlog_OpenEventLogW: Size [%d]\n", elog_size( info ))); + + if (!sync_eventlog_params(p->mem_ctx, + p->msg_ctx, + info)) { + elog_close(p, r->out.handle); + return NT_STATUS_EVENTLOG_FILE_CORRUPT; + } + prune_eventlog( ELOG_TDB_CTX(info->etdb) ); + + return NT_STATUS_OK; +} + +/******************************************************************** + _eventlog_ClearEventLogW + This call still needs some work + ********************************************************************/ +/** The windows client seems to be doing something funny with the file name + A call like + ClearEventLog(handle, "backup_file") + on the client side will result in the backup file name looking like this on the + server side: + \??\${CWD of client}\backup_file + If an absolute path gets specified, such as + ClearEventLog(handle, "C:\\temp\\backup_file") + then it is still mangled by the client into this: + \??\C:\temp\backup_file + when it is on the wire. + I'm not sure where the \?? is coming from, or why the ${CWD} of the client process + would be added in given that the backup file gets written on the server side. */ + +NTSTATUS _eventlog_ClearEventLogW(struct pipes_struct *p, + struct eventlog_ClearEventLogW *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle ); + + if ( !info ) + return NT_STATUS_INVALID_HANDLE; + + if (r->in.backupfile && r->in.backupfile->string) { + + DEBUG(8,( "_eventlog_ClearEventLogW: Using [%s] as the backup " + "file name for log [%s].\n", + r->in.backupfile->string, info->logname ) ); + } + + /* check for WRITE access to the file */ + + if ( !(info->access_granted & SEC_FILE_WRITE_DATA) ) + return NT_STATUS_ACCESS_DENIED; + + /* Force a close and reopen */ + + elog_close_tdb( info->etdb, True ); + become_root(); + info->etdb = elog_open_tdb( info->logname, True, False ); + unbecome_root(); + + if ( !info->etdb ) + return NT_STATUS_ACCESS_DENIED; + + return NT_STATUS_OK; +} + +/******************************************************************** + _eventlog_CloseEventLog + ********************************************************************/ + +NTSTATUS _eventlog_CloseEventLog(struct pipes_struct * p, + struct eventlog_CloseEventLog *r) +{ + NTSTATUS status; + + status = elog_close( p, r->in.handle ); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; +} + +/******************************************************************** + _eventlog_ReadEventLogW + ********************************************************************/ + +NTSTATUS _eventlog_ReadEventLogW(struct pipes_struct *p, + struct eventlog_ReadEventLogW *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle ); + int bytes_left, record_number; + uint32_t elog_read_type, elog_read_dir; + + if (!info) { + return NT_STATUS_INVALID_HANDLE; + } + + info->flags = r->in.flags; + bytes_left = r->in.number_of_bytes; + + if (!info->etdb) { + return NT_STATUS_ACCESS_DENIED; + } + + /* check for valid flags. Can't use the sequential and seek flags together */ + + elog_read_type = r->in.flags & (EVENTLOG_SEQUENTIAL_READ|EVENTLOG_SEEK_READ); + elog_read_dir = r->in.flags & (EVENTLOG_FORWARDS_READ|EVENTLOG_BACKWARDS_READ); + + if (r->in.flags == 0 || + elog_read_type == (EVENTLOG_SEQUENTIAL_READ|EVENTLOG_SEEK_READ) || + elog_read_dir == (EVENTLOG_FORWARDS_READ|EVENTLOG_BACKWARDS_READ)) + { + DEBUG(3,("_eventlog_ReadEventLogW: " + "Invalid flags [0x%08x] for ReadEventLog\n", + r->in.flags)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* a sequential read should ignore the offset */ + + if (elog_read_type & EVENTLOG_SEQUENTIAL_READ) { + record_number = info->current_record; + } else { + record_number = r->in.offset; + } + + if (r->in.number_of_bytes == 0) { + struct EVENTLOGRECORD *e; + e = evlog_pull_record(p->mem_ctx, ELOG_TDB_CTX(info->etdb), + record_number); + if (!e) { + return NT_STATUS_END_OF_FILE; + } + *r->out.real_size = e->Length; + return NT_STATUS_BUFFER_TOO_SMALL; + } + + while (bytes_left > 0) { + + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct EVENTLOGRECORD *e; + + e = evlog_pull_record(p->mem_ctx, ELOG_TDB_CTX(info->etdb), + record_number); + if (!e) { + break; + } + + ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, e, + (ndr_push_flags_fn_t)ndr_push_EVENTLOGRECORD); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(EVENTLOGRECORD, e); + } + + if (blob.length > r->in.number_of_bytes) { + *r->out.real_size = blob.length; + return NT_STATUS_BUFFER_TOO_SMALL; + } + + if (*r->out.sent_size + blob.length > r->in.number_of_bytes) { + break; + } + + bytes_left -= blob.length; + + if (info->flags & EVENTLOG_FORWARDS_READ) { + record_number++; + } else { + record_number--; + } + + /* update the eventlog record pointer */ + + info->current_record = record_number; + + memcpy(&r->out.data[*(r->out.sent_size)], + blob.data, blob.length); + *(r->out.sent_size) += blob.length; + } + + if (r->in.offset == 0 && record_number == 0 && *r->out.sent_size == 0) { + return NT_STATUS_END_OF_FILE; + } + + return NT_STATUS_OK; +} + +/******************************************************************** + _eventlog_GetOldestRecord + ********************************************************************/ + +NTSTATUS _eventlog_GetOldestRecord(struct pipes_struct *p, + struct eventlog_GetOldestRecord *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle ); + + if (info == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + if ( !( get_oldest_entry_hook( info ) ) ) + return NT_STATUS_ACCESS_DENIED; + + *r->out.oldest_entry = info->oldest_entry; + + return NT_STATUS_OK; +} + +/******************************************************************** +_eventlog_GetNumRecords + ********************************************************************/ + +NTSTATUS _eventlog_GetNumRecords(struct pipes_struct *p, + struct eventlog_GetNumRecords *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle ); + + if (info == NULL) { + return NT_STATUS_INVALID_HANDLE; + } + + if ( !( get_num_records_hook( info ) ) ) + return NT_STATUS_ACCESS_DENIED; + + *r->out.number = info->num_records; + + return NT_STATUS_OK; +} + +NTSTATUS _eventlog_BackupEventLogW(struct pipes_struct *p, struct eventlog_BackupEventLogW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/******************************************************************** +_eventlog_GetLogInformation + ********************************************************************/ + +NTSTATUS _eventlog_GetLogInformation(struct pipes_struct *p, + struct eventlog_GetLogInformation *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle); + struct EVENTLOG_FULL_INFORMATION f; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + + if (!info) { + return NT_STATUS_INVALID_HANDLE; + } + + if (r->in.level != 0) { + return NT_STATUS_INVALID_LEVEL; + } + + *r->out.bytes_needed = 4; + + if (r->in.buf_size < 4) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + /* FIXME: this should be retrieved from the handle */ + f.full = false; + + ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &f, + (ndr_push_flags_fn_t)ndr_push_EVENTLOG_FULL_INFORMATION); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(EVENTLOG_FULL_INFORMATION, &f); + } + + memcpy(r->out.buffer, blob.data, 4); + + return NT_STATUS_OK; +} + +/******************************************************************** +_eventlog_FlushEventLog + ********************************************************************/ + +NTSTATUS _eventlog_FlushEventLog(struct pipes_struct *p, + struct eventlog_FlushEventLog *r) +{ + EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle); + if (!info) { + return NT_STATUS_INVALID_HANDLE; + } + + return NT_STATUS_ACCESS_DENIED; +} + +/******************************************************************** + ********************************************************************/ + +static NTSTATUS evlog_report_to_record(TALLOC_CTX *mem_ctx, + const struct eventlog_ReportEventW *r, + const char *logname, + struct EVENTLOGRECORD *e) +{ + uint32_t i; + ZERO_STRUCTP(e); + + e->TimeGenerated = r->in.timestamp; + e->TimeWritten = time(NULL); + e->EventID = r->in.event_id; + e->EventType = r->in.event_type; + e->NumStrings = r->in.num_of_strings; + e->EventCategory = r->in.event_category; + e->ReservedFlags = r->in.flags; + e->DataLength = r->in.data_size; + e->SourceName = talloc_strdup(mem_ctx, logname); + NT_STATUS_HAVE_NO_MEMORY(e->SourceName); + if (r->in.servername->string) { + e->Computername = r->in.servername->string; + } else { + e->Computername = talloc_strdup(mem_ctx, ""); + NT_STATUS_HAVE_NO_MEMORY(e->Computername); + } + if (r->in.user_sid) { + e->UserSid = *r->in.user_sid; + } + e->Strings = talloc_array(mem_ctx, const char *, e->NumStrings); + NT_STATUS_HAVE_NO_MEMORY(e->Strings); + + for (i=0; i < e->NumStrings; i++) { + e->Strings[i] = talloc_strdup(e->Strings, + r->in.strings[i]->string); + NT_STATUS_HAVE_NO_MEMORY(e->Strings[i]); + } + e->Data = r->in.data; + + return NT_STATUS_OK; +} + +/******************************************************************** +_eventlog_ReportEventW + ********************************************************************/ + +NTSTATUS _eventlog_ReportEventW(struct pipes_struct *p, + struct eventlog_ReportEventW *r) +{ + NTSTATUS status; + struct EVENTLOGRECORD record; + + EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle); + if (!info) { + return NT_STATUS_INVALID_HANDLE; + } + + status = evlog_report_to_record(p->mem_ctx, r, info->logname, &record); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = evlog_push_record(p->mem_ctx, + ELOG_TDB_CTX(info->etdb), + &record, + r->out.record_number); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************** + ********************************************************************/ + +NTSTATUS _eventlog_DeregisterEventSource(struct pipes_struct *p, + struct eventlog_DeregisterEventSource *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_ChangeNotify(struct pipes_struct *p, + struct eventlog_ChangeNotify *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_RegisterEventSourceW(struct pipes_struct *p, + struct eventlog_RegisterEventSourceW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_OpenBackupEventLogW(struct pipes_struct *p, + struct eventlog_OpenBackupEventLogW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_ClearEventLogA(struct pipes_struct *p, + struct eventlog_ClearEventLogA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_BackupEventLogA(struct pipes_struct *p, + struct eventlog_BackupEventLogA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_OpenEventLogA(struct pipes_struct *p, + struct eventlog_OpenEventLogA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_RegisterEventSourceA(struct pipes_struct *p, + struct eventlog_RegisterEventSourceA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_OpenBackupEventLogA(struct pipes_struct *p, + struct eventlog_OpenBackupEventLogA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_ReadEventLogA(struct pipes_struct *p, + struct eventlog_ReadEventLogA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_ReportEventA(struct pipes_struct *p, + struct eventlog_ReportEventA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_RegisterClusterSvc(struct pipes_struct *p, + struct eventlog_RegisterClusterSvc *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_DeregisterClusterSvc(struct pipes_struct *p, + struct eventlog_DeregisterClusterSvc *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_WriteClusterEvents(struct pipes_struct *p, + struct eventlog_WriteClusterEvents *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _eventlog_ReportEventAndSourceW(struct pipes_struct *p, + struct eventlog_ReportEventAndSourceW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS eventlog__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_EVENTLOG_INIT_SERVER \ + eventlog_init_server + +static NTSTATUS eventlog_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + bool ok; + + ok = eventlog_init_winreg(msg_ctx); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + return eventlog__op_init_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_eventlog_scompat.c" diff --git a/source3/rpc_server/eventlog/srv_eventlog_reg.c b/source3/rpc_server/eventlog/srv_eventlog_reg.c new file mode 100644 index 0000000..513dd0f --- /dev/null +++ b/source3/rpc_server/eventlog/srv_eventlog_reg.c @@ -0,0 +1,267 @@ +/* + * Unix SMB/CIFS implementation. + * + * Eventlog RPC server keys initialization + * + * Copyright (c) 2005 Marcin Krzysztof Porwit + * Copyright (c) 2005 Brian Moran + * Copyright (c) 2005 Gerald (Jerry) Carter + * 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 "../librpc/gen_ndr/ndr_winreg_c.h" +#include "rpc_client/cli_winreg_int.h" +#include "rpc_client/cli_winreg.h" +#include "rpc_server/eventlog/srv_eventlog_reg.h" +#include "auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_REGISTRY + +#define TOP_LEVEL_EVENTLOG_KEY "SYSTEM\\CurrentControlSet\\Services\\Eventlog" + +bool eventlog_init_winreg(struct messaging_context *msg_ctx) +{ + struct dcerpc_binding_handle *h = NULL; + uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + struct policy_handle hive_hnd, key_hnd; + uint32_t uiMaxSize = 0x00080000; + uint32_t uiRetention = 0x93A80; + const char **elogs = lp_eventlog_list(); + const char **subkeys = NULL; + uint32_t num_subkeys = 0; + uint32_t i; + char *key = NULL; + NTSTATUS status; + WERROR result = WERR_OK; + bool ok = false; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return false; + } + + DEBUG(3, ("Initialise the eventlog registry keys if needed.\n")); + + key = talloc_strdup(tmp_ctx, TOP_LEVEL_EVENTLOG_KEY); + + status = dcerpc_winreg_int_hklm_openkey(tmp_ctx, + get_session_info_system(), + msg_ctx, + &h, + key, + false, + access_mask, + &hive_hnd, + &key_hnd, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("eventlog_init_winreg: Could not open %s - %s\n", + key, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("eventlog_init_winreg: Could not open %s - %s\n", + key, win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_enum_keys(tmp_ctx, + h, + &key_hnd, + &num_subkeys, + &subkeys, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("eventlog_init_winreg: Could enum keys at %s - %s\n", + key, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("eventlog_init_winreg: Could enum keys at %s - %s\n", + key, win_errstr(result))); + goto done; + } + + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result); + } + + /* create subkeys if they don't exist */ + while (elogs && *elogs) { + enum winreg_CreateAction action = REG_ACTION_NONE; + char *evt_tdb = NULL; + struct winreg_String wkey; + struct winreg_String wkeyclass; + bool skip = false; + + for (i = 0; i < num_subkeys; i++) { + if (strequal(subkeys[i], *elogs)) { + skip = true; + } + } + + if (skip) { + elogs++; + continue; + } + + ZERO_STRUCT(key_hnd); + ZERO_STRUCT(wkey); + + wkey.name = talloc_asprintf(tmp_ctx, "%s\\%s", key, *elogs); + if (wkey.name == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + ZERO_STRUCT(wkeyclass); + wkeyclass.name = ""; + + + status = dcerpc_winreg_CreateKey(h, + tmp_ctx, + &hive_hnd, + wkey, + wkeyclass, + 0, + access_mask, + NULL, + &key_hnd, + &action, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_dword(tmp_ctx, + h, + &key_hnd, + "MaxSize", + uiMaxSize, + &result); + + status = dcerpc_winreg_set_dword(tmp_ctx, + h, + &key_hnd, + "Retention", + uiRetention, + &result); + + status = dcerpc_winreg_set_sz(tmp_ctx, + h, + &key_hnd, + "PrimaryModule", + *elogs, + &result); + + evt_tdb = talloc_asprintf(tmp_ctx, + "%%SystemRoot%%\\system32\\config\\%s.tdb", + *elogs); + if (evt_tdb == NULL) { + goto done; + } + status = dcerpc_winreg_set_expand_sz(tmp_ctx, + h, + &key_hnd, + "File", + evt_tdb, + &result); + TALLOC_FREE(evt_tdb); + + status = dcerpc_winreg_add_multi_sz(tmp_ctx, + h, + &key_hnd, + "Sources", + *elogs, + &result); + + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result); + } + + /* sub-subkeys */ + { + uint32_t uiCategoryCount = 0x00000007; + + wkey.name = talloc_asprintf(tmp_ctx, + "%s\\%s", + wkey.name, *elogs); + if (wkey.name == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + status = dcerpc_winreg_CreateKey(h, + tmp_ctx, + &hive_hnd, + wkey, + wkeyclass, + 0, + access_mask, + NULL, + &key_hnd, + &action, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_dword(tmp_ctx, + h, + &key_hnd, + "CategoryCount", + uiCategoryCount, + &result); + + status = dcerpc_winreg_set_expand_sz(tmp_ctx, + h, + &key_hnd, + "CategoryMessageFile", + "%SystemRoot%\\system32\\eventlog.dll", + &result); + + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result); + } + } + + elogs++; + } /* loop */ + + ok = true; +done: + TALLOC_FREE(tmp_ctx); + return ok; +} + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/eventlog/srv_eventlog_reg.h b/source3/rpc_server/eventlog/srv_eventlog_reg.h new file mode 100644 index 0000000..02c2792 --- /dev/null +++ b/source3/rpc_server/eventlog/srv_eventlog_reg.h @@ -0,0 +1,29 @@ +/* + * Unix SMB/CIFS implementation. + * + * WINREG client routines + * + * 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/>. + */ + +#ifndef SRV_EVENTLOG_REG_H +#define SRV_EVENTLOG_REG_H + +bool eventlog_init_winreg(struct messaging_context *msg_ctx); + +#endif /* SRV_EVENTLOG_REG_H */ + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ 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; +} diff --git a/source3/rpc_server/initshutdown/srv_initshutdown_nt.c b/source3/rpc_server/initshutdown/srv_initshutdown_nt.c new file mode 100644 index 0000000..da32cdf --- /dev/null +++ b/source3/rpc_server/initshutdown/srv_initshutdown_nt.c @@ -0,0 +1,84 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997. + * Copyright (C) Gerald Carter 2006. + * + * 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/>. + */ + +/* Implementation of registry functions. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/gen_ndr/ndr_initshutdown.h" +#include "librpc/gen_ndr/ndr_initshutdown_scompat.h" +#include "librpc/gen_ndr/ndr_winreg.h" +#include "librpc/gen_ndr/ndr_winreg_scompat.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + + +/******************************************************************* + ********************************************************************/ +WERROR _initshutdown_Init(struct pipes_struct *p, struct initshutdown_Init *r) +{ + struct winreg_InitiateSystemShutdownEx s; + + s.in.hostname = r->in.hostname; + s.in.message = r->in.message; + s.in.timeout = r->in.timeout; + s.in.force_apps = r->in.force_apps; + s.in.do_reboot = r->in.do_reboot; + s.in.reason = 0; + + /* thunk down to _winreg_InitiateSystemShutdownEx() + (just returns a status) */ + + return _winreg_InitiateSystemShutdownEx( p, &s ); +} + +/******************************************************************* + ********************************************************************/ + +WERROR _initshutdown_InitEx(struct pipes_struct *p, struct initshutdown_InitEx *r) +{ + struct winreg_InitiateSystemShutdownEx s; + s.in.hostname = r->in.hostname; + s.in.message = r->in.message; + s.in.timeout = r->in.timeout; + s.in.force_apps = r->in.force_apps; + s.in.do_reboot = r->in.do_reboot; + s.in.reason = r->in.reason; + + return _winreg_InitiateSystemShutdownEx( p, &s); +} + + + + +/******************************************************************* + reg_abort_shutdwon + ********************************************************************/ + +WERROR _initshutdown_Abort(struct pipes_struct *p, struct initshutdown_Abort *r) +{ + struct winreg_AbortSystemShutdown s; + s.in.server = r->in.server; + return _winreg_AbortSystemShutdown( p, &s ); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_initshutdown_scompat.c" diff --git a/source3/rpc_server/lsa/srv_lsa_nt.c b/source3/rpc_server/lsa/srv_lsa_nt.c new file mode 100644 index 0000000..e2078c6 --- /dev/null +++ b/source3/rpc_server/lsa/srv_lsa_nt.c @@ -0,0 +1,5235 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Paul Ashton 1997, + * Copyright (C) Jeremy Allison 2001, 2006. + * Copyright (C) Rafal Szczesniak 2002, + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002, + * Copyright (C) Simo Sorce 2003. + * Copyright (C) Gerald (Jerry) Carter 2005. + * Copyright (C) Volker Lendecke 2005. + * Copyright (C) Guenther Deschner 2008. + * Copyright (C) Andrew Bartlett 2010. + * + * 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/>. + */ + +/* This is the implementation of the lsa server code. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_lsa_scompat.h" +#include "secrets.h" +#include "../librpc/gen_ndr/netlogon.h" +#include "rpc_client/init_lsa.h" +#include "../libcli/security/security.h" +#include "../libcli/security/dom_sid.h" +#include "../librpc/gen_ndr/drsblobs.h" +#include "../librpc/gen_ndr/ndr_drsblobs.h" +#include "../libcli/security/dom_sid.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "passdb.h" +#include "auth.h" +#include "lib/privileges.h" +#include "rpc_server/srv_access_check.h" +#include "../librpc/gen_ndr/ndr_wkssvc.h" +#include "../libcli/auth/libcli_auth.h" +#include "../libcli/lsarpc/util_lsarpc.h" +#include "lsa.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/rpc/dcerpc_helper.h" +#include "lib/param/loadparm.h" +#include "source3/lib/substitute.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define MAX_LOOKUP_SIDS 0x5000 /* 20480 */ + +enum lsa_handle_type { + LSA_HANDLE_POLICY_TYPE = 1, + LSA_HANDLE_ACCOUNT_TYPE = 2, + LSA_HANDLE_TRUST_TYPE = 3, + LSA_HANDLE_SECRET_TYPE = 4}; + +struct lsa_info { + struct dom_sid sid; + const char *name; + uint32_t access; + enum lsa_handle_type type; + struct security_descriptor *sd; +}; + +const struct generic_mapping lsa_account_mapping = { + LSA_ACCOUNT_READ, + LSA_ACCOUNT_WRITE, + LSA_ACCOUNT_EXECUTE, + LSA_ACCOUNT_ALL_ACCESS +}; + +const struct generic_mapping lsa_policy_mapping = { + LSA_POLICY_READ, + LSA_POLICY_WRITE, + LSA_POLICY_EXECUTE, + LSA_POLICY_ALL_ACCESS +}; + +const struct generic_mapping lsa_secret_mapping = { + LSA_SECRET_READ, + LSA_SECRET_WRITE, + LSA_SECRET_EXECUTE, + LSA_SECRET_ALL_ACCESS +}; + +const struct generic_mapping lsa_trusted_domain_mapping = { + LSA_TRUSTED_DOMAIN_READ, + LSA_TRUSTED_DOMAIN_WRITE, + LSA_TRUSTED_DOMAIN_EXECUTE, + LSA_TRUSTED_DOMAIN_ALL_ACCESS +}; + +/*************************************************************************** + initialize a lsa_DomainInfo structure. + ***************************************************************************/ + +static void init_dom_query_3(struct lsa_DomainInfo *r, + const char *name, + struct dom_sid *sid) +{ + init_lsa_StringLarge(&r->name, name); + r->sid = sid; +} + +/*************************************************************************** + initialize a lsa_DomainInfo structure. + ***************************************************************************/ + +static void init_dom_query_5(struct lsa_DomainInfo *r, + const char *name, + struct dom_sid *sid) +{ + init_lsa_StringLarge(&r->name, name); + r->sid = sid; +} + +/*************************************************************************** + lookup_lsa_rids. Must be called as root for lookup_name to work. + ***************************************************************************/ + +static NTSTATUS lookup_lsa_rids(TALLOC_CTX *mem_ctx, + struct lsa_RefDomainList *ref, + struct lsa_TranslatedSid *prid, + uint32_t num_entries, + struct lsa_String *name, + int flags, + uint32_t *pmapped_count) +{ + uint32_t mapped_count, i; + + SMB_ASSERT(num_entries <= MAX_LOOKUP_SIDS); + + mapped_count = 0; + *pmapped_count = 0; + + for (i = 0; i < num_entries; i++) { + struct dom_sid sid; + uint32_t rid; + int dom_idx; + const char *full_name; + const char *domain; + enum lsa_SidType type; + + /* Split name into domain and user component */ + + /* follow w2k8 behavior and return the builtin domain when no + * input has been passed in */ + + if (name[i].string) { + full_name = name[i].string; + } else { + full_name = "BUILTIN"; + } + + DEBUG(5, ("lookup_lsa_rids: looking up name %s\n", full_name)); + + if (!lookup_name(mem_ctx, full_name, flags, &domain, NULL, + &sid, &type)) { + type = SID_NAME_UNKNOWN; + } + + switch (type) { + case SID_NAME_USER: + case SID_NAME_DOM_GRP: + case SID_NAME_DOMAIN: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + DEBUG(5, ("init_lsa_rids: %s found\n", full_name)); + /* Leave these unchanged */ + break; + default: + /* Don't hand out anything but the list above */ + DEBUG(5, ("init_lsa_rids: %s not found\n", full_name)); + type = SID_NAME_UNKNOWN; + break; + } + + rid = 0; + dom_idx = -1; + + if (type != SID_NAME_UNKNOWN) { + if (type == SID_NAME_DOMAIN) { + rid = (uint32_t)-1; + } else { + sid_split_rid(&sid, &rid); + } + dom_idx = init_lsa_ref_domain_list(mem_ctx, ref, domain, &sid); + mapped_count++; + } + + prid[i].sid_type = type; + prid[i].rid = rid; + prid[i].sid_index = dom_idx; + } + + *pmapped_count = mapped_count; + return NT_STATUS_OK; +} + +/*************************************************************************** + lookup_lsa_sids. Must be called as root for lookup_name to work. + ***************************************************************************/ + +static NTSTATUS lookup_lsa_sids(TALLOC_CTX *mem_ctx, + struct lsa_RefDomainList *ref, + struct lsa_TranslatedSid3 *trans_sids, + uint32_t num_entries, + struct lsa_String *name, + int flags, + uint32_t *pmapped_count) +{ + uint32_t mapped_count, i; + + SMB_ASSERT(num_entries <= MAX_LOOKUP_SIDS); + + mapped_count = 0; + *pmapped_count = 0; + + for (i = 0; i < num_entries; i++) { + struct dom_sid sid; + uint32_t rid; + int dom_idx; + const char *full_name; + const char *domain; + enum lsa_SidType type; + + ZERO_STRUCT(sid); + + /* Split name into domain and user component */ + + full_name = name[i].string; + if (full_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(5, ("lookup_lsa_sids: looking up name %s\n", full_name)); + + if (!lookup_name(mem_ctx, full_name, flags, &domain, NULL, + &sid, &type)) { + type = SID_NAME_UNKNOWN; + } + + switch (type) { + case SID_NAME_USER: + case SID_NAME_DOM_GRP: + case SID_NAME_DOMAIN: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + DEBUG(5, ("lookup_lsa_sids: %s found\n", full_name)); + /* Leave these unchanged */ + break; + default: + /* Don't hand out anything but the list above */ + DEBUG(5, ("lookup_lsa_sids: %s not found\n", full_name)); + type = SID_NAME_UNKNOWN; + break; + } + + rid = 0; + dom_idx = -1; + + if (type != SID_NAME_UNKNOWN) { + struct dom_sid domain_sid; + sid_copy(&domain_sid, &sid); + sid_split_rid(&domain_sid, &rid); + dom_idx = init_lsa_ref_domain_list(mem_ctx, ref, domain, &domain_sid); + mapped_count++; + } + + /* Initialize the lsa_TranslatedSid3 return. */ + trans_sids[i].sid_type = type; + trans_sids[i].sid = dom_sid_dup(mem_ctx, &sid); + trans_sids[i].sid_index = dom_idx; + } + + *pmapped_count = mapped_count; + return NT_STATUS_OK; +} + +static NTSTATUS make_lsa_object_sd(TALLOC_CTX *mem_ctx, struct security_descriptor **sd, size_t *sd_size, + const struct generic_mapping *map, + struct dom_sid *sid, uint32_t sid_access) +{ + struct dom_sid adm_sid; + struct security_ace ace[5]; + size_t i = 0; + + struct security_acl *psa = NULL; + + /* READ|EXECUTE access for Everyone */ + + init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED, + map->generic_execute | map->generic_read, 0); + + /* Add Full Access 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */ + + init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators, + SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0); + init_sec_ace(&ace[i++], &global_sid_Builtin_Account_Operators, + SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0); + + /* Add Full Access for Domain Admins */ + sid_compose(&adm_sid, get_global_sam_sid(), DOMAIN_RID_ADMINS); + init_sec_ace(&ace[i++], &adm_sid, SEC_ACE_TYPE_ACCESS_ALLOWED, + map->generic_all, 0); + + /* If we have a sid, give it some special access */ + + if (sid) { + init_sec_ace(&ace[i++], sid, SEC_ACE_TYPE_ACCESS_ALLOWED, + sid_access, 0); + } + + if((psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION, i, ace)) == NULL) + return NT_STATUS_NO_MEMORY; + + if((*sd = make_sec_desc(mem_ctx, SECURITY_DESCRIPTOR_REVISION_1, + SEC_DESC_SELF_RELATIVE, &adm_sid, NULL, NULL, + psa, sd_size)) == NULL) + return NT_STATUS_NO_MEMORY; + + return NT_STATUS_OK; +} + +/*************************************************************************** + ***************************************************************************/ + +static NTSTATUS create_lsa_policy_handle(TALLOC_CTX *mem_ctx, + struct pipes_struct *p, + enum lsa_handle_type type, + uint32_t acc_granted, + struct dom_sid *sid, + const char *name, + const struct security_descriptor *sd, + struct policy_handle *handle) +{ + struct lsa_info *info; + + ZERO_STRUCTP(handle); + + info = talloc_zero(mem_ctx, struct lsa_info); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + info->type = type; + info->access = acc_granted; + + if (sid) { + sid_copy(&info->sid, sid); + } + + info->name = talloc_strdup(info, name); + + if (sd != NULL) { + info->sd = security_descriptor_copy(info, sd); + if (info->sd == NULL) { + talloc_free(info); + return NT_STATUS_NO_MEMORY; + } + } + + if (!create_policy_hnd(p, handle, type, info)) { + talloc_free(info); + ZERO_STRUCTP(handle); + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_OpenPolicy2 + ***************************************************************************/ + +NTSTATUS _lsa_OpenPolicy2(struct pipes_struct *p, + struct lsa_OpenPolicy2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + size_t sd_size; + uint32_t des_access = r->in.access_mask; + uint32_t acc_granted; + NTSTATUS status; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&des_access, &lsa_policy_mapping); + + /* get the generic lsa policy SD until we store it */ + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, &lsa_policy_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, des_access, + &acc_granted, "_lsa_OpenPolicy2" ); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_POLICY_TYPE, + acc_granted, + get_global_sam_sid(), + NULL, + psd, + r->out.handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_OpenPolicy + ***************************************************************************/ + +NTSTATUS _lsa_OpenPolicy(struct pipes_struct *p, + struct lsa_OpenPolicy *r) +{ + struct lsa_OpenPolicy2 o; + + /* _lsa_OpenPolicy2 will check if this is a NCACN_NP connection */ + + o.in.system_name = NULL; /* should be ignored */ + o.in.attr = r->in.attr; + o.in.access_mask = r->in.access_mask; + + o.out.handle = r->out.handle; + + return _lsa_OpenPolicy2(p, &o); +} + +/*************************************************************************** + _lsa_EnumTrustDom - this needs fixing to do more than return NULL ! JRA. + ufff, done :) mimir + ***************************************************************************/ + +NTSTATUS _lsa_EnumTrustDom(struct pipes_struct *p, + struct lsa_EnumTrustDom *r) +{ + struct lsa_info *info; + uint32_t i, count; + struct trustdom_info **domains; + struct lsa_DomainInfo *entries; + NTSTATUS nt_status; + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &nt_status); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + become_root(); + nt_status = pdb_enum_trusteddoms(p->mem_ctx, &count, &domains); + unbecome_root(); + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + entries = talloc_zero_array(p->mem_ctx, struct lsa_DomainInfo, count); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<count; i++) { + init_lsa_StringLarge(&entries[i].name, domains[i]->name); + entries[i].sid = &domains[i]->sid; + } + + if (*r->in.resume_handle >= count) { + *r->out.resume_handle = -1; + TALLOC_FREE(entries); + return NT_STATUS_NO_MORE_ENTRIES; + } + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 60 */ + r->out.domains->count = count - *r->in.resume_handle; + r->out.domains->count = MIN(r->out.domains->count, + 1+(r->in.max_size/LSA_ENUM_TRUST_DOMAIN_MULTIPLIER)); + + r->out.domains->domains = entries + *r->in.resume_handle; + + if (r->out.domains->count < count - *r->in.resume_handle) { + *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count; + return STATUS_MORE_ENTRIES; + } + + /* according to MS-LSAD 3.1.4.7.8 output resume handle MUST + * always be larger than the previous input resume handle, in + * particular when hitting the last query it is vital to set the + * resume handle correctly to avoid infinite client loops, as + * seen e.g. with Windows XP SP3 when resume handle is 0 and + * status is NT_STATUS_OK - gd */ + + *r->out.resume_handle = (uint32_t)-1; + + return NT_STATUS_OK; +} + +#define LSA_AUDIT_NUM_CATEGORIES_NT4 7 +#define LSA_AUDIT_NUM_CATEGORIES_WIN2K 9 +#define LSA_AUDIT_NUM_CATEGORIES LSA_AUDIT_NUM_CATEGORIES_NT4 + +/*************************************************************************** + _lsa_QueryInfoPolicy + ***************************************************************************/ + +NTSTATUS _lsa_QueryInfoPolicy(struct pipes_struct *p, + struct lsa_QueryInfoPolicy *r) +{ + NTSTATUS status = NT_STATUS_OK; + struct lsa_info *handle; + struct dom_sid domain_sid; + const char *name; + struct dom_sid *sid = NULL; + union lsa_PolicyInformation *info = NULL; + uint32_t acc_required = 0; + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + switch (r->in.level) { + case LSA_POLICY_INFO_AUDIT_LOG: + case LSA_POLICY_INFO_AUDIT_EVENTS: + acc_required = LSA_POLICY_VIEW_AUDIT_INFORMATION; + break; + case LSA_POLICY_INFO_DOMAIN: + acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION; + break; + case LSA_POLICY_INFO_PD: + acc_required = LSA_POLICY_GET_PRIVATE_INFORMATION; + break; + case LSA_POLICY_INFO_ACCOUNT_DOMAIN: + acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION; + break; + case LSA_POLICY_INFO_ROLE: + case LSA_POLICY_INFO_REPLICA: + acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION; + break; + case LSA_POLICY_INFO_QUOTA: + acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION; + break; + case LSA_POLICY_INFO_MOD: + case LSA_POLICY_INFO_AUDIT_FULL_SET: + /* according to MS-LSAD 3.1.4.4.3 */ + return NT_STATUS_INVALID_PARAMETER; + case LSA_POLICY_INFO_AUDIT_FULL_QUERY: + acc_required = LSA_POLICY_VIEW_AUDIT_INFORMATION; + break; + case LSA_POLICY_INFO_DNS: + case LSA_POLICY_INFO_DNS_INT: + case LSA_POLICY_INFO_L_ACCOUNT_DOMAIN: + acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION; + break; + default: + break; + } + + if (!(handle->access & acc_required)) { + /* return NT_STATUS_ACCESS_DENIED; */ + } + + info = talloc_zero(p->mem_ctx, union lsa_PolicyInformation); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + /* according to MS-LSAD 3.1.4.4.3 */ + case LSA_POLICY_INFO_MOD: + case LSA_POLICY_INFO_AUDIT_FULL_SET: + case LSA_POLICY_INFO_AUDIT_FULL_QUERY: + return NT_STATUS_INVALID_PARAMETER; + case LSA_POLICY_INFO_AUDIT_LOG: + info->audit_log.percent_full = 0; + info->audit_log.maximum_log_size = 0; + info->audit_log.retention_time = 0; + info->audit_log.shutdown_in_progress = 0; + info->audit_log.time_to_shutdown = 0; + info->audit_log.next_audit_record = 0; + status = NT_STATUS_OK; + break; + case LSA_POLICY_INFO_PD: + info->pd.name.string = NULL; + status = NT_STATUS_OK; + break; + case LSA_POLICY_INFO_REPLICA: + info->replica.source.string = NULL; + info->replica.account.string = NULL; + status = NT_STATUS_OK; + break; + case LSA_POLICY_INFO_QUOTA: + info->quota.paged_pool = 0; + info->quota.non_paged_pool = 0; + info->quota.min_wss = 0; + info->quota.max_wss = 0; + info->quota.pagefile = 0; + info->quota.unknown = 0; + status = NT_STATUS_OK; + break; + case LSA_POLICY_INFO_AUDIT_EVENTS: + { + + uint32_t policy_def = LSA_AUDIT_POLICY_ALL; + + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_VIEW_AUDIT_INFORMATION)) { + DEBUG(10,("_lsa_QueryInfoPolicy: insufficient access rights\n")); + return NT_STATUS_ACCESS_DENIED; + } + + /* fake info: We audit everything. ;) */ + + info->audit_events.auditing_mode = true; + info->audit_events.count = LSA_AUDIT_NUM_CATEGORIES; + info->audit_events.settings = talloc_zero_array(p->mem_ctx, + enum lsa_PolicyAuditPolicy, + info->audit_events.count); + if (!info->audit_events.settings) { + return NT_STATUS_NO_MEMORY; + } + + info->audit_events.settings[LSA_AUDIT_CATEGORY_ACCOUNT_MANAGEMENT] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_FILE_AND_OBJECT_ACCESS] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_LOGON] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_PROCCESS_TRACKING] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_SECURITY_POLICY_CHANGES] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_SYSTEM] = policy_def; + info->audit_events.settings[LSA_AUDIT_CATEGORY_USE_OF_USER_RIGHTS] = policy_def; + + break; + } + case LSA_POLICY_INFO_DOMAIN: + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + /* Request PolicyPrimaryDomainInformation. */ + switch (lp_server_role()) { + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + name = get_global_sam_name(); + sid = dom_sid_dup(p->mem_ctx, get_global_sam_sid()); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + break; + case ROLE_DOMAIN_MEMBER: + name = lp_workgroup(); + /* We need to return the Domain SID here. */ + if (secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) { + sid = dom_sid_dup(p->mem_ctx, &domain_sid); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + } else { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + break; + case ROLE_STANDALONE: + name = lp_workgroup(); + sid = NULL; + break; + default: + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + init_dom_query_3(&info->domain, name, sid); + break; + case LSA_POLICY_INFO_ACCOUNT_DOMAIN: + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + /* Request PolicyAccountDomainInformation. */ + name = get_global_sam_name(); + sid = get_global_sam_sid(); + + init_dom_query_5(&info->account_domain, name, sid); + break; + case LSA_POLICY_INFO_ROLE: + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + switch (lp_server_role()) { + case ROLE_DOMAIN_BDC: + /* + * only a BDC is a backup controller + * of the domain, it controls. + */ + info->role.role = LSA_ROLE_BACKUP; + break; + default: + /* + * any other role is a primary + * of the domain, it controls. + */ + info->role.role = LSA_ROLE_PRIMARY; + break; + } + break; + case LSA_POLICY_INFO_DNS: + case LSA_POLICY_INFO_DNS_INT: { + struct pdb_domain_info *dominfo; + + if ((pdb_capabilities() & PDB_CAP_ADS) == 0) { + DEBUG(10, ("Not replying to LSA_POLICY_INFO_DNS " + "without ADS passdb backend\n")); + status = NT_STATUS_INVALID_INFO_CLASS; + break; + } + + dominfo = pdb_get_domain_info(info); + if (dominfo == NULL) { + status = NT_STATUS_NO_MEMORY; + break; + } + + init_lsa_StringLarge(&info->dns.name, + dominfo->name); + init_lsa_StringLarge(&info->dns.dns_domain, + dominfo->dns_domain); + init_lsa_StringLarge(&info->dns.dns_forest, + dominfo->dns_forest); + info->dns.domain_guid = dominfo->guid; + info->dns.sid = &dominfo->sid; + break; + } + default: + DEBUG(0,("_lsa_QueryInfoPolicy: unknown info level in Lsa Query: %d\n", + r->in.level)); + status = NT_STATUS_INVALID_INFO_CLASS; + break; + } + + *r->out.info = info; + + return status; +} + +/*************************************************************************** + _lsa_QueryInfoPolicy2 + ***************************************************************************/ + +NTSTATUS _lsa_QueryInfoPolicy2(struct pipes_struct *p, + struct lsa_QueryInfoPolicy2 *r2) +{ + struct lsa_QueryInfoPolicy r; + + if ((pdb_capabilities() & PDB_CAP_ADS) == 0) { + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; + } + + ZERO_STRUCT(r); + r.in.handle = r2->in.handle; + r.in.level = r2->in.level; + r.out.info = r2->out.info; + + return _lsa_QueryInfoPolicy(p, &r); +} + +/*************************************************************************** + _lsa_lookup_sids_internal + ***************************************************************************/ + +static NTSTATUS _lsa_lookup_sids_internal(struct pipes_struct *p, + TALLOC_CTX *mem_ctx, + uint16_t level, /* input */ + int num_sids, /* input */ + struct lsa_SidPtr *sid, /* input */ + struct lsa_RefDomainList **pp_ref, /* input/output */ + struct lsa_TranslatedName2 **pp_names,/* input/output */ + uint32_t *pp_mapped_count) /* input/output */ +{ + NTSTATUS status; + int i; + const struct dom_sid **sids = NULL; + struct lsa_RefDomainList *ref = NULL; + uint32_t mapped_count = 0; + struct lsa_dom_info *dom_infos = NULL; + struct lsa_name_info *name_infos = NULL; + struct lsa_TranslatedName2 *names = NULL; + + *pp_mapped_count = 0; + *pp_names = NULL; + *pp_ref = NULL; + + if (num_sids == 0) { + return NT_STATUS_OK; + } + + sids = talloc_array(p->mem_ctx, const struct dom_sid *, num_sids); + ref = talloc_zero(p->mem_ctx, struct lsa_RefDomainList); + + if (sids == NULL || ref == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_sids; i++) { + sids[i] = sid[i].sid; + } + + status = lookup_sids(p->mem_ctx, num_sids, sids, level, + &dom_infos, &name_infos); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + names = talloc_array(p->mem_ctx, struct lsa_TranslatedName2, num_sids); + if (names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<LSA_REF_DOMAIN_LIST_MULTIPLIER; i++) { + + if (!dom_infos[i].valid) { + break; + } + + if (init_lsa_ref_domain_list(mem_ctx, ref, + dom_infos[i].name, + &dom_infos[i].sid) != i) { + DEBUG(0, ("Domain %s mentioned twice??\n", + dom_infos[i].name)); + return NT_STATUS_INTERNAL_ERROR; + } + } + + for (i=0; i<num_sids; i++) { + struct lsa_name_info *name = &name_infos[i]; + + if (name->type == SID_NAME_UNKNOWN) { + name->dom_idx = -1; + /* Unknown sids should return the string + * representation of the SID. Windows 2003 behaves + * rather erratic here, in many cases it returns the + * RID as 8 bytes hex, in others it returns the full + * SID. We (Jerry/VL) could not figure out which the + * hard cases are, so leave it with the SID. */ + name->name = dom_sid_string(p->mem_ctx, sids[i]); + if (name->name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } else { + mapped_count += 1; + } + + names[i].sid_type = name->type; + names[i].name.string = name->name; + names[i].sid_index = name->dom_idx; + names[i].unknown = 0; + } + + status = NT_STATUS_NONE_MAPPED; + if (mapped_count > 0) { + status = (mapped_count < num_sids) ? + STATUS_SOME_UNMAPPED : NT_STATUS_OK; + } + + DEBUG(10, ("num_sids %d, mapped_count %d, status %s\n", + num_sids, mapped_count, nt_errstr(status))); + + *pp_mapped_count = mapped_count; + *pp_names = names; + *pp_ref = ref; + + return status; +} + +/*************************************************************************** + _lsa_LookupSids + ***************************************************************************/ + +NTSTATUS _lsa_LookupSids(struct pipes_struct *p, + struct lsa_LookupSids *r) +{ + NTSTATUS status; + struct lsa_info *handle; + int num_sids = r->in.sids->num_sids; + uint32_t mapped_count = 0; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TranslatedName *names_out = NULL; + struct lsa_TranslatedName2 *names = NULL; + int i; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + if ((r->in.level < 1) || (r->in.level > 6)) { + return NT_STATUS_INVALID_PARAMETER; + } + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (num_sids > MAX_LOOKUP_SIDS) { + DEBUG(5,("_lsa_LookupSids: limit of %d exceeded, requested %d\n", + MAX_LOOKUP_SIDS, num_sids)); + return NT_STATUS_NONE_MAPPED; + } + + status = _lsa_lookup_sids_internal(p, + p->mem_ctx, + r->in.level, + num_sids, + r->in.sids->sids, + &domains, + &names, + &mapped_count); + + /* Only return here when there is a real error. + NT_STATUS_NONE_MAPPED is a special case as it indicates that none of + the requested sids could be resolved. Older versions of XP (pre SP3) + rely that we return with the string representations of those SIDs in + that case. If we don't, XP crashes - Guenther + */ + + if (NT_STATUS_IS_ERR(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return status; + } + + /* Convert from lsa_TranslatedName2 to lsa_TranslatedName */ + names_out = talloc_array(p->mem_ctx, struct lsa_TranslatedName, + num_sids); + if (!names_out) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_sids; i++) { + names_out[i].sid_type = names[i].sid_type; + names_out[i].name = names[i].name; + names_out[i].sid_index = names[i].sid_index; + } + + *r->out.domains = domains; + r->out.names->count = num_sids; + r->out.names->names = names_out; + *r->out.count = mapped_count; + + return status; +} + +static NTSTATUS _lsa_LookupSids_common(struct pipes_struct *p, + struct lsa_LookupSids2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + NTSTATUS status; + struct lsa_info *handle; + int num_sids = r->in.sids->num_sids; + uint32_t mapped_count = 0; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TranslatedName2 *names = NULL; + bool check_policy = true; + + switch (dce_call->pkt.u.request.opnum) { + case NDR_LSA_LOOKUPSIDS3: + check_policy = false; + break; + case NDR_LSA_LOOKUPSIDS2: + default: + check_policy = true; + } + + if ((r->in.level < 1) || (r->in.level > 6)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (check_policy) { + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (num_sids > MAX_LOOKUP_SIDS) { + DEBUG(5,("_lsa_LookupSids2: limit of %d exceeded, requested %d\n", + MAX_LOOKUP_SIDS, num_sids)); + return NT_STATUS_NONE_MAPPED; + } + + status = _lsa_lookup_sids_internal(p, + p->mem_ctx, + r->in.level, + num_sids, + r->in.sids->sids, + &domains, + &names, + &mapped_count); + + *r->out.domains = domains; + r->out.names->count = num_sids; + r->out.names->names = names; + *r->out.count = mapped_count; + + return status; +} + +/*************************************************************************** + _lsa_LookupSids2 + ***************************************************************************/ + +NTSTATUS _lsa_LookupSids2(struct pipes_struct *p, + struct lsa_LookupSids2 *r) +{ + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + return _lsa_LookupSids_common(p, r); +} + +/*************************************************************************** + _lsa_LookupSids3 + ***************************************************************************/ + +NTSTATUS _lsa_LookupSids3(struct pipes_struct *p, + struct lsa_LookupSids3 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + struct lsa_LookupSids2 q; + + if (p->transport != NCACN_IP_TCP) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + + /* No policy handle on this call. Restrict to crypto connections. */ + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL || + auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) { + DEBUG(1, ("_lsa_LookupSids3: The client %s is not using " + "a secure connection over netlogon\n", + get_remote_machine_name() )); + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + q.in.handle = NULL; + q.in.sids = r->in.sids; + q.in.level = r->in.level; + q.in.lookup_options = r->in.lookup_options; + q.in.client_revision = r->in.client_revision; + q.in.names = r->in.names; + q.in.count = r->in.count; + + q.out.domains = r->out.domains; + q.out.names = r->out.names; + q.out.count = r->out.count; + + return _lsa_LookupSids_common(p, &q); +} + +/*************************************************************************** + ***************************************************************************/ + +static int lsa_lookup_level_to_flags(enum lsa_LookupNamesLevel level) +{ + int flags; + + switch (level) { + case LSA_LOOKUP_NAMES_ALL: /* 1 */ + flags = LOOKUP_NAME_ALL; + break; + case LSA_LOOKUP_NAMES_DOMAINS_ONLY: /* 2 */ + flags = LOOKUP_NAME_DOMAIN|LOOKUP_NAME_REMOTE|LOOKUP_NAME_ISOLATED; + break; + case LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY: /* 3 */ + flags = LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED; + break; + case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY: /* 4 */ + case LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY: /* 5 */ + case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2: /* 6 */ + case LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC: /* 7 */ + default: + flags = LOOKUP_NAME_NONE; + break; + } + + return flags; +} + +/*************************************************************************** + _lsa_LookupNames + ***************************************************************************/ + +NTSTATUS _lsa_LookupNames(struct pipes_struct *p, + struct lsa_LookupNames *r) +{ + NTSTATUS status = NT_STATUS_NONE_MAPPED; + struct lsa_info *handle; + struct lsa_String *names = r->in.names; + uint32_t num_entries = r->in.num_names; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TranslatedSid *rids = NULL; + uint32_t mapped_count = 0; + int flags = 0; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + if (num_entries > MAX_LOOKUP_SIDS) { + num_entries = MAX_LOOKUP_SIDS; + DEBUG(5,("_lsa_LookupNames: truncating name lookup list to %d\n", + num_entries)); + } + + flags = lsa_lookup_level_to_flags(r->in.level); + + domains = talloc_zero(p->mem_ctx, struct lsa_RefDomainList); + if (!domains) { + return NT_STATUS_NO_MEMORY; + } + + if (num_entries) { + rids = talloc_zero_array(p->mem_ctx, struct lsa_TranslatedSid, + num_entries); + if (!rids) { + return NT_STATUS_NO_MEMORY; + } + } else { + rids = NULL; + } + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INVALID_HANDLE; + goto done; + } + + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + /* set up the LSA Lookup RIDs response */ + become_root(); /* lookup_name can require root privs */ + status = lookup_lsa_rids(p->mem_ctx, domains, rids, num_entries, + names, flags, &mapped_count); + unbecome_root(); + +done: + + if (NT_STATUS_IS_OK(status) && (num_entries != 0) ) { + if (mapped_count == 0) { + status = NT_STATUS_NONE_MAPPED; + } else if (mapped_count != num_entries) { + status = STATUS_SOME_UNMAPPED; + } + } + + *r->out.count = mapped_count; + *r->out.domains = domains; + r->out.sids->sids = rids; + r->out.sids->count = num_entries; + + return status; +} + +/*************************************************************************** + _lsa_LookupNames2 + ***************************************************************************/ + +NTSTATUS _lsa_LookupNames2(struct pipes_struct *p, + struct lsa_LookupNames2 *r) +{ + NTSTATUS status; + struct lsa_LookupNames q; + struct lsa_TransSidArray2 *sid_array2 = r->in.sids; + struct lsa_TransSidArray *sid_array = NULL; + uint32_t i; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + sid_array = talloc_zero(p->mem_ctx, struct lsa_TransSidArray); + if (!sid_array) { + return NT_STATUS_NO_MEMORY; + } + + q.in.handle = r->in.handle; + q.in.num_names = r->in.num_names; + q.in.names = r->in.names; + q.in.level = r->in.level; + q.in.sids = sid_array; + q.in.count = r->in.count; + /* we do not know what this is for */ + /* = r->in.unknown1; */ + /* = r->in.unknown2; */ + + q.out.domains = r->out.domains; + q.out.sids = sid_array; + q.out.count = r->out.count; + + status = _lsa_LookupNames(p, &q); + + sid_array2->count = sid_array->count; + sid_array2->sids = talloc_array(p->mem_ctx, struct lsa_TranslatedSid2, sid_array->count); + if (!sid_array2->sids) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<sid_array->count; i++) { + sid_array2->sids[i].sid_type = sid_array->sids[i].sid_type; + sid_array2->sids[i].rid = sid_array->sids[i].rid; + sid_array2->sids[i].sid_index = sid_array->sids[i].sid_index; + sid_array2->sids[i].unknown = 0; + } + + r->out.sids = sid_array2; + + return status; +} + +static NTSTATUS _lsa_LookupNames_common(struct pipes_struct *p, + struct lsa_LookupNames3 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + NTSTATUS status; + struct lsa_info *handle; + struct lsa_String *names = r->in.names; + uint32_t num_entries = r->in.num_names; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TranslatedSid3 *trans_sids = NULL; + uint32_t mapped_count = 0; + int flags = 0; + bool check_policy = true; + + switch (dce_call->pkt.u.request.opnum) { + case NDR_LSA_LOOKUPNAMES4: + check_policy = false; + break; + case NDR_LSA_LOOKUPNAMES3: + default: + check_policy = true; + } + + if (num_entries > MAX_LOOKUP_SIDS) { + num_entries = MAX_LOOKUP_SIDS; + DEBUG(5,("_lsa_LookupNames3: truncating name lookup list to %d\n", num_entries)); + } + + flags = lsa_lookup_level_to_flags(r->in.level); + + domains = talloc_zero(p->mem_ctx, struct lsa_RefDomainList); + if (!domains) { + return NT_STATUS_NO_MEMORY; + } + + if (num_entries) { + trans_sids = talloc_zero_array(p->mem_ctx, struct lsa_TranslatedSid3, + num_entries); + if (!trans_sids) { + return NT_STATUS_NO_MEMORY; + } + } else { + trans_sids = NULL; + } + + if (check_policy) { + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_INVALID_HANDLE; + goto done; + } + + /* check if the user has enough rights */ + if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + } + + /* set up the LSA Lookup SIDs response */ + become_root(); /* lookup_name can require root privs */ + status = lookup_lsa_sids(p->mem_ctx, domains, trans_sids, num_entries, + names, flags, &mapped_count); + unbecome_root(); + +done: + + if (NT_STATUS_IS_OK(status)) { + if (mapped_count == 0) { + status = NT_STATUS_NONE_MAPPED; + } else if (mapped_count != num_entries) { + status = STATUS_SOME_UNMAPPED; + } + } + + *r->out.count = mapped_count; + *r->out.domains = domains; + r->out.sids->sids = trans_sids; + r->out.sids->count = num_entries; + + return status; +} + +/*************************************************************************** + _lsa_LookupNames3 + ***************************************************************************/ + +NTSTATUS _lsa_LookupNames3(struct pipes_struct *p, + struct lsa_LookupNames3 *r) +{ + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + return _lsa_LookupNames_common(p, r); +} + +/*************************************************************************** + _lsa_LookupNames4 + ***************************************************************************/ + +NTSTATUS _lsa_LookupNames4(struct pipes_struct *p, + struct lsa_LookupNames4 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + struct lsa_LookupNames3 q; + + if (p->transport != NCACN_IP_TCP) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + + /* No policy handle on this call. Restrict to crypto connections. */ + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL || + auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) { + DEBUG(1, ("_lsa_LookupNames4: The client %s is not using " + "a secure connection over netlogon\n", + get_remote_machine_name())); + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + q.in.handle = NULL; + q.in.num_names = r->in.num_names; + q.in.names = r->in.names; + q.in.level = r->in.level; + q.in.lookup_options = r->in.lookup_options; + q.in.client_revision = r->in.client_revision; + q.in.sids = r->in.sids; + q.in.count = r->in.count; + + q.out.domains = r->out.domains; + q.out.sids = r->out.sids; + q.out.count = r->out.count; + + return _lsa_LookupNames_common(p, &q); +} + +/*************************************************************************** + _lsa_close. Also weird - needs to check if lsa handle is correct. JRA. + ***************************************************************************/ + +NTSTATUS _lsa_Close(struct pipes_struct *p, struct lsa_Close *r) +{ + NTSTATUS status; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + (void)find_policy_by_hnd(p, + r->in.handle, + DCESRV_HANDLE_ANY, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + close_policy_hnd(p, r->in.handle); + ZERO_STRUCTP(r->out.handle); + return NT_STATUS_OK; +} + +/*************************************************************************** + ***************************************************************************/ + +static NTSTATUS lsa_lookup_trusted_domain_by_sid(TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + struct trustdom_info **info) +{ + NTSTATUS status; + uint32_t num_domains = 0; + struct trustdom_info **domains = NULL; + int i; + + status = pdb_enum_trusteddoms(mem_ctx, &num_domains, &domains); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (i=0; i < num_domains; i++) { + if (dom_sid_equal(&domains[i]->sid, sid)) { + break; + } + } + + if (i == num_domains) { + return NT_STATUS_INVALID_PARAMETER; + } + + *info = domains[i]; + + return NT_STATUS_OK; +} + +/*************************************************************************** + ***************************************************************************/ + +static NTSTATUS lsa_lookup_trusted_domain_by_name(TALLOC_CTX *mem_ctx, + const char *netbios_domain_name, + struct trustdom_info **info_p) +{ + NTSTATUS status; + struct trustdom_info *info; + struct pdb_trusted_domain *td; + + status = pdb_get_trusted_domain(mem_ctx, netbios_domain_name, &td); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info = talloc(mem_ctx, struct trustdom_info); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + info->name = talloc_strdup(info, netbios_domain_name); + NT_STATUS_HAVE_NO_MEMORY(info->name); + + sid_copy(&info->sid, &td->security_identifier); + + *info_p = info; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_OpenSecret + ***************************************************************************/ + +NTSTATUS _lsa_OpenSecret(struct pipes_struct *p, + struct lsa_OpenSecret *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd; + NTSTATUS status; + uint32_t acc_granted; + + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!r->in.name.string) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &r->in.access_mask); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&r->in.access_mask, &lsa_secret_mapping); + + status = pdb_get_secret(p->mem_ctx, r->in.name.string, + NULL, + NULL, + NULL, + NULL, + &psd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + r->in.access_mask, + &acc_granted, "_lsa_OpenSecret"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_SECRET_TYPE, + acc_granted, + NULL, + r->in.name.string, + psd, + r->out.sec_handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_OpenTrustedDomain_base + ***************************************************************************/ + +static NTSTATUS _lsa_OpenTrustedDomain_base(struct pipes_struct *p, + uint32_t access_mask, + struct trustdom_info *info, + struct policy_handle *handle) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + size_t sd_size; + uint32_t acc_granted; + NTSTATUS status; + + /* des_access is for the account here, not the policy + * handle - so don't check against policy handle. */ + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &access_mask); + + /* map the generic bits to the lsa account ones */ + se_map_generic(&access_mask, &lsa_trusted_domain_mapping); + + /* get the generic lsa account SD until we store it */ + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_trusted_domain_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + access_mask, &acc_granted, + "_lsa_OpenTrustedDomain"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_TRUST_TYPE, + acc_granted, + &info->sid, + info->name, + psd, + handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_OpenTrustedDomain + ***************************************************************************/ + +NTSTATUS _lsa_OpenTrustedDomain(struct pipes_struct *p, + struct lsa_OpenTrustedDomain *r) +{ + struct trustdom_info *info = NULL; + NTSTATUS status; + + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + status = lsa_lookup_trusted_domain_by_sid(p->mem_ctx, + r->in.sid, + &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return _lsa_OpenTrustedDomain_base(p, r->in.access_mask, info, + r->out.trustdom_handle); +} + +/*************************************************************************** + _lsa_OpenTrustedDomainByName + ***************************************************************************/ + +NTSTATUS _lsa_OpenTrustedDomainByName(struct pipes_struct *p, + struct lsa_OpenTrustedDomainByName *r) +{ + struct trustdom_info *info = NULL; + NTSTATUS status; + + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + status = lsa_lookup_trusted_domain_by_name(p->mem_ctx, + r->in.name.string, + &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return _lsa_OpenTrustedDomain_base(p, r->in.access_mask, info, + r->out.trustdom_handle); +} + +static NTSTATUS get_trustdom_auth_blob(struct pipes_struct *p, + TALLOC_CTX *mem_ctx, DATA_BLOB *auth_blob, + struct trustDomainPasswords *auth_struct) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + enum ndr_err_code ndr_err; + DATA_BLOB lsession_key; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t my_session_key; + NTSTATUS status; + int rc; + bool encrypted; + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + status = session_extract_session_key( + session_info, &lsession_key, KEY_USE_16BYTES); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_PARAMETER; + } + + my_session_key = (gnutls_datum_t) { + .data = lsession_key.data, + .size = lsession_key.length, + }; + + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &my_session_key, + NULL); + if (rc < 0) { + GNUTLS_FIPS140_SET_STRICT_MODE(); + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + auth_blob->data, + auth_blob->length); + gnutls_cipher_deinit(cipher_hnd); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + ndr_err = ndr_pull_struct_blob(auth_blob, mem_ctx, + auth_struct, + (ndr_pull_flags_fn_t)ndr_pull_trustDomainPasswords); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + status = NT_STATUS_OK; +out: + return status; +} + +static NTSTATUS get_trustauth_inout_blob(TALLOC_CTX *mem_ctx, + struct trustAuthInOutBlob *iopw, + DATA_BLOB *trustauth_blob) +{ + enum ndr_err_code ndr_err; + + if (iopw->current.count != iopw->count) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (iopw->previous.count > iopw->current.count) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (iopw->previous.count == 0) { + /* + * If the previous credentials are not present + * we need to make a copy. + */ + iopw->previous = iopw->current; + } + + if (iopw->previous.count < iopw->current.count) { + struct AuthenticationInformationArray *c = &iopw->current; + struct AuthenticationInformationArray *p = &iopw->previous; + + /* + * The previous array needs to have the same size + * as the current one. + * + * We may have to fill with TRUST_AUTH_TYPE_NONE + * elements. + */ + p->array = talloc_realloc(mem_ctx, p->array, + struct AuthenticationInformation, + c->count); + if (p->array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + while (p->count < c->count) { + struct AuthenticationInformation *a = + &p->array[p->count++]; + + *a = (struct AuthenticationInformation) { + .LastUpdateTime = p->array[0].LastUpdateTime, + .AuthType = TRUST_AUTH_TYPE_NONE, + }; + } + } + + ndr_err = ndr_push_struct_blob(trustauth_blob, mem_ctx, + iopw, + (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_CreateTrustedDomainEx2 + ***************************************************************************/ + +NTSTATUS _lsa_CreateTrustedDomainEx2(struct pipes_struct *p, + struct lsa_CreateTrustedDomainEx2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct lsa_info *policy; + NTSTATUS status; + uint32_t acc_granted; + struct security_descriptor *psd; + size_t sd_size; + struct pdb_trusted_domain td; + struct trustDomainPasswords auth_struct; + DATA_BLOB auth_blob; + + if (!IS_DC) { + return NT_STATUS_NOT_SUPPORTED; + } + + policy = find_policy_by_hnd(p, + r->in.policy_handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(policy->access & LSA_POLICY_TRUST_ADMIN)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (session_info->unix_token->uid != sec_initial_uid() && + !nt_token_check_domain_rid( + session_info->security_token, DOMAIN_RID_ADMINS)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &r->in.access_mask); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&r->in.access_mask, &lsa_account_mapping); + + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_trusted_domain_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + r->in.access_mask, &acc_granted, + "_lsa_CreateTrustedDomainEx2"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ZERO_STRUCT(td); + + td.domain_name = talloc_strdup(p->mem_ctx, + r->in.info->domain_name.string); + if (td.domain_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + td.netbios_name = talloc_strdup(p->mem_ctx, + r->in.info->netbios_name.string); + if (td.netbios_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + sid_copy(&td.security_identifier, r->in.info->sid); + td.trust_direction = r->in.info->trust_direction; + td.trust_type = r->in.info->trust_type; + td.trust_attributes = r->in.info->trust_attributes; + + if (r->in.auth_info_internal->auth_blob.size != 0) { + auth_blob.length = r->in.auth_info_internal->auth_blob.size; + auth_blob.data = r->in.auth_info_internal->auth_blob.data; + + status = get_trustdom_auth_blob(p, p->mem_ctx, &auth_blob, &auth_struct); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_UNSUCCESSFUL; + } + + status = get_trustauth_inout_blob(p->mem_ctx, &auth_struct.incoming, &td.trust_auth_incoming); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_UNSUCCESSFUL; + } + + status = get_trustauth_inout_blob(p->mem_ctx, &auth_struct.outgoing, &td.trust_auth_outgoing); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_UNSUCCESSFUL; + } + } else { + td.trust_auth_incoming.data = NULL; + td.trust_auth_incoming.length = 0; + td.trust_auth_outgoing.data = NULL; + td.trust_auth_outgoing.length = 0; + } + + status = pdb_set_trusted_domain(r->in.info->domain_name.string, &td); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_TRUST_TYPE, + acc_granted, + r->in.info->sid, + r->in.info->netbios_name.string, + psd, + r->out.trustdom_handle); + if (!NT_STATUS_IS_OK(status)) { + pdb_del_trusted_domain(r->in.info->netbios_name.string); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_CreateTrustedDomainEx + ***************************************************************************/ + +NTSTATUS _lsa_CreateTrustedDomainEx(struct pipes_struct *p, + struct lsa_CreateTrustedDomainEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/*************************************************************************** + _lsa_CreateTrustedDomain + ***************************************************************************/ + +NTSTATUS _lsa_CreateTrustedDomain(struct pipes_struct *p, + struct lsa_CreateTrustedDomain *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/*************************************************************************** + _lsa_DeleteTrustedDomain + ***************************************************************************/ + +NTSTATUS _lsa_DeleteTrustedDomain(struct pipes_struct *p, + struct lsa_DeleteTrustedDomain *r) +{ + NTSTATUS status; + struct lsa_info *handle; + struct pdb_trusted_domain *td; + + /* find the connection policy handle. */ + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(handle->access & LSA_POLICY_TRUST_ADMIN)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pdb_get_trusted_domain_by_sid(p->mem_ctx, r->in.dom_sid, &td); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (td->netbios_name == NULL || *td->netbios_name == '\0') { + struct dom_sid_buf buf; + DEBUG(10, ("Missing netbios name for trusted domain %s.\n", + dom_sid_str_buf(r->in.dom_sid, &buf))); + return NT_STATUS_UNSUCCESSFUL; + } + + status = pdb_del_trusted_domain(td->netbios_name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_CloseTrustedDomainEx + ***************************************************************************/ + +NTSTATUS _lsa_CloseTrustedDomainEx(struct pipes_struct *p, + struct lsa_CloseTrustedDomainEx *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/*************************************************************************** + _lsa_QueryTrustedDomainInfo + ***************************************************************************/ + +static NTSTATUS pdb_trusted_domain_2_info_ex(TALLOC_CTX *mem_ctx, + struct pdb_trusted_domain *td, + struct lsa_TrustDomainInfoInfoEx *info_ex) +{ + if (td->domain_name == NULL || + td->netbios_name == NULL || + is_null_sid(&td->security_identifier)) { + return NT_STATUS_INVALID_PARAMETER; + } + + info_ex->domain_name.string = talloc_strdup(mem_ctx, td->domain_name); + info_ex->netbios_name.string = talloc_strdup(mem_ctx, td->netbios_name); + info_ex->sid = dom_sid_dup(mem_ctx, &td->security_identifier); + if (info_ex->domain_name.string == NULL || + info_ex->netbios_name.string == NULL || + info_ex->sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + info_ex->trust_direction = td->trust_direction; + info_ex->trust_type = td->trust_type; + info_ex->trust_attributes = td->trust_attributes; + + return NT_STATUS_OK; +} + +NTSTATUS _lsa_QueryTrustedDomainInfo(struct pipes_struct *p, + struct lsa_QueryTrustedDomainInfo *r) +{ + NTSTATUS status; + struct lsa_info *handle; + union lsa_TrustedDomainInfo *info; + struct pdb_trusted_domain *td; + uint32_t acc_required; + + /* find the connection policy handle. */ + handle = find_policy_by_hnd(p, + r->in.trustdom_handle, + LSA_HANDLE_TRUST_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + switch (r->in.level) { + case LSA_TRUSTED_DOMAIN_INFO_NAME: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME; + break; + case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS: + acc_required = LSA_TRUSTED_QUERY_CONTROLLERS; + break; + case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET: + acc_required = LSA_TRUSTED_QUERY_POSIX; + break; + case LSA_TRUSTED_DOMAIN_INFO_PASSWORD: + acc_required = LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_INFO_BASIC: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME; + break; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME; + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO: + acc_required = LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME | + LSA_TRUSTED_QUERY_POSIX | + LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL: + acc_required = LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME | + LSA_TRUSTED_QUERY_POSIX | + LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL: + acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME | + LSA_TRUSTED_QUERY_POSIX | + LSA_TRUSTED_QUERY_AUTH; + break; + case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES: + acc_required = LSA_TRUSTED_QUERY_POSIX; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(handle->access & acc_required)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pdb_get_trusted_domain_by_sid(p->mem_ctx, &handle->sid, &td); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info = talloc_zero(p->mem_ctx, union lsa_TrustedDomainInfo); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case LSA_TRUSTED_DOMAIN_INFO_NAME: + init_lsa_StringLarge(&info->name.netbios_name, td->netbios_name); + break; + case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS: + return NT_STATUS_INVALID_PARAMETER; + case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET: + info->posix_offset.posix_offset = *td->trust_posix_offset; + break; + case LSA_TRUSTED_DOMAIN_INFO_PASSWORD: + return NT_STATUS_INVALID_INFO_CLASS; + case LSA_TRUSTED_DOMAIN_INFO_BASIC: + return NT_STATUS_INVALID_PARAMETER; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX: + status = pdb_trusted_domain_2_info_ex(info, td, &info->info_ex); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO: + return NT_STATUS_INVALID_INFO_CLASS; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO: + status = pdb_trusted_domain_2_info_ex(info, td, + &info->full_info.info_ex); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + info->full_info.posix_offset.posix_offset = *td->trust_posix_offset; + status = auth_blob_2_auth_info(p->mem_ctx, + td->trust_auth_incoming, + td->trust_auth_outgoing, + &info->full_info.auth_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL: + return NT_STATUS_INVALID_INFO_CLASS; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL: + return NT_STATUS_INVALID_INFO_CLASS; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL: + return NT_STATUS_INVALID_PARAMETER; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL: + info->full_info2_internal.posix_offset.posix_offset = *td->trust_posix_offset; + status = auth_blob_2_auth_info(p->mem_ctx, + td->trust_auth_incoming, + td->trust_auth_outgoing, + &info->full_info2_internal.auth_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES: + info->enc_types.enc_types = *td->supported_enc_type; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_QueryTrustedDomainInfoBySid + ***************************************************************************/ + +NTSTATUS _lsa_QueryTrustedDomainInfoBySid(struct pipes_struct *p, + struct lsa_QueryTrustedDomainInfoBySid *r) +{ + NTSTATUS status; + struct policy_handle trustdom_handle; + struct lsa_OpenTrustedDomain o; + struct lsa_QueryTrustedDomainInfo q; + struct lsa_Close c; + + o.in.handle = r->in.handle; + o.in.sid = r->in.dom_sid; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.trustdom_handle = &trustdom_handle; + + status = _lsa_OpenTrustedDomain(p, &o); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + q.in.trustdom_handle = &trustdom_handle; + q.in.level = r->in.level; + q.out.info = r->out.info; + + status = _lsa_QueryTrustedDomainInfo(p, &q); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &trustdom_handle; + + return _lsa_Close(p, &c); +} + +/*************************************************************************** + _lsa_QueryTrustedDomainInfoByName + ***************************************************************************/ + +NTSTATUS _lsa_QueryTrustedDomainInfoByName(struct pipes_struct *p, + struct lsa_QueryTrustedDomainInfoByName *r) +{ + NTSTATUS status; + struct policy_handle trustdom_handle; + struct lsa_OpenTrustedDomainByName o; + struct lsa_QueryTrustedDomainInfo q; + struct lsa_Close c; + + o.in.handle = r->in.handle; + o.in.name.string = r->in.trusted_domain->string; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.trustdom_handle = &trustdom_handle; + + status = _lsa_OpenTrustedDomainByName(p, &o); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + return status; + } + + q.in.trustdom_handle = &trustdom_handle; + q.in.level = r->in.level; + q.out.info = r->out.info; + + status = _lsa_QueryTrustedDomainInfo(p, &q); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &trustdom_handle; + + return _lsa_Close(p, &c); +} + +/*************************************************************************** + _lsa_CreateSecret + ***************************************************************************/ + +NTSTATUS _lsa_CreateSecret(struct pipes_struct *p, + struct lsa_CreateSecret *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + struct lsa_info *handle; + uint32_t acc_granted; + struct security_descriptor *psd; + size_t sd_size; + + /* find the connection policy handle. */ + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + + if (!(handle->access & LSA_POLICY_CREATE_SECRET)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &r->in.access_mask); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&r->in.access_mask, &lsa_secret_mapping); + + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_secret_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + r->in.access_mask, + &acc_granted, "_lsa_CreateSecret"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!r->in.name.string) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strlen(r->in.name.string) > 128) { + return NT_STATUS_NAME_TOO_LONG; + } + + status = pdb_get_secret(p->mem_ctx, r->in.name.string, + NULL, NULL, NULL, NULL, NULL); + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + status = pdb_set_secret(r->in.name.string, NULL, NULL, psd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_SECRET_TYPE, + acc_granted, + NULL, + r->in.name.string, + psd, + r->out.sec_handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_SetSecret + ***************************************************************************/ + +NTSTATUS _lsa_SetSecret(struct pipes_struct *p, + struct lsa_SetSecret *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + struct lsa_info *info = NULL; + DATA_BLOB blob_new, blob_old; + DATA_BLOB cleartext_blob_new = data_blob_null; + DATA_BLOB cleartext_blob_old = data_blob_null; + DATA_BLOB *cleartext_blob_new_p = NULL; + DATA_BLOB *cleartext_blob_old_p = NULL; + DATA_BLOB session_key; + + info = find_policy_by_hnd(p, + r->in.sec_handle, + LSA_HANDLE_SECRET_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_SECRET_SET_VALUE)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + return status; + } + + if (r->in.new_val) { + blob_new = data_blob_const(r->in.new_val->data, + r->in.new_val->length); + + status = sess_decrypt_blob(p->mem_ctx, &blob_new, + &session_key, + &cleartext_blob_new); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + cleartext_blob_new_p = &cleartext_blob_new; + } + + if (r->in.old_val) { + blob_old = data_blob_const(r->in.old_val->data, + r->in.old_val->length); + + status = sess_decrypt_blob(p->mem_ctx, &blob_old, + &session_key, + &cleartext_blob_old); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + cleartext_blob_old_p = &cleartext_blob_old; + } + + status = pdb_set_secret(info->name, cleartext_blob_new_p, cleartext_blob_old_p, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + +#ifdef DEBUG_PASSWORD + DEBUG(10,("_lsa_SetSecret: successfully set new secret\n")); + dump_data(10, cleartext_blob_new.data, cleartext_blob_new.length); + DEBUG(10,("_lsa_SetSecret: successfully set old secret\n")); + dump_data(10, cleartext_blob_old.data, cleartext_blob_old.length); +#endif + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_QuerySecret + ***************************************************************************/ + +NTSTATUS _lsa_QuerySecret(struct pipes_struct *p, + struct lsa_QuerySecret *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct lsa_info *info = NULL; + DATA_BLOB blob_new, blob_old; + DATA_BLOB blob_new_crypt, blob_old_crypt; + DATA_BLOB session_key; + NTTIME nttime_new, nttime_old; + NTSTATUS status; + + info = find_policy_by_hnd(p, + r->in.sec_handle, + LSA_HANDLE_SECRET_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_SECRET_QUERY_VALUE)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pdb_get_secret(p->mem_ctx, info->name, + &blob_new, &nttime_new, + &blob_old, &nttime_old, + NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + return status; + } + + if (r->in.new_val) { + if (blob_new.length) { + if (!r->out.new_val->buf) { + r->out.new_val->buf = talloc_zero(p->mem_ctx, struct lsa_DATA_BUF); + } + if (!r->out.new_val->buf) { + return NT_STATUS_NO_MEMORY; + } + + blob_new_crypt = sess_encrypt_blob(p->mem_ctx, &blob_new, + &session_key); + if (!blob_new_crypt.length) { + return NT_STATUS_NO_MEMORY; + } + + r->out.new_val->buf->data = blob_new_crypt.data; + r->out.new_val->buf->length = blob_new_crypt.length; + r->out.new_val->buf->size = blob_new_crypt.length; + } + } + + if (r->in.old_val) { + if (blob_old.length) { + if (!r->out.old_val->buf) { + r->out.old_val->buf = talloc_zero(p->mem_ctx, struct lsa_DATA_BUF); + } + if (!r->out.old_val->buf) { + return NT_STATUS_NO_MEMORY; + } + + blob_old_crypt = sess_encrypt_blob(p->mem_ctx, &blob_old, + &session_key); + if (!blob_old_crypt.length) { + return NT_STATUS_NO_MEMORY; + } + + r->out.old_val->buf->data = blob_old_crypt.data; + r->out.old_val->buf->length = blob_old_crypt.length; + r->out.old_val->buf->size = blob_old_crypt.length; + } + } + + if (r->out.new_mtime) { + *r->out.new_mtime = nttime_new; + } + + if (r->out.old_mtime) { + *r->out.old_mtime = nttime_old; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_DeleteObject + ***************************************************************************/ + +NTSTATUS _lsa_DeleteObject(struct pipes_struct *p, + struct lsa_DeleteObject *r) +{ + NTSTATUS status; + struct lsa_info *info = NULL; + + info = find_policy_by_hnd(p, + r->in.handle, + DCESRV_HANDLE_ANY, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & SEC_STD_DELETE)) { + return NT_STATUS_ACCESS_DENIED; + } + + switch (info->type) { + case LSA_HANDLE_ACCOUNT_TYPE: + status = privilege_delete_account(&info->sid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("_lsa_DeleteObject: privilege_delete_account gave: %s\n", + nt_errstr(status))); + return status; + } + break; + case LSA_HANDLE_TRUST_TYPE: + if (!pdb_del_trusteddom_pw(info->name)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + status = NT_STATUS_OK; + break; + case LSA_HANDLE_SECRET_TYPE: + status = pdb_delete_secret(info->name); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_INVALID_HANDLE; + } + break; + default: + return NT_STATUS_INVALID_HANDLE; + } + + close_policy_hnd(p, r->in.handle); + ZERO_STRUCTP(r->out.handle); + + return status; +} + +/*************************************************************************** + _lsa_EnumPrivs + ***************************************************************************/ + +NTSTATUS _lsa_EnumPrivs(struct pipes_struct *p, + struct lsa_EnumPrivs *r) +{ + struct lsa_info *handle; + uint32_t i; + uint32_t enum_context = *r->in.resume_handle; + int num_privs = num_privileges_in_short_list(); + struct lsa_PrivEntry *entries = NULL; + NTSTATUS status; + + /* remember that the enum_context starts at 0 and not 1 */ + + if ( enum_context >= num_privs ) + return NT_STATUS_NO_MORE_ENTRIES; + + DEBUG(10,("_lsa_EnumPrivs: enum_context:%d total entries:%d\n", + enum_context, num_privs)); + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights + I don't know if it's the right one. not documented. */ + + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + if (num_privs) { + entries = talloc_zero_array(p->mem_ctx, struct lsa_PrivEntry, num_privs); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + } else { + entries = NULL; + } + + for (i = 0; i < num_privs; i++) { + if( i < enum_context) { + + init_lsa_StringLarge(&entries[i].name, NULL); + + entries[i].luid.low = 0; + entries[i].luid.high = 0; + } else { + + init_lsa_StringLarge(&entries[i].name, sec_privilege_name_from_index(i)); + + entries[i].luid.low = sec_privilege_from_index(i); + entries[i].luid.high = 0; + } + } + + enum_context = num_privs; + + *r->out.resume_handle = enum_context; + r->out.privs->count = num_privs; + r->out.privs->privs = entries; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_LookupPrivDisplayName + ***************************************************************************/ + +NTSTATUS _lsa_LookupPrivDisplayName(struct pipes_struct *p, + struct lsa_LookupPrivDisplayName *r) +{ + struct lsa_info *handle; + const char *description; + struct lsa_StringLarge *lsa_name; + NTSTATUS status; + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + + /* + * I don't know if it's the right one. not documented. + */ + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + DEBUG(10,("_lsa_LookupPrivDisplayName: name = %s\n", r->in.name->string)); + + description = get_privilege_dispname(r->in.name->string); + if (!description) { + DEBUG(10,("_lsa_LookupPrivDisplayName: doesn't exist\n")); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + DEBUG(10,("_lsa_LookupPrivDisplayName: display name = %s\n", description)); + + lsa_name = talloc_zero(p->mem_ctx, struct lsa_StringLarge); + if (!lsa_name) { + return NT_STATUS_NO_MEMORY; + } + + init_lsa_StringLarge(lsa_name, description); + + *r->out.returned_language_id = r->in.language_id; + *r->out.disp_name = lsa_name; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_EnumAccounts + ***************************************************************************/ + +NTSTATUS _lsa_EnumAccounts(struct pipes_struct *p, + struct lsa_EnumAccounts *r) +{ + struct lsa_info *handle; + struct dom_sid *sid_list; + int i, j, num_entries; + NTSTATUS status; + struct lsa_SidPtr *sids = NULL; + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + sid_list = NULL; + num_entries = 0; + + /* The only way we can currently find out all the SIDs that have been + privileged is to scan all privileges */ + + status = privilege_enumerate_accounts(&sid_list, &num_entries); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (*r->in.resume_handle >= num_entries) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + if (num_entries - *r->in.resume_handle) { + sids = talloc_zero_array(p->mem_ctx, struct lsa_SidPtr, + num_entries - *r->in.resume_handle); + if (!sids) { + talloc_free(sid_list); + return NT_STATUS_NO_MEMORY; + } + + for (i = *r->in.resume_handle, j = 0; i < num_entries; i++, j++) { + sids[j].sid = dom_sid_dup(p->mem_ctx, &sid_list[i]); + if (!sids[j].sid) { + talloc_free(sid_list); + return NT_STATUS_NO_MEMORY; + } + } + } + + talloc_free(sid_list); + + *r->out.resume_handle = num_entries; + r->out.sids->num_sids = num_entries; + r->out.sids->sids = sids; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_GetUserName + ***************************************************************************/ + +NTSTATUS _lsa_GetUserName(struct pipes_struct *p, + struct lsa_GetUserName *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *username, *domname; + struct lsa_String *account_name = NULL; + struct lsa_String *authority_name = NULL; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + if (r->in.account_name && + *r->in.account_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.authority_name && + *r->in.authority_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (security_session_user_level(session_info, NULL) < SECURITY_USER) { + /* + * I'm 99% sure this is not the right place to do this, + * global_sid_Anonymous should probably be put into the token + * instead of the guest id -- vl + */ + if (!lookup_sid(p->mem_ctx, &global_sid_Anonymous, + &domname, &username, NULL)) { + return NT_STATUS_NO_MEMORY; + } + } else { + username = session_info->unix_info->sanitized_username; + domname = session_info->info->domain_name; + } + + account_name = talloc(p->mem_ctx, struct lsa_String); + if (!account_name) { + return NT_STATUS_NO_MEMORY; + } + init_lsa_String(account_name, username); + + if (r->out.authority_name) { + authority_name = talloc(p->mem_ctx, struct lsa_String); + if (!authority_name) { + return NT_STATUS_NO_MEMORY; + } + init_lsa_String(authority_name, domname); + } + + *r->out.account_name = account_name; + if (r->out.authority_name) { + *r->out.authority_name = authority_name; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_CreateAccount + ***************************************************************************/ + +NTSTATUS _lsa_CreateAccount(struct pipes_struct *p, + struct lsa_CreateAccount *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + struct lsa_info *handle; + uint32_t acc_granted; + struct security_descriptor *psd; + size_t sd_size; + uint32_t owner_access = (LSA_ACCOUNT_ALL_ACCESS & + ~(LSA_ACCOUNT_ADJUST_PRIVILEGES| + LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS| + SEC_STD_DELETE)); + + /* find the connection policy handle. */ + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + + if (!(handle->access & LSA_POLICY_CREATE_ACCOUNT)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &r->in.access_mask); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&r->in.access_mask, &lsa_account_mapping); + + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_account_mapping, + r->in.sid, owner_access); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, r->in.access_mask, + &acc_granted, "_lsa_CreateAccount"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ( is_privileged_sid( r->in.sid ) ) + return NT_STATUS_OBJECT_NAME_COLLISION; + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_ACCOUNT_TYPE, + acc_granted, + r->in.sid, + NULL, + psd, + r->out.acct_handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return privilege_create_account(r->in.sid); +} + +/*************************************************************************** + _lsa_OpenAccount + ***************************************************************************/ + +NTSTATUS _lsa_OpenAccount(struct pipes_struct *p, + struct lsa_OpenAccount *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + size_t sd_size; + uint32_t des_access = r->in.access_mask; + uint32_t acc_granted; + uint32_t owner_access = (LSA_ACCOUNT_ALL_ACCESS & + ~(LSA_ACCOUNT_ADJUST_PRIVILEGES| + LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS| + SEC_STD_DELETE)); + NTSTATUS status; + + /* find the connection policy handle. */ + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* des_access is for the account here, not the policy + * handle - so don't check against policy handle. */ + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + /* map the generic bits to the lsa account ones */ + se_map_generic(&des_access, &lsa_account_mapping); + + /* get the generic lsa account SD until we store it */ + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_account_mapping, + r->in.sid, owner_access); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, des_access, + &acc_granted, "_lsa_OpenAccount" ); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* TODO: Fis the parsing routine before reenabling this check! */ + #if 0 + if (!lookup_sid(&handle->sid, dom_name, name, &type)) + return NT_STATUS_ACCESS_DENIED; + #endif + + status = create_lsa_policy_handle(p->mem_ctx, p, + LSA_HANDLE_ACCOUNT_TYPE, + acc_granted, + r->in.sid, + NULL, + psd, + r->out.acct_handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_EnumPrivsAccount + For a given SID, enumerate all the privilege this account has. + ***************************************************************************/ + +NTSTATUS _lsa_EnumPrivsAccount(struct pipes_struct *p, + struct lsa_EnumPrivsAccount *r) +{ + NTSTATUS status = NT_STATUS_OK; + struct lsa_info *info=NULL; + PRIVILEGE_SET *privileges; + struct lsa_PrivilegeSet *priv_set = NULL; + struct dom_sid_buf buf; + + /* find the connection policy handle. */ + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_ACCOUNT_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_VIEW)) + return NT_STATUS_ACCESS_DENIED; + + status = get_privileges_for_sid_as_set(p->mem_ctx, &privileges, &info->sid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.privs = priv_set = talloc_zero(p->mem_ctx, struct lsa_PrivilegeSet); + if (!priv_set) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10,("_lsa_EnumPrivsAccount: %s has %d privileges\n", + dom_sid_str_buf(&info->sid, &buf), + privileges->count)); + + priv_set->count = privileges->count; + priv_set->unknown = 0; + priv_set->set = talloc_move(priv_set, &privileges->set); + + return status; +} + +/*************************************************************************** + _lsa_GetSystemAccessAccount + ***************************************************************************/ + +NTSTATUS _lsa_GetSystemAccessAccount(struct pipes_struct *p, + struct lsa_GetSystemAccessAccount *r) +{ + NTSTATUS status; + struct lsa_info *info = NULL; + struct lsa_EnumPrivsAccount e; + struct lsa_PrivilegeSet *privset; + + /* find the connection policy handle. */ + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_ACCOUNT_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_VIEW)) + return NT_STATUS_ACCESS_DENIED; + + privset = talloc_zero(p->mem_ctx, struct lsa_PrivilegeSet); + if (!privset) { + return NT_STATUS_NO_MEMORY; + } + + e.in.handle = r->in.handle; + e.out.privs = &privset; + + status = _lsa_EnumPrivsAccount(p, &e); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("_lsa_GetSystemAccessAccount: " + "failed to call _lsa_EnumPrivsAccount(): %s\n", + nt_errstr(status))); + return status; + } + + /* Samba4 would iterate over the privset to merge the policy mode bits, + * not sure samba3 can do the same here, so just return what we did in + * the past - gd */ + + /* + 0x01 -> Log on locally + 0x02 -> Access this computer from network + 0x04 -> Log on as a batch job + 0x10 -> Log on as a service + + they can be ORed together + */ + + *r->out.access_mask = LSA_POLICY_MODE_INTERACTIVE | + LSA_POLICY_MODE_NETWORK; + + return NT_STATUS_OK; +} + +/*************************************************************************** + update the systemaccount information + ***************************************************************************/ + +NTSTATUS _lsa_SetSystemAccessAccount(struct pipes_struct *p, + struct lsa_SetSystemAccessAccount *r) +{ + struct lsa_info *info=NULL; + NTSTATUS status; + GROUP_MAP *map; + + /* find the connection policy handle. */ + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_ACCOUNT_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS)) { + return NT_STATUS_ACCESS_DENIED; + } + + map = talloc_zero(p->mem_ctx, GROUP_MAP); + if (!map) { + return NT_STATUS_NO_MEMORY; + } + + if (!pdb_getgrsid(map, info->sid)) { + TALLOC_FREE(map); + return NT_STATUS_NO_SUCH_GROUP; + } + + status = pdb_update_group_mapping_entry(map); + TALLOC_FREE(map); + return status; +} + +/*************************************************************************** + _lsa_AddPrivilegesToAccount + For a given SID, add some privileges. + ***************************************************************************/ + +NTSTATUS _lsa_AddPrivilegesToAccount(struct pipes_struct *p, + struct lsa_AddPrivilegesToAccount *r) +{ + struct lsa_info *info = NULL; + struct lsa_PrivilegeSet *set = NULL; + NTSTATUS status; + + /* find the connection policy handle. */ + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_ACCOUNT_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_ADJUST_PRIVILEGES)) { + return NT_STATUS_ACCESS_DENIED; + } + + set = r->in.privs; + + if ( !grant_privilege_set( &info->sid, set ) ) { + struct dom_sid_buf buf; + DEBUG(3,("_lsa_AddPrivilegesToAccount: grant_privilege_set(%s) failed!\n", + dom_sid_str_buf(&info->sid, &buf))); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_RemovePrivilegesFromAccount + For a given SID, remove some privileges. + ***************************************************************************/ + +NTSTATUS _lsa_RemovePrivilegesFromAccount(struct pipes_struct *p, + struct lsa_RemovePrivilegesFromAccount *r) +{ + struct lsa_info *info = NULL; + struct lsa_PrivilegeSet *set = NULL; + NTSTATUS status; + + /* find the connection policy handle. */ + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_ACCOUNT_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_ADJUST_PRIVILEGES)) { + return NT_STATUS_ACCESS_DENIED; + } + + set = r->in.privs; + + if ( !revoke_privilege_set( &info->sid, set) ) { + struct dom_sid_buf buf; + DEBUG(3,("_lsa_RemovePrivilegesFromAccount: revoke_privilege(%s) failed!\n", + dom_sid_str_buf(&info->sid, &buf))); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_LookupPrivName + ***************************************************************************/ + +NTSTATUS _lsa_LookupPrivName(struct pipes_struct *p, + struct lsa_LookupPrivName *r) +{ + struct lsa_info *info = NULL; + const char *name; + struct lsa_StringLarge *lsa_name; + NTSTATUS status; + + /* find the connection policy handle. */ + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (r->in.luid->high != 0) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + name = sec_privilege_name(r->in.luid->low); + if (!name) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + lsa_name = talloc_zero(p->mem_ctx, struct lsa_StringLarge); + if (!lsa_name) { + return NT_STATUS_NO_MEMORY; + } + + lsa_name->string = talloc_strdup(lsa_name, name); + if (!lsa_name->string) { + TALLOC_FREE(lsa_name); + return NT_STATUS_NO_MEMORY; + } + + *r->out.name = lsa_name; + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_QuerySecurity + ***************************************************************************/ + +NTSTATUS _lsa_QuerySecurity(struct pipes_struct *p, + struct lsa_QuerySecurity *r) +{ + struct lsa_info *handle=NULL; + struct security_descriptor *psd = NULL; + size_t sd_size = 0; + NTSTATUS status; + + /* find the connection policy handle. */ + handle = find_policy_by_hnd(p, + r->in.handle, + DCESRV_HANDLE_ANY, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + switch (handle->type) { + case LSA_HANDLE_POLICY_TYPE: + case LSA_HANDLE_ACCOUNT_TYPE: + case LSA_HANDLE_TRUST_TYPE: + case LSA_HANDLE_SECRET_TYPE: + psd = handle->sd; + sd_size = ndr_size_security_descriptor(psd, 0); + status = NT_STATUS_OK; + break; + default: + status = NT_STATUS_INVALID_HANDLE; + break; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.sdbuf = make_sec_desc_buf(p->mem_ctx, sd_size, psd); + if (!*r->out.sdbuf) { + return NT_STATUS_NO_MEMORY; + } + + return status; +} + +/*************************************************************************** + _lsa_AddAccountRights + ***************************************************************************/ + +NTSTATUS _lsa_AddAccountRights(struct pipes_struct *p, + struct lsa_AddAccountRights *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + int i = 0; + uint32_t acc_granted = 0; + struct security_descriptor *psd = NULL; + size_t sd_size; + struct dom_sid sid; + NTSTATUS status; + + /* find the connection policy handle. */ + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* get the generic lsa account SD for this SID until we store it */ + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_account_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * From the MS DOCs. If the sid doesn't exist, ask for LSA_POLICY_CREATE_ACCOUNT + * on the policy handle. If it does, ask for + * LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW, + * on the account sid. We don't check here so just use the latter. JRA. + */ + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW, + &acc_granted, "_lsa_AddAccountRights" ); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* according to an NT4 PDC, you can add privileges to SIDs even without + call_lsa_create_account() first. And you can use any arbitrary SID. */ + + sid_copy( &sid, r->in.sid ); + + for ( i=0; i < r->in.rights->count; i++ ) { + + const char *privname = r->in.rights->names[i].string; + + /* only try to add non-null strings */ + + if ( !privname ) + continue; + + if ( !grant_privilege_by_name( &sid, privname ) ) { + DEBUG(2,("_lsa_AddAccountRights: Failed to add privilege [%s]\n", + privname )); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_RemoveAccountRights + ***************************************************************************/ + +NTSTATUS _lsa_RemoveAccountRights(struct pipes_struct *p, + struct lsa_RemoveAccountRights *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + int i = 0; + struct security_descriptor *psd = NULL; + size_t sd_size; + struct dom_sid sid; + const char *privname = NULL; + uint32_t acc_granted = 0; + NTSTATUS status; + + /* find the connection policy handle. */ + (void)find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* get the generic lsa account SD for this SID until we store it */ + status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, + &lsa_account_mapping, + NULL, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * From the MS DOCs. We need + * LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW + * and DELETE on the account sid. + */ + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, + LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS| + LSA_ACCOUNT_VIEW|SEC_STD_DELETE, + &acc_granted, "_lsa_RemoveAccountRights"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + sid_copy( &sid, r->in.sid ); + + if ( r->in.remove_all ) { + if ( !revoke_all_privileges( &sid ) ) + return NT_STATUS_ACCESS_DENIED; + + return NT_STATUS_OK; + } + + for ( i=0; i < r->in.rights->count; i++ ) { + + privname = r->in.rights->names[i].string; + + /* only try to add non-null strings */ + + if ( !privname ) + continue; + + if ( !revoke_privilege_by_name( &sid, privname ) ) { + DEBUG(2,("_lsa_RemoveAccountRights: Failed to revoke privilege [%s]\n", + privname )); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + } + + return NT_STATUS_OK; +} + +/******************************************************************* +********************************************************************/ + +static NTSTATUS init_lsa_right_set(TALLOC_CTX *mem_ctx, + struct lsa_RightSet *r, + PRIVILEGE_SET *privileges) +{ + uint32_t i; + const char *privname; + const char **privname_array = NULL; + size_t num_priv = 0; + + for (i=0; i<privileges->count; i++) { + if (privileges->set[i].luid.high) { + continue; + } + privname = sec_privilege_name(privileges->set[i].luid.low); + if (privname) { + if (!add_string_to_array(mem_ctx, privname, + &privname_array, &num_priv)) { + return NT_STATUS_NO_MEMORY; + } + } + } + + if (num_priv) { + + r->names = talloc_zero_array(mem_ctx, struct lsa_StringLarge, + num_priv); + if (!r->names) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_priv; i++) { + init_lsa_StringLarge(&r->names[i], privname_array[i]); + } + + r->count = num_priv; + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_EnumAccountRights + ***************************************************************************/ + +NTSTATUS _lsa_EnumAccountRights(struct pipes_struct *p, + struct lsa_EnumAccountRights *r) +{ + NTSTATUS status; + struct lsa_info *info = NULL; + PRIVILEGE_SET *privileges; + struct dom_sid_buf buf; + + /* find the connection policy handle. */ + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_ACCOUNT_VIEW)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* according to an NT4 PDC, you can add privileges to SIDs even without + call_lsa_create_account() first. And you can use any arbitrary SID. */ + + /* according to MS-LSAD 3.1.4.5.10 it is required to return + * NT_STATUS_OBJECT_NAME_NOT_FOUND if the account sid was not found in + * the lsa database */ + + status = get_privileges_for_sid_as_set(p->mem_ctx, &privileges, r->in.sid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10,("_lsa_EnumAccountRights: %s has %d privileges\n", + dom_sid_str_buf(r->in.sid, &buf), + privileges->count)); + + status = init_lsa_right_set(p->mem_ctx, r->out.rights, privileges); + + return status; +} + +/*************************************************************************** + _lsa_LookupPrivValue + ***************************************************************************/ + +NTSTATUS _lsa_LookupPrivValue(struct pipes_struct *p, + struct lsa_LookupPrivValue *r) +{ + struct lsa_info *info = NULL; + const char *name = NULL; + NTSTATUS status; + + /* find the connection policy handle. */ + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_POLICY_LOOKUP_NAMES)) + return NT_STATUS_ACCESS_DENIED; + + name = r->in.name->string; + + DEBUG(10,("_lsa_lookup_priv_value: name = %s\n", name)); + + r->out.luid->low = sec_privilege_id(name); + r->out.luid->high = 0; + if (r->out.luid->low == SEC_PRIV_INVALID) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_EnumAccountsWithUserRight + ***************************************************************************/ + +NTSTATUS _lsa_EnumAccountsWithUserRight(struct pipes_struct *p, + struct lsa_EnumAccountsWithUserRight *r) +{ + NTSTATUS status; + struct lsa_info *info = NULL; + struct dom_sid *sids = NULL; + int num_sids = 0; + uint32_t i; + enum sec_privilege privilege; + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(info->access & LSA_POLICY_LOOKUP_NAMES)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (!r->in.name || !r->in.name->string) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + privilege = sec_privilege_id(r->in.name->string); + if (privilege == SEC_PRIV_INVALID) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + status = privilege_enum_sids(privilege, p->mem_ctx, + &sids, &num_sids); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.sids->num_sids = num_sids; + r->out.sids->sids = talloc_array(p->mem_ctx, struct lsa_SidPtr, + r->out.sids->num_sids); + + for (i=0; i < r->out.sids->num_sids; i++) { + r->out.sids->sids[i].sid = dom_sid_dup(r->out.sids->sids, + &sids[i]); + if (!r->out.sids->sids[i].sid) { + TALLOC_FREE(r->out.sids->sids); + r->out.sids->num_sids = 0; + return NT_STATUS_NO_MEMORY; + } + } + + return NT_STATUS_OK; +} + +/*************************************************************************** + _lsa_Delete + ***************************************************************************/ + +NTSTATUS _lsa_Delete(struct pipes_struct *p, + struct lsa_Delete *r) +{ + return NT_STATUS_NOT_SUPPORTED; +} + +static NTSTATUS info_ex_2_pdb_trusted_domain( + struct lsa_TrustDomainInfoInfoEx *info_ex, + struct pdb_trusted_domain *td) +{ + if (info_ex->domain_name.string == NULL || + info_ex->netbios_name.string == NULL || + info_ex->sid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + td->domain_name = talloc_strdup(td, info_ex->domain_name.string); + td->netbios_name = talloc_strdup(td, info_ex->netbios_name.string); + sid_copy(&td->security_identifier, info_ex->sid); + if (td->domain_name == NULL || + td->netbios_name == NULL || + is_null_sid(&td->security_identifier)) { + return NT_STATUS_NO_MEMORY; + } + td->trust_direction = info_ex->trust_direction; + td->trust_type = info_ex->trust_type; + td->trust_attributes = info_ex->trust_attributes; + + return NT_STATUS_OK; +} + +static NTSTATUS setInfoTrustedDomain_base(struct pipes_struct *p, + TALLOC_CTX *mem_ctx, + struct lsa_info *policy, + enum lsa_TrustDomInfoEnum level, + union lsa_TrustedDomainInfo *info) +{ + struct lsa_TrustDomainInfoAuthInfoInternal *auth_info_int = NULL; + DATA_BLOB auth_blob; + struct trustDomainPasswords auth_struct; + NTSTATUS nt_status; + + struct pdb_trusted_domain *td; + struct pdb_trusted_domain *orig_td; + + td = talloc_zero(mem_ctx, struct pdb_trusted_domain); + if (td == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch (level) { + case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET: + if (!(policy->access & LSA_TRUSTED_SET_POSIX)) { + return NT_STATUS_ACCESS_DENIED; + } + td->trust_posix_offset = &info->posix_offset.posix_offset; + break; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX: + if (!(policy->access & LSA_TRUSTED_SET_POSIX)) { + return NT_STATUS_ACCESS_DENIED; + } + nt_status = info_ex_2_pdb_trusted_domain(&info->info_ex, td); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO: + if (!(policy->access & LSA_TRUSTED_SET_AUTH)) { + return NT_STATUS_ACCESS_DENIED; + } + nt_status = auth_info_2_auth_blob(td, &info->auth_info, + &td->trust_auth_incoming, + &td->trust_auth_outgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO: + if (!(policy->access & (LSA_TRUSTED_SET_AUTH | LSA_TRUSTED_SET_POSIX))) { + return NT_STATUS_ACCESS_DENIED; + } + td->trust_posix_offset = &info->full_info.posix_offset.posix_offset; + nt_status = info_ex_2_pdb_trusted_domain(&info->full_info.info_ex, + td); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + nt_status = auth_info_2_auth_blob(td, + &info->full_info.auth_info, + &td->trust_auth_incoming, + &td->trust_auth_outgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL: + if (!(policy->access & LSA_TRUSTED_SET_AUTH)) { + return NT_STATUS_ACCESS_DENIED; + } + auth_info_int = &info->auth_info_internal; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL: + if (!(policy->access & (LSA_TRUSTED_SET_AUTH | LSA_TRUSTED_SET_POSIX))) { + return NT_STATUS_ACCESS_DENIED; + } + td->trust_posix_offset = &info->full_info_internal.posix_offset.posix_offset; + nt_status = info_ex_2_pdb_trusted_domain(&info->full_info_internal.info_ex, + td); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + auth_info_int = &info->full_info_internal.auth_info; + break; + case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES: + if (!(policy->access & LSA_TRUSTED_SET_POSIX)) { + return NT_STATUS_ACCESS_DENIED; + } + td->supported_enc_type = &info->enc_types.enc_types; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + /* decode auth_info_int if set */ + if (auth_info_int) { + + /* now decrypt blob */ + auth_blob = data_blob_const(auth_info_int->auth_blob.data, + auth_info_int->auth_blob.size); + + nt_status = get_trustdom_auth_blob(p, mem_ctx, + &auth_blob, &auth_struct); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + memset(&auth_struct, 0, sizeof(auth_struct)); + } + +/* TODO: verify only one object matches the dns/netbios/sid triplet and that + * this is the one we already have */ + +/* TODO: check if the trust direction is changed and we need to add or remove + * auth data */ + +/* TODO: check if trust type shall be changed and return an error in this case + * */ + nt_status = pdb_get_trusted_domain_by_sid(p->mem_ctx, &policy->sid, + &orig_td); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + + /* TODO: should we fetch previous values from the existing entry + * and append them ? */ + if (auth_struct.incoming.count) { + nt_status = get_trustauth_inout_blob(mem_ctx, + &auth_struct.incoming, + &td->trust_auth_incoming); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + ZERO_STRUCT(td->trust_auth_incoming); + } + + if (auth_struct.outgoing.count) { + nt_status = get_trustauth_inout_blob(mem_ctx, + &auth_struct.outgoing, + &td->trust_auth_outgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + ZERO_STRUCT(td->trust_auth_outgoing); + } + + nt_status = pdb_set_trusted_domain(orig_td->domain_name, td); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + return NT_STATUS_OK; +} + +NTSTATUS _lsa_SetTrustedDomainInfo(struct pipes_struct *p, + struct lsa_SetTrustedDomainInfo *r) +{ + NTSTATUS status; + struct policy_handle trustdom_handle; + struct lsa_OpenTrustedDomain o; + struct lsa_SetInformationTrustedDomain s; + struct lsa_Close c; + + o.in.handle = r->in.handle; + o.in.sid = r->in.dom_sid; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.trustdom_handle = &trustdom_handle; + + status = _lsa_OpenTrustedDomain(p, &o); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + s.in.trustdom_handle = &trustdom_handle; + s.in.level = r->in.level; + s.in.info = r->in.info; + + status = _lsa_SetInformationTrustedDomain(p, &s); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &trustdom_handle; + + return _lsa_Close(p, &c); +} + +NTSTATUS _lsa_SetTrustedDomainInfoByName(struct pipes_struct *p, + struct lsa_SetTrustedDomainInfoByName *r) +{ + NTSTATUS status; + struct policy_handle trustdom_handle; + struct lsa_OpenTrustedDomainByName o; + struct lsa_SetInformationTrustedDomain s; + struct lsa_Close c; + + o.in.handle = r->in.handle; + o.in.name.string = r->in.trusted_domain->string; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.trustdom_handle = &trustdom_handle; + + status = _lsa_OpenTrustedDomainByName(p, &o); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + return status; + } + + s.in.trustdom_handle = &trustdom_handle; + s.in.level = r->in.level; + s.in.info = r->in.info; + + status = _lsa_SetInformationTrustedDomain(p, &s); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &trustdom_handle; + + return _lsa_Close(p, &c); +} + +NTSTATUS _lsa_SetInformationTrustedDomain(struct pipes_struct *p, + struct lsa_SetInformationTrustedDomain *r) +{ + struct lsa_info *policy; + NTSTATUS status; + + policy = find_policy_by_hnd(p, + r->in.trustdom_handle, + LSA_HANDLE_TRUST_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + return setInfoTrustedDomain_base(p, p->mem_ctx, policy, + r->in.level, r->in.info); +} + + +/* + * From here on the server routines are just dummy ones to make smbd link with + * librpc/gen_ndr/srv_lsa.c. These routines are actually never called, we are + * pulling the server stubs across one by one. + */ + +NTSTATUS _lsa_SetSecObj(struct pipes_struct *p, struct lsa_SetSecObj *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_ChangePassword(struct pipes_struct *p, + struct lsa_ChangePassword *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_SetInfoPolicy(struct pipes_struct *p, struct lsa_SetInfoPolicy *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_ClearAuditLog(struct pipes_struct *p, struct lsa_ClearAuditLog *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_GetQuotasForAccount(struct pipes_struct *p, + struct lsa_GetQuotasForAccount *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_SetQuotasForAccount(struct pipes_struct *p, + struct lsa_SetQuotasForAccount *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_StorePrivateData(struct pipes_struct *p, + struct lsa_StorePrivateData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_RetrievePrivateData(struct pipes_struct *p, + struct lsa_RetrievePrivateData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_SetInfoPolicy2(struct pipes_struct *p, + struct lsa_SetInfoPolicy2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_EnumTrustedDomainsEx(struct pipes_struct *p, + struct lsa_EnumTrustedDomainsEx *r) +{ + struct lsa_info *info; + uint32_t count; + struct pdb_trusted_domain **domains; + struct lsa_TrustDomainInfoInfoEx *entries; + int i; + NTSTATUS nt_status; + + /* bail out early if pdb backend is not capable of ex trusted domains, + * if we don't do that, the client might not call + * _lsa_EnumTrustedDomains() afterwards - gd */ + + if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) { + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; + } + + info = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_POLICY_TYPE, + struct lsa_info, + &nt_status); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_INVALID_HANDLE; + } + + /* check if the user has enough rights */ + if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) + return NT_STATUS_ACCESS_DENIED; + + become_root(); + nt_status = pdb_enum_trusted_domains(p->mem_ctx, &count, &domains); + unbecome_root(); + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + entries = talloc_zero_array(p->mem_ctx, struct lsa_TrustDomainInfoInfoEx, + count); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<count; i++) { + init_lsa_StringLarge(&entries[i].domain_name, + domains[i]->domain_name); + init_lsa_StringLarge(&entries[i].netbios_name, + domains[i]->netbios_name); + entries[i].sid = &domains[i]->security_identifier; + entries[i].trust_direction = domains[i]->trust_direction; + entries[i].trust_type = domains[i]->trust_type; + entries[i].trust_attributes = domains[i]->trust_attributes; + } + + if (*r->in.resume_handle >= count) { + *r->out.resume_handle = -1; + TALLOC_FREE(entries); + return NT_STATUS_NO_MORE_ENTRIES; + } + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 60 */ + r->out.domains->count = count - *r->in.resume_handle; + r->out.domains->count = MIN(r->out.domains->count, + (r->in.max_size/LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER)); + + r->out.domains->domains = entries + *r->in.resume_handle; + + if (r->out.domains->count < count - *r->in.resume_handle) { + *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count; + return STATUS_MORE_ENTRIES; + } + + /* according to MS-LSAD 3.1.4.7.8 output resume handle MUST + * always be larger than the previous input resume handle, in + * particular when hitting the last query it is vital to set the + * resume handle correctly to avoid infinite client loops, as + * seen e.g. with Windows XP SP3 when resume handle is 0 and + * status is NT_STATUS_OK - gd */ + + *r->out.resume_handle = (uint32_t)-1; + + return NT_STATUS_OK; +} + +NTSTATUS _lsa_QueryDomainInformationPolicy(struct pipes_struct *p, + struct lsa_QueryDomainInformationPolicy *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_SetDomainInformationPolicy(struct pipes_struct *p, + struct lsa_SetDomainInformationPolicy *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_TestCall(struct pipes_struct *p, struct lsa_TestCall *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRWRITE(struct pipes_struct *p, struct lsa_CREDRWRITE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRREAD(struct pipes_struct *p, struct lsa_CREDRREAD *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRENUMERATE(struct pipes_struct *p, struct lsa_CREDRENUMERATE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRWRITEDOMAINCREDENTIALS(struct pipes_struct *p, + struct lsa_CREDRWRITEDOMAINCREDENTIALS *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRREADDOMAINCREDENTIALS(struct pipes_struct *p, + struct lsa_CREDRREADDOMAINCREDENTIALS *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRDELETE(struct pipes_struct *p, struct lsa_CREDRDELETE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRGETTARGETINFO(struct pipes_struct *p, + struct lsa_CREDRGETTARGETINFO *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRPROFILELOADED(struct pipes_struct *p, + struct lsa_CREDRPROFILELOADED *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_CREDRGETSESSIONTYPES(struct pipes_struct *p, + struct lsa_CREDRGETSESSIONTYPES *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARREGISTERAUDITEVENT(struct pipes_struct *p, + struct lsa_LSARREGISTERAUDITEVENT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARGENAUDITEVENT(struct pipes_struct *p, + struct lsa_LSARGENAUDITEVENT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARUNREGISTERAUDITEVENT(struct pipes_struct *p, + struct lsa_LSARUNREGISTERAUDITEVENT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_lsaRQueryForestTrustInformation(struct pipes_struct *p, + struct lsa_lsaRQueryForestTrustInformation *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +#define DNS_CMP_MATCH 0 +#define DNS_CMP_FIRST_IS_CHILD 1 +#define DNS_CMP_SECOND_IS_CHILD 2 +#define DNS_CMP_NO_MATCH 3 + +/* this function assumes names are well formed DNS names. + * it doesn't validate them */ +static int dns_cmp(const char *s1, size_t l1, + const char *s2, size_t l2) +{ + const char *p1, *p2; + size_t t1, t2; + int cret; + + if (l1 == l2) { + if (strcasecmp_m(s1, s2) == 0) { + return DNS_CMP_MATCH; + } + return DNS_CMP_NO_MATCH; + } + + if (l1 > l2) { + p1 = s1; + p2 = s2; + t1 = l1; + t2 = l2; + cret = DNS_CMP_FIRST_IS_CHILD; + } else { + p1 = s2; + p2 = s1; + t1 = l2; + t2 = l1; + cret = DNS_CMP_SECOND_IS_CHILD; + } + + if (p1[t1 - t2 - 1] != '.') { + return DNS_CMP_NO_MATCH; + } + + if (strcasecmp_m(&p1[t1 - t2], p2) == 0) { + return cret; + } + + return DNS_CMP_NO_MATCH; +} + +static NTSTATUS make_ft_info(TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation *lfti, + struct ForestTrustInfo *fti) +{ + struct lsa_ForestTrustRecord *lrec; + struct ForestTrustInfoRecord *rec; + struct lsa_StringLarge *tln; + struct lsa_ForestTrustDomainInfo *info; + uint32_t i; + + fti->version = 1; + fti->count = lfti->count; + fti->records = talloc_array(mem_ctx, + struct ForestTrustInfoRecordArmor, + fti->count); + if (!fti->records) { + return NT_STATUS_NO_MEMORY; + } + for (i = 0; i < fti->count; i++) { + lrec = lfti->entries[i]; + rec = &fti->records[i].record; + + rec->flags = lrec->flags; + rec->timestamp = lrec->time; + rec->type = (enum ForestTrustInfoRecordType)lrec->type; + + switch (lrec->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + tln = &lrec->forest_trust_data.top_level_name; + rec->data.name.string = + talloc_strdup(mem_ctx, tln->string); + if (!rec->data.name.string) { + return NT_STATUS_NO_MEMORY; + } + rec->data.name.size = strlen(rec->data.name.string); + break; + case LSA_FOREST_TRUST_DOMAIN_INFO: + info = &lrec->forest_trust_data.domain_info; + rec->data.info.sid = *info->domain_sid; + rec->data.info.dns_name.string = + talloc_strdup(mem_ctx, + info->dns_domain_name.string); + if (!rec->data.info.dns_name.string) { + return NT_STATUS_NO_MEMORY; + } + rec->data.info.dns_name.size = + strlen(rec->data.info.dns_name.string); + rec->data.info.netbios_name.string = + talloc_strdup(mem_ctx, + info->netbios_domain_name.string); + if (!rec->data.info.netbios_name.string) { + return NT_STATUS_NO_MEMORY; + } + rec->data.info.netbios_name.size = + strlen(rec->data.info.netbios_name.string); + break; + default: + return NT_STATUS_INVALID_DOMAIN_STATE; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS add_collision(struct lsa_ForestTrustCollisionInfo *c_info, + uint32_t index, uint32_t collision_type, + uint32_t conflict_type, const char *tdo_name); + +static NTSTATUS check_ft_info(TALLOC_CTX *mem_ctx, + const char *tdo_name, + struct ForestTrustInfo *tdo_fti, + struct ForestTrustInfo *new_fti, + struct lsa_ForestTrustCollisionInfo *c_info) +{ + struct ForestTrustInfoRecord *nrec; + struct ForestTrustInfoRecord *trec; + const char *dns_name; + const char *nb_name = NULL; + struct dom_sid *sid = NULL; + const char *tname = NULL; + size_t dns_len = 0; + size_t tlen = 0; + uint32_t new_fti_idx; + uint32_t i; + /* use always TDO type, until we understand when Xref can be used */ + uint32_t collision_type = LSA_FOREST_TRUST_COLLISION_TDO; + bool tln_conflict; + bool sid_conflict; + bool nb_conflict; + bool exclusion; + bool ex_rule = false; + int ret; + + for (new_fti_idx = 0; new_fti_idx < new_fti->count; new_fti_idx++) { + + nrec = &new_fti->records[new_fti_idx].record; + dns_name = NULL; + tln_conflict = false; + sid_conflict = false; + nb_conflict = false; + exclusion = false; + + switch (nrec->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + /* exclusions do not conflict by definition */ + break; + + case FOREST_TRUST_TOP_LEVEL_NAME: + dns_name = nrec->data.name.string; + dns_len = nrec->data.name.size; + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + dns_name = nrec->data.info.dns_name.string; + dns_len = nrec->data.info.dns_name.size; + nb_name = nrec->data.info.netbios_name.string; + sid = &nrec->data.info.sid; + break; + } + + if (!dns_name) continue; + + /* check if this is already taken and not excluded */ + for (i = 0; i < tdo_fti->count; i++) { + trec = &tdo_fti->records[i].record; + + switch (trec->type) { + case FOREST_TRUST_TOP_LEVEL_NAME: + ex_rule = false; + tname = trec->data.name.string; + tlen = trec->data.name.size; + break; + case FOREST_TRUST_TOP_LEVEL_NAME_EX: + ex_rule = true; + tname = trec->data.name.string; + tlen = trec->data.name.size; + break; + case FOREST_TRUST_DOMAIN_INFO: + ex_rule = false; + tname = trec->data.info.dns_name.string; + tlen = trec->data.info.dns_name.size; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + ret = dns_cmp(dns_name, dns_len, tname, tlen); + switch (ret) { + case DNS_CMP_MATCH: + /* if it matches exclusion, + * it doesn't conflict */ + if (ex_rule) { + exclusion = true; + break; + } + + FALL_THROUGH; + case DNS_CMP_FIRST_IS_CHILD: + case DNS_CMP_SECOND_IS_CHILD: + tln_conflict = true; + + FALL_THROUGH; + default: + break; + } + + /* explicit exclusion, no dns name conflict here */ + if (exclusion) { + tln_conflict = false; + } + + if (trec->type != FOREST_TRUST_DOMAIN_INFO) { + continue; + } + + /* also test for domain info */ + if (!(trec->flags & LSA_SID_DISABLED_ADMIN) && + dom_sid_compare(&trec->data.info.sid, sid) == 0) { + sid_conflict = true; + } + if (!(trec->flags & LSA_NB_DISABLED_ADMIN) && + strcasecmp_m(trec->data.info.netbios_name.string, + nb_name) == 0) { + nb_conflict = true; + } + } + + if (tln_conflict) { + (void)add_collision(c_info, new_fti_idx, + collision_type, + LSA_TLN_DISABLED_CONFLICT, + tdo_name); + } + if (sid_conflict) { + (void)add_collision(c_info, new_fti_idx, + collision_type, + LSA_SID_DISABLED_CONFLICT, + tdo_name); + } + if (nb_conflict) { + (void)add_collision(c_info, new_fti_idx, + collision_type, + LSA_NB_DISABLED_CONFLICT, + tdo_name); + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS add_collision(struct lsa_ForestTrustCollisionInfo *c_info, + uint32_t idx, uint32_t collision_type, + uint32_t conflict_type, const char *tdo_name) +{ + struct lsa_ForestTrustCollisionRecord **es; + uint32_t i = c_info->count; + + es = talloc_realloc(c_info, c_info->entries, + struct lsa_ForestTrustCollisionRecord *, i + 1); + if (!es) { + return NT_STATUS_NO_MEMORY; + } + c_info->entries = es; + c_info->count = i + 1; + + es[i] = talloc(es, struct lsa_ForestTrustCollisionRecord); + if (!es[i]) { + return NT_STATUS_NO_MEMORY; + } + + es[i]->index = idx; + es[i]->type = collision_type; + es[i]->flags = conflict_type; + es[i]->name.string = talloc_strdup(es[i], tdo_name); + if (!es[i]->name.string) { + return NT_STATUS_NO_MEMORY; + } + es[i]->name.size = strlen(es[i]->name.string); + + return NT_STATUS_OK; +} + +static NTSTATUS get_ft_info(TALLOC_CTX *mem_ctx, + struct pdb_trusted_domain *td, + struct ForestTrustInfo *info) +{ + enum ndr_err_code ndr_err; + + if (td->trust_forest_trust_info.length == 0 || + td->trust_forest_trust_info.data == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + ndr_err = ndr_pull_struct_blob_all(&td->trust_forest_trust_info, mem_ctx, + info, + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + return NT_STATUS_OK; +} + +static NTSTATUS own_ft_info(struct pdb_domain_info *dom_info, + struct ForestTrustInfo *fti) +{ + struct ForestTrustDataDomainInfo *info; + struct ForestTrustInfoRecord *rec; + + fti->version = 1; + fti->count = 2; + fti->records = talloc_array(fti, + struct ForestTrustInfoRecordArmor, 2); + if (!fti->records) { + return NT_STATUS_NO_MEMORY; + } + + /* TLN info */ + rec = &fti->records[0].record; + + rec->flags = 0; + rec->timestamp = 0; + rec->type = FOREST_TRUST_TOP_LEVEL_NAME; + + rec->data.name.string = talloc_strdup(fti, dom_info->dns_forest); + if (!rec->data.name.string) { + return NT_STATUS_NO_MEMORY; + } + rec->data.name.size = strlen(rec->data.name.string); + + /* DOMAIN info */ + rec = &fti->records[1].record; + + rec->flags = 0; + rec->timestamp = 0; + rec->type = FOREST_TRUST_DOMAIN_INFO; + + info = &rec->data.info; + + info->sid = dom_info->sid; + info->dns_name.string = talloc_strdup(fti, dom_info->dns_domain); + if (!info->dns_name.string) { + return NT_STATUS_NO_MEMORY; + } + info->dns_name.size = strlen(info->dns_name.string); + info->netbios_name.string = talloc_strdup(fti, dom_info->name); + if (!info->netbios_name.string) { + return NT_STATUS_NO_MEMORY; + } + info->netbios_name.size = strlen(info->netbios_name.string); + + return NT_STATUS_OK; +} + +NTSTATUS _lsa_lsaRSetForestTrustInformation(struct pipes_struct *p, + struct lsa_lsaRSetForestTrustInformation *r) +{ + NTSTATUS status; + int i; + int j; + struct lsa_info *handle; + uint32_t num_domains; + struct pdb_trusted_domain **domains; + struct ForestTrustInfo *nfti; + struct ForestTrustInfo *fti; + struct lsa_ForestTrustCollisionInfo *c_info; + struct pdb_domain_info *dom_info; + enum ndr_err_code ndr_err; + + if (!IS_DC) { + return NT_STATUS_NOT_SUPPORTED; + } + + handle = find_policy_by_hnd(p, + r->in.handle, + LSA_HANDLE_TRUST_TYPE, + struct lsa_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_INVALID_HANDLE; + } + + if (!(handle->access & LSA_TRUSTED_SET_AUTH)) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pdb_enum_trusted_domains(p->mem_ctx, &num_domains, &domains); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (num_domains == 0) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + for (i = 0; i < num_domains; i++) { + if (domains[i]->domain_name == NULL) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + if (strcasecmp_m(domains[i]->domain_name, + r->in.trusted_domain_name->string) == 0) { + break; + } + } + if (i >= num_domains) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + if (!(domains[i]->trust_attributes & + LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.highest_record_type >= LSA_FOREST_TRUST_RECORD_TYPE_LAST) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* The following section until COPY_END is a copy from + * source4/rpmc_server/lsa/scesrc_lsa.c */ + nfti = talloc(p->mem_ctx, struct ForestTrustInfo); + if (!nfti) { + return NT_STATUS_NO_MEMORY; + } + + status = make_ft_info(nfti, r->in.forest_trust_info, nfti); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + c_info = talloc_zero(r->out.collision_info, + struct lsa_ForestTrustCollisionInfo); + if (!c_info) { + return NT_STATUS_NO_MEMORY; + } + + /* first check own info, then other domains */ + fti = talloc(p->mem_ctx, struct ForestTrustInfo); + if (!fti) { + return NT_STATUS_NO_MEMORY; + } + + dom_info = pdb_get_domain_info(p->mem_ctx); + + status = own_ft_info(dom_info, fti); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = check_ft_info(c_info, dom_info->dns_domain, fti, nfti, c_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (j = 0; j < num_domains; j++) { + fti = talloc(p->mem_ctx, struct ForestTrustInfo); + if (!fti) { + return NT_STATUS_NO_MEMORY; + } + + status = get_ft_info(p->mem_ctx, domains[j], fti); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + continue; + } + return status; + } + + if (domains[j]->domain_name == NULL) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + status = check_ft_info(c_info, domains[j]->domain_name, + fti, nfti, c_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (c_info->count != 0) { + *r->out.collision_info = c_info; + } + + if (r->in.check_only != 0) { + return NT_STATUS_OK; + } + + /* COPY_END */ + + ndr_err = ndr_push_struct_blob(&domains[i]->trust_forest_trust_info, + p->mem_ctx, nfti, + (ndr_push_flags_fn_t)ndr_push_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = pdb_set_trusted_domain(domains[i]->domain_name, domains[i]); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS _lsa_CREDRRENAME(struct pipes_struct *p, + struct lsa_CREDRRENAME *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSAROPENPOLICYSCE(struct pipes_struct *p, + struct lsa_LSAROPENPOLICYSCE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARADTREGISTERSECURITYEVENTSOURCE(struct pipes_struct *p, + struct lsa_LSARADTREGISTERSECURITYEVENTSOURCE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE(struct pipes_struct *p, + struct lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_LSARADTREPORTSECURITYEVENT(struct pipes_struct *p, + struct lsa_LSARADTREPORTSECURITYEVENT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +void _lsa_Opnum82NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum82NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum83NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum83NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum84NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum84NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum85NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum85NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum86NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum86NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum87NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum87NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum88NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum88NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum89NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum89NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum90NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum90NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum91NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum91NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum92NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum92NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum93NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum93NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum94NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum94NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum95NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum95NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum96NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum96NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum97NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum97NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum98NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum98NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum99NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum99NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum100NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum100NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum101NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum101NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum102NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum102NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum103NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum103NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum104NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum104NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum105NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum105NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum106NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum106NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum107NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum107NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum108NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum108NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum109NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum109NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum110NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum110NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum111NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum111NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum112NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum112NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum113NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum113NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum114NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum114NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum115NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum115NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum116NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum116NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum117NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum117NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum118NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum118NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum119NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum119NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum120NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum120NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum121NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum121NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum122NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum122NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum123NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum123NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum124NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum124NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum125NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum125NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum126NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum126NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum127NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum127NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _lsa_Opnum128NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum128NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +/*************************************************************************** + _lsa_CreateTrustedDomainEx3 + ***************************************************************************/ + +NTSTATUS _lsa_CreateTrustedDomainEx3(struct pipes_struct *p, + struct lsa_CreateTrustedDomainEx3 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/*************************************************************************** + _lsa_OpenPolicy3 + ***************************************************************************/ + +NTSTATUS _lsa_OpenPolicy3(struct pipes_struct *p, + struct lsa_OpenPolicy3 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + size_t sd_size; + uint32_t des_access = r->in.access_mask; + uint32_t acc_granted; + NTSTATUS status; + + if (p->transport != NCACN_NP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + ZERO_STRUCTP(r->out.handle); + + /* + * The attributes have no effect and MUST be ignored, except the + * root_dir which MUST be NULL. + */ + if (r->in.attr != NULL && r->in.attr->root_dir != NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.in_version) { + case 1: + *r->out.out_version = 1; + + r->out.out_revision_info->info1.revision = 1; + /* TODO: Enable as soon as we support it */ +#if 0 + r->out.out_revision_info->info1.supported_features = + LSA_FEATURE_TDO_AUTH_INFO_AES_CIPHER; +#endif + + break; + default: + return NT_STATUS_NOT_SUPPORTED; + } + + /* Work out max allowed. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + /* map the generic bits to the lsa policy ones */ + se_map_generic(&des_access, &lsa_policy_mapping); + + /* get the generic lsa policy SD until we store it */ + status = make_lsa_object_sd(p->mem_ctx, + &psd, + &sd_size, + &lsa_policy_mapping, + NULL, + 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = access_check_object(psd, + session_info->security_token, + SEC_PRIV_INVALID, + SEC_PRIV_INVALID, + 0, + des_access, + &acc_granted, + "_lsa_OpenPolicy2"); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = create_lsa_policy_handle(p->mem_ctx, + p, + LSA_HANDLE_POLICY_TYPE, + acc_granted, + get_global_sam_sid(), + NULL, + psd, + r->out.handle); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +void _lsa_Opnum131NotUsedOnWire(struct pipes_struct *p, + struct lsa_Opnum131NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +NTSTATUS _lsa_lsaRQueryForestTrustInformation2(struct pipes_struct *p, + struct lsa_lsaRQueryForestTrustInformation2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS _lsa_lsaRSetForestTrustInformation2(struct pipes_struct *p, + struct lsa_lsaRSetForestTrustInformation2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +#include "librpc/rpc/dcesrv_core.h" + +#define DCESRV_INTERFACE_LSARPC_BIND(context, iface) \ + dcesrv_interface_lsarpc_bind(context, iface) + +static NTSTATUS dcesrv_interface_lsarpc_bind( + struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_reject_connect(context, iface); +} + +static NTSTATUS lsarpc__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); +static const struct dcesrv_interface dcesrv_lsarpc_interface; + +#define NCACN_NP_PIPE_NETLOGON "ncacn_np:[\\pipe\\netlogon]" +#define NCACN_NP_PIPE_LSASS "ncacn_np:[\\pipe\\lsass]" + +#define DCESRV_INTERFACE_LSARPC_NCACN_NP_SECONDARY_ENDPOINT \ + NCACN_NP_PIPE_LSASS + +#define DCESRV_INTERFACE_LSARPC_INIT_SERVER \ + dcesrv_interface_lsarpc_init_server + +static NTSTATUS dcesrv_interface_lsarpc_init_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + NTSTATUS ret = dcesrv_interface_register(dce_ctx, + NCACN_NP_PIPE_NETLOGON, + NCACN_NP_PIPE_LSASS, + &dcesrv_lsarpc_interface, + NULL); + if (!NT_STATUS_IS_OK(ret)) { + DBG_ERR("Failed to register endpoint " + "'\\pipe\\netlogon'\n"); + return ret; + } + + return lsarpc__op_init_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_lsa_scompat.c" diff --git a/source3/rpc_server/mdssvc/README b/source3/rpc_server/mdssvc/README new file mode 100644 index 0000000..7dff83e --- /dev/null +++ b/source3/rpc_server/mdssvc/README @@ -0,0 +1,14 @@ +Introduction: +============= +This directory contains source code for the metadata search service +aka Spotlight. + +Bison and flex: +=============== +Not yet integrated into the waf buildsystem, run these by hand: + +$ bison -d -o sparql_parser.c sparql_parser.y +$ flex -o sparql_lexer.c sparql_lexer.l + +or use the bundled Makefile. + diff --git a/source3/rpc_server/mdssvc/dalloc.c b/source3/rpc_server/mdssvc/dalloc.c new file mode 100644 index 0000000..44db9f8 --- /dev/null +++ b/source3/rpc_server/mdssvc/dalloc.c @@ -0,0 +1,404 @@ +/* + Copyright (c) Ralph Boehme 2012-2014 + + 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 "replace.h" +#include <talloc.h> +#include "dalloc.h" +#include "marshalling.h" +#include "lib/util/charset/charset.h" +#include "lib/util/talloc_stack.h" +#include "system/time.h" + +/** + * Dynamic Datastore + **/ +struct dalloc_ctx { + void **dd_talloc_array; +}; + +void *_dalloc_new(TALLOC_CTX *mem_ctx, const char *type) +{ + void *p; + + p = talloc_zero(mem_ctx, DALLOC_CTX); + if (p == NULL) { + return NULL; + } + talloc_set_name_const(p, type); + + return p; +} + +int _dalloc_add_talloc_chunk(DALLOC_CTX *dd, void *obj, const char *type, size_t size) +{ + size_t array_len = talloc_array_length(dd->dd_talloc_array); + + dd->dd_talloc_array = talloc_realloc(dd, + dd->dd_talloc_array, + void *, + array_len + 1); + if (dd->dd_talloc_array == NULL) { + return -1; + } + + if (size != 0) { + void *p; + + p = talloc_named_const(dd->dd_talloc_array, size, type); + if (p == NULL) { + return -1; + } + memcpy(p, obj, size); + obj = p; + } else { + _talloc_get_type_abort(obj, type, __location__); + } + + dd->dd_talloc_array[array_len] = obj; + + return 0; +} + +/* Get number of elements, returns 0 if the structure is empty or not initialized */ +size_t dalloc_size(const DALLOC_CTX *d) +{ + if (d == NULL) { + return 0; + } + return talloc_array_length(d->dd_talloc_array); +} + +/* Return element at position */ +void *dalloc_get_object(const DALLOC_CTX *d, int i) +{ + size_t size = dalloc_size(d); + + if (i >= size) { + return NULL; + } + + return d->dd_talloc_array[i]; +} + +/* Return typename of element at position */ +const char *dalloc_get_name(const DALLOC_CTX *d, int i) +{ + void *o = dalloc_get_object(d, i); + + if (o == NULL) { + return NULL; + } + + return talloc_get_name(o); +} + +/* + * Get pointer to value from a DALLOC object + * + * Returns pointer to object from a DALLOC object. Nested object integration + * is supported by using the type string "DALLOC_CTX". Any other type string + * designates the requested objects type. + */ +void *dalloc_get(const DALLOC_CTX *d, ...) +{ + int result = 0; + void *p = NULL; + va_list args; + const char *type; + int elem; + + va_start(args, d); + type = va_arg(args, const char *); + + while (strcmp(type, "DALLOC_CTX") == 0) { + elem = va_arg(args, int); + if (elem >= talloc_array_length(d->dd_talloc_array)) { + result = -1; + goto done; + } + d = d->dd_talloc_array[elem]; + type = va_arg(args, const char *); + } + + elem = va_arg(args, int); + if (elem >= talloc_array_length(d->dd_talloc_array)) { + result = -1; + goto done; + } + + p = talloc_check_name(d->dd_talloc_array[elem], type); + if (p == NULL) { + result = -1; + goto done; + } + +done: + va_end(args); + if (result != 0) { + p = NULL; + } + return p; +} + +void *dalloc_value_for_key(const DALLOC_CTX *d, ...) +{ + int result = 0; + void *p = NULL; + va_list args; + const char *type = NULL; + int elem; + size_t array_len; + + va_start(args, d); + type = va_arg(args, const char *); + + while (strcmp(type, "DALLOC_CTX") == 0) { + array_len = talloc_array_length(d->dd_talloc_array); + elem = va_arg(args, int); + if (elem >= array_len) { + result = -1; + goto done; + } + d = d->dd_talloc_array[elem]; + type = va_arg(args, const char *); + } + + array_len = talloc_array_length(d->dd_talloc_array); + + for (elem = 0; elem + 1 < array_len; elem += 2) { + if (strcmp(talloc_get_name(d->dd_talloc_array[elem]), "char *") != 0) { + result = -1; + goto done; + } + if (strcmp((char *)d->dd_talloc_array[elem],type) == 0) { + p = d->dd_talloc_array[elem + 1]; + break; + } + } + if (p == NULL) { + goto done; + } + + type = va_arg(args, const char *); + if (strcmp(talloc_get_name(p), type) != 0) { + p = NULL; + } + +done: + va_end(args); + if (result != 0) { + p = NULL; + } + return p; +} + +static char *dalloc_strdup(TALLOC_CTX *mem_ctx, const char *string) +{ + char *p; + + p = talloc_strdup(mem_ctx, string); + if (p == NULL) { + return NULL; + } + talloc_set_name_const(p, "char *"); + return p; +} + +int dalloc_stradd(DALLOC_CTX *d, const char *string) +{ + int result; + char *p; + + p = dalloc_strdup(d, string); + if (p == NULL) { + return -1; + } + + result = dalloc_add(d, p, char *); + if (result != 0) { + return -1; + } + + return 0; +} + +static char *tab_level(TALLOC_CTX *mem_ctx, int level) +{ + int i; + char *string = talloc_array(mem_ctx, char, level + 1); + + for (i = 0; i < level; i++) { + string[i] = '\t'; + } + + string[i] = '\0'; + return string; +} + +char *dalloc_dump(DALLOC_CTX *dd, int nestinglevel) +{ + const char *type; + int n, result; + uint64_t i; + sl_bool_t bl; + sl_time_t t; + struct tm *tm; + char datestring[256]; + sl_cnids_t cnids; + char *logstring, *nested_logstring; + char *tab_string1, *tab_string2; + void *p; + bool ok; + char *utf8string; + size_t utf8len; + + tab_string1 = tab_level(dd, nestinglevel); + if (tab_string1 == NULL) { + return NULL; + } + tab_string2 = tab_level(dd, nestinglevel + 1); + if (tab_string2 == NULL) { + return NULL; + } + + logstring = talloc_asprintf(dd, + "%s%s(#%zu): {\n", + tab_string1, + talloc_get_name(dd), + dalloc_size(dd)); + if (logstring == NULL) { + return NULL; + } + + for (n = 0; n < dalloc_size(dd); n++) { + type = dalloc_get_name(dd, n); + if (type == NULL) { + return NULL; + } + p = dalloc_get_object(dd, n); + if (p == NULL) { + return NULL; + } + if (strcmp(type, "DALLOC_CTX") == 0 + || strcmp(type, "sl_array_t") == 0 + || strcmp(type, "sl_filemeta_t") == 0 + || strcmp(type, "sl_dict_t") == 0) { + nested_logstring = dalloc_dump(p, nestinglevel + 1); + if (nested_logstring == NULL) { + return NULL; + } + logstring = talloc_strdup_append(logstring, + nested_logstring); + } else if (strcmp(type, "uint64_t") == 0) { + memcpy(&i, p, sizeof(uint64_t)); + logstring = talloc_asprintf_append( + logstring, + "%suint64_t: 0x%04jx\n", + tab_string2, (uintmax_t)i); + } else if (strcmp(type, "char *") == 0) { + logstring = talloc_asprintf_append( + logstring, + "%sstring: %s\n", + tab_string2, + (char *)p); + } else if (strcmp(type, "smb_ucs2_t *") == 0) { + ok = convert_string_talloc(talloc_tos(), + CH_UTF16LE, + CH_UTF8, + p, + talloc_get_size(p), + &utf8string, + &utf8len); + if (!ok) { + return NULL; + } + logstring = talloc_asprintf_append( + logstring, + "%sUTF16-string: %s\n", + tab_string2, + utf8string); + TALLOC_FREE(utf8string); + } else if (strcmp(type, "sl_bool_t") == 0) { + memcpy(&bl, p, sizeof(sl_bool_t)); + logstring = talloc_asprintf_append( + logstring, + "%sbool: %s\n", + tab_string2, + bl ? "true" : "false"); + } else if (strcmp(type, "sl_nil_t") == 0) { + logstring = talloc_asprintf_append( + logstring, + "%snil\n", + tab_string2); + } else if (strcmp(type, "sl_time_t") == 0) { + memcpy(&t, p, sizeof(sl_time_t)); + tm = localtime(&t.tv_sec); + if (tm == NULL) { + return NULL; + } + result = strftime(datestring, + sizeof(datestring), + "%Y-%m-%d %H:%M:%S", tm); + if (result == 0) { + return NULL; + } + logstring = talloc_asprintf_append( + logstring, + "%ssl_time_t: %s.%06lu\n", + tab_string2, + datestring, + (unsigned long)t.tv_usec); + } else if (strcmp(type, "sl_cnids_t") == 0) { + memcpy(&cnids, p, sizeof(sl_cnids_t)); + logstring = talloc_asprintf_append( + logstring, + "%sCNIDs: unkn1: 0x%" PRIx16 ", unkn2: 0x%" PRIx32 "\n", + tab_string2, + cnids.ca_unkn1, + cnids.ca_context); + if (logstring == NULL) { + return NULL; + } + if (cnids.ca_cnids) { + nested_logstring = dalloc_dump( + cnids.ca_cnids, + nestinglevel + 2); + if (!nested_logstring) { + return NULL; + } + logstring = talloc_strdup_append(logstring, + nested_logstring); + } + } else { + logstring = talloc_asprintf_append( + logstring, + "%stype: %s\n", + tab_string2, + type); + } + if (logstring == NULL) { + return NULL; + } + } + logstring = talloc_asprintf_append(logstring, + "%s}\n", + tab_string1); + if (logstring == NULL) { + return NULL; + } + return logstring; +} diff --git a/source3/rpc_server/mdssvc/dalloc.h b/source3/rpc_server/mdssvc/dalloc.h new file mode 100644 index 0000000..69650b8 --- /dev/null +++ b/source3/rpc_server/mdssvc/dalloc.h @@ -0,0 +1,165 @@ +/* + Copyright (c) Ralph Boehme 2012-2014 + + 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/>. +*/ + +/* + Typesafe, dynamic object store based on talloc + + Usage + ===== + + Define some types: + + A key/value store aka dictionary that supports retrieving elements + by key: + + typedef dict_t DALLOC_CTX; + + An ordered set that can store different objects which can be + retrieved by number: + + typedef set_t DALLOC_CTX; + + Create an dalloc object and add elementes of different type: + + TALLOC_CTX *mem_ctx = talloc_new(NULL); + DALLOC_CTX *d = dalloc_new(mem_ctx); + + Store an int value in the object: + + uint64_t i = 1; + dalloc_add_copy(d, &i, uint64_t); + + Store a string: + + dalloc_stradd(d, "hello world"); + + Add a nested object: + + DALLOC_CTX *nested = dalloc_new(d); + dalloc_add(d, nested, DALLOC_CTX); + + Add an int value to the nested object, this can be fetched: + + i = 2; + dalloc_add_copy(nested, &i, uint64_t); + + Add a nested set: + + set_t *set = dalloc_zero(nested, set_t); + dalloc_add(nested, set, set_t); + + Add an int value to the set: + + i = 3; + dalloc_add_copy(set, &i, uint64_t); + + Add a dictionary (key/value store): + + dict_t *dict = dalloc_zero(nested, dict_t); + dalloc_add(nested, dict, dict_t); + + Store a string as key in the dict: + + dalloc_stradd(dict, "key"); + + Add a value for the key: + + i = 4; + dalloc_add_copy(dict, &i, uint64_t); + + Fetching value references + ========================= + + You can fetch anything that is not a DALLOC_CTXs, because passing + "DALLOC_CTXs" as type to the functions dalloc_get() and + dalloc_value_for_key() tells the function to step into that object + and expect more arguments that specify which element to fetch. + + Get reference to an objects element by position: + + uint64_t *p = dalloc_get(d, "uint64_t", 0); + + p now points to the first int with a value of 1. + + Get reference to the "hello world" string: + + str = dalloc_get(d, "char *", 1); + + You can't fetch a DALLOC_CTX itself: + + nested = dalloc_get(d, "DALLOC_CTX", 2); + + But you can fetch elements from the nested DALLOC_CTX: + + p = dalloc_get(d, "DALLOC_CTX", 2, "uint64_t", 0); + + p now points to the value 2. + + You can fetch types that are typedefd DALLOC_CTXs: + + set = dalloc_get(d, "DALLOC_CTX", 2, "set_t", 1); + + Fetch int from set, must use DALLOC_CTX as type for the set: + + p = dalloc_get(d, "DALLOC_CTX", 2, "DALLOC_CTX", 1, "uint64_t", 0); + + p points to 3. + + Fetch value by key from dictionary: + + p = dalloc_value_for_key(d, "DALLOC_CTX", 2, "DALLOC_CTX", 2, "key"); + + p now points to 4. +*/ + +#ifndef DALLOC_H +#define DALLOC_H + +#include <talloc.h> + +struct dalloc_ctx; +typedef struct dalloc_ctx DALLOC_CTX; + +#define dalloc_new(mem_ctx) (DALLOC_CTX *)_dalloc_new((mem_ctx), "DALLOC_CTX") +#define dalloc_zero(mem_ctx, type) (type *)_dalloc_new((mem_ctx), #type) + +/** + * talloc a chunk for obj of required size, copy the obj into the + * chunk and add the chunk to the dalloc ctx + **/ +#define dalloc_add_copy(d, obj, type) _dalloc_add_talloc_chunk((d), (obj), #type, sizeof(type)) + +/** + * Add a pointer to a talloced object to the dalloc ctx. The object + * must be a talloc child of the dalloc ctx. + **/ +#define dalloc_add(d, obj, type) _dalloc_add_talloc_chunk((d), (obj), #type, 0) + + +extern void *dalloc_get(const DALLOC_CTX *d, ...); +extern void *dalloc_value_for_key(const DALLOC_CTX *d, ...); +extern size_t dalloc_size(const DALLOC_CTX *d); +extern void *dalloc_get_object(const DALLOC_CTX *d, int i); +extern const char *dalloc_get_name(const DALLOC_CTX *d, int i); +extern int dalloc_stradd(DALLOC_CTX *d, const char *string); + +extern void *_dalloc_new(TALLOC_CTX *mem_ctx, const char *type); +extern int _dalloc_add_talloc_chunk(DALLOC_CTX *d, void *obj, const char *type, size_t size); + +extern char *dalloc_dump(DALLOC_CTX *dd, int nestinglevel); + +#endif /* DALLOC_H */ diff --git a/source3/rpc_server/mdssvc/elasticsearch_mappings.json b/source3/rpc_server/mdssvc/elasticsearch_mappings.json new file mode 100644 index 0000000..9f68a64 --- /dev/null +++ b/source3/rpc_server/mdssvc/elasticsearch_mappings.json @@ -0,0 +1,142 @@ +{ + "attribute_mappings": { + "*": { + "type": "fts", + "attribute": "" + }, + "kMDItemTextContent": { + "type": "str", + "attribute": "content" + }, + "_kMDItemGroupId": { + "type": "type", + "attribute": "file.content_type" + }, + "kMDItemContentType": { + "type": "type", + "attribute": "file.content_type" + }, + "kMDItemContentTypeTree": { + "type": "type", + "attribute": "file.content_type" + }, + "kMDItemFSContentChangeDate": { + "type": "date", + "attribute": "file.last_modified" + }, + "kMDItemFSCreationDate": { + "type": "date", + "attribute": "file.created" + }, + "kMDItemFSName": { + "type": "str", + "attribute": "file.filename" + }, + "kMDItemFSOwnerGroupID": { + "type": "str", + "attribute": "attributes.owner" + }, + "kMDItemFSOwnerUserID": { + "type": "str", + "attribute": "attributes.group" + }, + "kMDItemFSSize": { + "type": "num", + "attribute": "file.filesize" + }, + "kMDItemPath": { + "type": "str", + "attribute": "path.real" + }, + "kMDItemAttributeChangeDate": { + "type": "date", + "attribute": "file.last_modified" + }, + "kMDItemAuthors": { + "type": "str", + "attribute": "meta.author" + }, + "kMDItemContentCreationDate": { + "type": "date", + "attribute": "file.created" + }, + "kMDItemContentModificationDate": { + "type": "date", + "attribute": "file.last_modified" + }, + "kMDItemCreator": { + "type": "str", + "attribute": "meta.raw.creator" + }, + "kMDItemDescription": { + "type": "str", + "attribute": "meta.raw.description" + }, + "kMDItemDisplayName": { + "type": "str", + "attribute": "file.filename" + }, + "kMDItemDurationSeconds": { + "type": "num", + "attribute": "meta.raw.xmpDM:duration" + }, + "kMDItemNumberOfPages": { + "type": "num", + "attribute": "meta.raw.xmpTPg:NPages" + }, + "kMDItemTitle": { + "type": "str", + "attribute": "meta.title" + }, + "kMDItemAlbum": { + "type": "str", + "attribute": "meta.raw.xmpDM:album" + }, + "kMDItemBitsPerSample": { + "type": "num", + "attribute": "meta.raw.tiff:BitsPerSample" + }, + "kMDItemPixelHeight": { + "type": "num", + "attribute": "meta.raw.Image Height" + }, + "kMDItemPixelWidth": { + "type": "num", + "attribute": "meta.raw.Image Width" + }, + "kMDItemResolutionHeightDPI": { + "type": "num", + "attribute": "meta.raw.Y Resolution" + }, + "kMDItemResolutionWidthDPI": { + "type": "num", + "attribute": "meta.raw.X Resolution" + } + }, + "mime_mappings": { + "1": "message/rfc822", + "2": "text/x-vcard", + "6": "text/x-vcard", + "7": "video/*", + "8": "application/octet-stream", + "9": "text/directory", + "10": "audio/*", + "11": "application/pdf", + "12": "application/vnd.oasis.opendocument.presentation", + "13": "image/*", + "public.content": "message/rfc822 application/pdf application/vnd.oasis.opendocument.presentation image/* text/*", + "public.jpeg": "image/jpeg", + "public.tiff": "image/tiff", + "com.compuserve.gif": "image/gif", + "public.png": "image/png", + "com.microsoft.bmp": "image/bmp", + "public.mp3": "audio/mpeg", + "public.mpeg-4-audio": "audio/x-aac", + "public.text": "text/*", + "public.plain-text": "text/plain", + "public.rtf": "text/rtf", + "public.html": "text/html", + "public.xml": "text/xml", + "public.archive": "application/zip application/x-bzip application/x-bzip2 application/x-tar application/x-7z-compressed" + } +} diff --git a/source3/rpc_server/mdssvc/es_lexer.l b/source3/rpc_server/mdssvc/es_lexer.l new file mode 100644 index 0000000..4be4225 --- /dev/null +++ b/source3/rpc_server/mdssvc/es_lexer.l @@ -0,0 +1,92 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Elasticsearch backend + + Copyright (C) Ralph Boehme 2019 + + 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 "rpc_server/mdssvc/es_parser.tab.h" + +#define YY_NO_INPUT +#define mdsyylalloc SMB_MALLOC +#define mdsyylrealloc SMB_REALLOC + +static char *strip_quote(const char *phrase); +%} + +%option nounput noyyalloc noyyrealloc prefix="mdsyyl" + +ASC [a-zA-Z0-9_\*\:\-\.] +U [\x80-\xbf] +U2 [\xc2-\xdf] +U3 [\xe0-\xef] +U4 [\xf0-\xf4] +SPECIAL [\!\#\$\%\&\'\(\)\+\,\.\/\;\<\=\>\?\@\[\]\^\`\{\}\|\~\\] +ESCHAR [\"\*] +BLANK [ \t\n] + +UANY {ASC}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} +UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} +UPHRASE {UANY}|{SPECIAL}|{BLANK}|\\{ESCHAR} + +%% +InRange return FUNC_INRANGE; +\$time\.iso return DATE_ISO; +false {mdsyyllval.bval = false; return BOOLEAN;} +true {mdsyyllval.bval = true; return BOOLEAN;} +\" return QUOTE; +\( return OBRACE; +\) return CBRACE; +\&\& return AND; +\|\| return OR; +\=\= return EQUAL; +\!\= return UNEQUAL; +\= return EQUAL; +\< return LT; +\> return GT; +\, return COMMA; +{UANY}+ {mdsyyllval.sval = talloc_strdup(talloc_tos(), yytext); return WORD;} +\"{UPHRASE}+\" {mdsyyllval.sval = strip_quote(yytext); return PHRASE;} +{BLANK} /* ignore */ +%% + +static char *strip_quote(const char *phrase) +{ + size_t phrase_len = 0; + char *stripped_phrase = NULL; + + if (phrase == NULL) { + return NULL; + } + + phrase_len = strlen(phrase); + if (phrase_len < 2 || + phrase[0] != '\"' || + phrase[phrase_len - 1] != '\"') + { + return talloc_strdup(talloc_tos(), phrase); + } + + phrase++; + + stripped_phrase = talloc_strndup(talloc_tos(), phrase, phrase_len - 2); + if (stripped_phrase == NULL) { + return NULL; + } + return stripped_phrase; +} diff --git a/source3/rpc_server/mdssvc/es_mapping.c b/source3/rpc_server/mdssvc/es_mapping.c new file mode 100644 index 0000000..e8d181d --- /dev/null +++ b/source3/rpc_server/mdssvc/es_mapping.c @@ -0,0 +1,241 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Elasticsearch backend + + Copyright (C) Ralph Boehme 2019 + + 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 "es_mapping.h" + +/* + * Escaping of special characters in Lucene query syntax across HTTP and JSON + * ========================================================================== + * + * These characters in Lucene queries need escaping [1]: + * + * + - & | ! ( ) { } [ ] ^ " ~ * ? : \ / + * + * Additionally JSON requires escaping of: + * + * " \ + * + * Characters already escaped by the mdssvc client: + * + * * " \ + * + * The following table contains the resulting escaped strings, beginning with the + * search term, the corresponding Spotlight query and the final string that gets + * sent to the target Elasticsearch server. + * + * string | mdfind | http + * -------+--------+------ + * x!x x!x x\\!x + * x&x x&x x\\&x + * x+x x+x x\\+x + * x-x x-x x\\-x + * x.x x.x x\\.x + * x<x x<x x\\<x + * x>x x>x x\\>x + * x=x x=x x\\=x + * x?x x?x x\\?x + * x[x x[x x\\[x + * x]x x]x x\\]x + * x^x x^x x\\^x + * x{x x{x x\\{x + * x}x x}x x\\}x + * x|x x|x x\\|x + * x x x x x\\ x + * x*x x\*x x\\*x + * x\x x\\x x\\\\x + * x"x x\"x x\\\"x + * + * Special cases: + * x y It's not possible to search for terms including spaces, Spotlight + * will search for x OR y. + * x(x Search for terms including ( and ) does not work with Spotlight. + * + * [1] <http://lucene.apache.org/core/8_2_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters> + */ + +static char *escape_str(TALLOC_CTX *mem_ctx, + const char *in, + const char *escape_list, + const char *escape_exceptions) +{ + char *out = NULL; + size_t in_len; + size_t new_len; + size_t in_pos; + size_t out_pos = 0; + + if (in == NULL) { + return NULL; + } + in_len = strlen(in); + + if (escape_list == NULL) { + escape_list = ""; + } + if (escape_exceptions == NULL) { + escape_exceptions = ""; + } + + /* + * Allocate enough space for the worst case: every char needs to be + * escaped and requires an additional char. + */ + new_len = (in_len * 2) + 1; + if (new_len <= in_len) { + return NULL; + } + + out = talloc_zero_array(mem_ctx, char, new_len); + if (out == NULL) { + return NULL; + } + + for (in_pos = 0, out_pos = 0; in_pos < in_len; in_pos++, out_pos++) { + if (strchr(escape_list, in[in_pos]) != NULL && + strchr(escape_exceptions, in[in_pos]) == NULL) + { + out[out_pos++] = '\\'; + } + out[out_pos] = in[in_pos]; + } + + return out; +} + +char *es_escape_str(TALLOC_CTX *mem_ctx, + const char *in, + const char *exceptions) +{ + const char *lucene_escape_list = "+-&|!(){}[]^\"~*?:\\/ "; + const char *json_escape_list = "\\\""; + char *lucene_escaped = NULL; + char *full_escaped = NULL; + + lucene_escaped = escape_str(mem_ctx, + in, + lucene_escape_list, + exceptions); + if (lucene_escaped == NULL) { + return NULL; + } + + full_escaped = escape_str(mem_ctx, + lucene_escaped, + json_escape_list, + NULL); + TALLOC_FREE(lucene_escaped); + return full_escaped; +} + +struct es_attr_map *es_map_sl_attr(TALLOC_CTX *mem_ctx, + json_t *kmd_map, + const char *sl_attr) +{ + struct es_attr_map *es_map = NULL; + const char *typestr = NULL; + enum ssm_type type = ssmt_bool; + char *es_attr = NULL; + size_t i; + int cmp; + int ret; + + static struct { + const char *typestr; + enum ssm_type typeval; + } ssmt_type_map[] = { + {"bool", ssmt_bool}, + {"num", ssmt_num}, + {"str", ssmt_str}, + {"fts", ssmt_fts}, + {"date", ssmt_date}, + {"type", ssmt_type}, + }; + + if (sl_attr == NULL) { + return NULL; + } + + ret = json_unpack(kmd_map, + "{s: {s: s}}", + sl_attr, + "type", + &typestr); + if (ret != 0) { + DBG_DEBUG("No JSON type mapping for [%s]\n", sl_attr); + return NULL; + } + + ret = json_unpack(kmd_map, + "{s: {s: s}}", + sl_attr, + "attribute", + &es_attr); + if (ret != 0) { + DBG_ERR("No JSON attribute mapping for [%s]\n", sl_attr); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(ssmt_type_map); i++) { + cmp = strcmp(typestr, ssmt_type_map[i].typestr); + if (cmp == 0) { + type = ssmt_type_map[i].typeval; + break; + } + } + if (i == ARRAY_SIZE(ssmt_type_map)) { + return NULL; + } + + es_map = talloc_zero(mem_ctx, struct es_attr_map); + if (es_map == NULL) { + return NULL; + } + es_map->type = type; + + es_map->name = es_escape_str(es_map, es_attr, NULL); + if (es_map->name == NULL) { + TALLOC_FREE(es_map); + return false; + } + + return es_map; +} + +const char *es_map_sl_type(json_t *mime_map, + const char *sl_type) +{ + const char *mime_type = NULL; + int ret; + + if (sl_type == NULL) { + return NULL; + } + + ret = json_unpack(mime_map, + "{s: s}", + sl_type, + &mime_type); + if (ret != 0) { + return NULL; + } + + return mime_type; +} diff --git a/source3/rpc_server/mdssvc/es_mapping.h b/source3/rpc_server/mdssvc/es_mapping.h new file mode 100644 index 0000000..29511b5 --- /dev/null +++ b/source3/rpc_server/mdssvc/es_mapping.h @@ -0,0 +1,49 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Elasticsearch backend + + Copyright (c) Ralph Boehme 2019 + + 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 2 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 _ES_MAPPING_H_ +#define _ES_MAPPING_H_ + +#include <jansson.h> + +enum ssm_type { + ssmt_bool, /* a boolean value */ + ssmt_num, /* a numeric value */ + ssmt_str, /* a string value */ + ssmt_fts, /* a string value */ + ssmt_date, /* date values */ + ssmt_type /* kMDItemContentType, requires special mapping */ +}; + +struct es_attr_map { + enum ssm_type type; + const char *name; +}; + +char *es_escape_str(TALLOC_CTX *mem_ctx, + const char *in, + const char *exceptions); +struct es_attr_map *es_map_sl_attr(TALLOC_CTX *mem_ctx, + json_t *kmd_map, + const char *sl_attr); +const char *es_map_sl_type(json_t *mime_map, + const char *sl_type); + +#endif diff --git a/source3/rpc_server/mdssvc/es_parser.y b/source3/rpc_server/mdssvc/es_parser.y new file mode 100644 index 0000000..3fbdf93 --- /dev/null +++ b/source3/rpc_server/mdssvc/es_parser.y @@ -0,0 +1,686 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Elasticsearch backend + + Copyright (C) Ralph Boehme 2019 + + 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 "rpc_server/mdssvc/mdssvc.h" + #include "rpc_server/mdssvc/mdssvc_es.h" + #include "rpc_server/mdssvc/es_parser.tab.h" + #include "rpc_server/mdssvc/es_mapping.h" + #include "lib/util/smb_strtox.h" + #include <jansson.h> + + /* + * allow building with -O3 -Wp,-D_FORTIFY_SOURCE=2 + * + * /tmp/samba-testbase/.../mdssvc/es_parser.y: In function + * ‘mdsyylparse’: + * es_parser.tab.c:1124:6: error: assuming pointer wraparound + * does not occur when comparing P +- C1 with P +- C2 + * [-Werror=strict-overflow] + * + * The generated code in es_parser.tab.c looks like this: + * + * if (yyss + yystacksize - 1 <= yyssp) + */ + #pragma GCC diagnostic ignored "-Wstrict-overflow" + + #define YYMALLOC SMB_MALLOC + #define YYREALLOC SMB_REALLOC + + struct yy_buffer_state; + typedef struct yy_buffer_state *YY_BUFFER_STATE; + int mdsyyllex(void); + void mdsyylerror(char const *); + void *mdsyylterminate(void); + YY_BUFFER_STATE mdsyyl_scan_string(const char *str); + void mdsyyl_delete_buffer(YY_BUFFER_STATE buffer); + + /* forward declarations */ + static char *isodate_to_sldate(const char *s); + static char *map_expr(const struct es_attr_map *attr, + char op, + const char *val1, + const char *val2); + + /* global vars, eg needed by the lexer */ + struct es_parser_state { + TALLOC_CTX *frame; + json_t *kmd_map; + json_t *mime_map; + bool ignore_unknown_attribute; + bool ignore_unknown_type; + bool type_error; + YY_BUFFER_STATE s; + const char *result; + } *global_es_parser_state; +%} + +%code provides { + #include <stdbool.h> + #include <jansson.h> + #include "rpc_server/mdssvc/mdssvc.h" + + /* 2001-01-01T00:00:00Z - Unix Epoch = SP_RAW_TIME_OFFSET */ + #define SP_RAW_TIME_OFFSET 978307200 + + int mdsyylwrap(void); + bool map_spotlight_to_es_query(TALLOC_CTX *mem_ctx, + json_t *mappings, + const char *path_scope, + const char *query_string, + char **_es_query); +} + +%union { + bool bval; + const char *sval; + struct es_attr_map *attr_map; +} + +%name-prefix "mdsyyl" +%expect 1 +%error-verbose + +%type <sval> match expr line function value isodate +%type <attr_map> attribute + +%token <sval> WORD PHRASE +%token <bval> BOOLEAN +%token FUNC_INRANGE +%token DATE_ISO +%token OBRACE CBRACE EQUAL UNEQUAL GT LT COMMA QUOTE +%left OR +%left AND +%% + +input: +/* empty */ +| input line +; + +line: +expr { + if ($1 == NULL) { + YYABORT; + } + if (global_es_parser_state->type_error) { + YYABORT; + } + global_es_parser_state->result = $1; +} +; + +expr: +OBRACE expr CBRACE { + if ($2 == NULL) { + $$ = NULL; + } else { + $$ = talloc_asprintf(talloc_tos(), "(%s)", $2); + if ($$ == NULL) YYABORT; + } +} +| expr AND expr { + if ($1 == NULL && $3 == NULL) { + $$ = NULL; + } else if ($1 == NULL) { + $$ = $3; + } else if ($3 == NULL) { + $$ = $1; + } else { + $$ = talloc_asprintf(talloc_tos(), "(%s) AND (%s)", $1, $3); + if ($$ == NULL) YYABORT; + } +} +| expr OR expr { + if ($1 == NULL && $3 == NULL) { + $$ = NULL; + } else if ($1 == NULL) { + $$ = $3; + } else if ($3 == NULL) { + $$ = $1; + } else { + $$ = talloc_asprintf(talloc_tos(), "%s OR %s", $1, $3); + if ($$ == NULL) YYABORT; + } +} +| match { + $$ = $1; +} +| BOOLEAN { + /* + * We can't properly handle these in expressions, fortunately this + * is probably only ever used by OS X as sole element in an + * expression ie "False" (when Finder window selected our share + * but no search string entered yet). Packet traces showed that OS + * X Spotlight server then returns a failure (ie -1) which is what + * we do here too by calling YYABORT. + */ + YYABORT; +}; + +match: +attribute EQUAL value { + if ($1 == NULL) { + $$ = NULL; + } else { + $$ = map_expr($1, '=', $3, NULL); + } +} +| attribute UNEQUAL value { + if ($1 == NULL) { + $$ = NULL; + } else { + $$ = map_expr($1, '!', $3, NULL); + } +} +| attribute LT value { + if ($1 == NULL) { + $$ = NULL; + } else { + $$ = map_expr($1, '<', $3, NULL); + } +} +| attribute GT value { + if ($1 == NULL) { + $$ = NULL; + } else { + $$ = map_expr($1, '>', $3, NULL); + } +} +| function { + $$ = $1; +} +| match WORD { + $$ = $1; +}; + +function: +FUNC_INRANGE OBRACE attribute COMMA WORD COMMA WORD CBRACE { + if ($3 == NULL) { + $$ = NULL; + } else { + $$ = map_expr($3, '~', $5, $7); + } +}; + +attribute: +WORD { + $$ = es_map_sl_attr(global_es_parser_state->frame, + global_es_parser_state->kmd_map, + $1); + if ($$ == NULL && + !global_es_parser_state->ignore_unknown_attribute) + { + YYABORT; + } +}; + +value: +PHRASE { + $$ = $1; +} +| isodate { + $$ = $1; +}; + +isodate: +DATE_ISO OBRACE WORD CBRACE { + $$ = isodate_to_sldate($3); + if ($$ == NULL) YYABORT; +}; + +%% + +/* + * Spotlight has two date formats: + * - seconds since 2001-01-01 00:00:00Z + * - as string "$time.iso(%Y-%m-%dT%H:%M:%SZ)" + * This function converts the latter to the former as string, so the parser + * can work on a uniform format. + */ +static char *isodate_to_sldate(const char *isodate) +{ + struct es_parser_state *s = global_es_parser_state; + struct tm tm = {}; + const char *p = NULL; + char *tstr = NULL; + time_t t; + + p = strptime(isodate, "%Y-%m-%dT%H:%M:%SZ", &tm); + if (p == NULL) { + DBG_ERR("strptime [%s] failed\n", isodate); + return NULL; + } + + t = timegm(&tm); + t -= SP_RAW_TIME_OFFSET; + + tstr = talloc_asprintf(s->frame, "%jd", (intmax_t)t); + if (tstr == NULL) { + return NULL; + } + + return tstr; +} + +static char *map_type(const struct es_attr_map *attr, + char op, + const char *val) +{ + struct es_parser_state *s = global_es_parser_state; + const char *mime_type_list = NULL; + char *esc_mime_type_list = NULL; + const char *not = NULL; + const char *end = NULL; + char *es = NULL; + + mime_type_list = es_map_sl_type(s->mime_map, val); + if (mime_type_list == NULL) { + DBG_DEBUG("Mapping type [%s] failed\n", val); + if (!s->ignore_unknown_type) { + s->type_error = true; + } + return NULL; + } + + esc_mime_type_list = es_escape_str(s->frame, + mime_type_list, + "* "); + if (esc_mime_type_list == NULL) { + return NULL; + } + + switch (op) { + case '=': + not = ""; + end = ""; + break; + case '!': + not = "(NOT "; + end = ")"; + break; + default: + DBG_ERR("Mapping type [%s] unexpected op [%c]\n", val, op); + return NULL; + } + es = talloc_asprintf(s->frame, + "%s%s:(%s)%s", + not, + attr->name, + esc_mime_type_list, + end); + if (es == NULL) { + return NULL; + } + + return es; +} + +static char *map_num(const struct es_attr_map *attr, + char op, + const char *val1, + const char *val2) +{ + struct es_parser_state *s = global_es_parser_state; + char *es = NULL; + + switch (op) { + case '>': + es = talloc_asprintf(s->frame, + "%s:{%s TO *}", + attr->name, + val1); + break; + case '<': + es = talloc_asprintf(s->frame, + "%s:{* TO %s}", + attr->name, + val1); + break; + case '~': + es = talloc_asprintf(s->frame, + "%s:[%s TO %s]", + attr->name, + val1, + val2); + break; + case '=': + es = talloc_asprintf(s->frame, + "%s:%s", + attr->name, + val1); + break; + case '!': + es = talloc_asprintf(s->frame, + "(NOT %s:%s)", + attr->name, + val1); + break; + default: + DBG_ERR("Mapping num unexpected op [%c]\n", op); + return NULL; + } + if (es == NULL) { + return NULL; + } + + return es; +} + +static char *map_fts(const struct es_attr_map *attr, + char op, + const char *val) +{ + struct es_parser_state *s = global_es_parser_state; + const char *not = NULL; + const char *end = NULL; + char *esval = NULL; + char *es = NULL; + + esval = es_escape_str(s->frame, val, "*\\\""); + if (esval == NULL) { + yyerror("es_escape_str failed"); + return NULL; + } + + switch (op) { + case '=': + not = ""; + end = ""; + break; + case '!': + not = "(NOT "; + end = ")"; + break; + default: + DBG_ERR("Mapping fts [%s] unexpected op [%c]\n", val, op); + return NULL; + } + es = talloc_asprintf(s->frame, + "%s%s%s", + not, + esval, + end); + if (es == NULL) { + return NULL; + } + return es; +} + +static char *map_str(const struct es_attr_map *attr, + char op, + const char *val) +{ + struct es_parser_state *s = global_es_parser_state; + char *esval = NULL; + char *es = NULL; + const char *not = NULL; + const char *end = NULL; + + esval = es_escape_str(s->frame, val, "*\\\""); + if (esval == NULL) { + yyerror("es_escape_str failed"); + return NULL; + } + + switch (op) { + case '=': + not = ""; + end = ""; + break; + case '!': + not = "(NOT "; + end = ")"; + break; + default: + DBG_ERR("Mapping string [%s] unexpected op [%c]\n", val, op); + return NULL; + } + + es = talloc_asprintf(s->frame, + "%s%s:%s%s", + not, + attr->name, + esval, + end); + if (es == NULL) { + return NULL; + } + return es; +} + +/* + * Convert Spotlight date seconds since 2001-01-01 00:00:00Z + * to a date string in the format %Y-%m-%dT%H:%M:%SZ. + */ +static char *map_sldate_to_esdate(TALLOC_CTX *mem_ctx, + const char *sldate) +{ + struct tm *tm = NULL; + char *esdate = NULL; + char buf[21]; + size_t len; + time_t t; + int error; + + t = (time_t)smb_strtoull(sldate, NULL, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + DBG_ERR("smb_strtoull [%s] failed\n", sldate); + return NULL; + } + t += SP_RAW_TIME_OFFSET; + + tm = gmtime(&t); + if (tm == NULL) { + DBG_ERR("localtime [%s] failed\n", sldate); + return NULL; + } + + len = strftime(buf, sizeof(buf), + "%Y-%m-%dT%H:%M:%SZ", tm); + if (len != 20) { + DBG_ERR("strftime [%s] failed\n", sldate); + return NULL; + } + + esdate = es_escape_str(mem_ctx, buf, NULL); + if (esdate == NULL) { + yyerror("es_escape_str failed"); + return NULL; + } + return esdate; +} + +static char *map_date(const struct es_attr_map *attr, + char op, + const char *sldate1, + const char *sldate2) +{ + struct es_parser_state *s = global_es_parser_state; + char *esdate1 = NULL; + char *esdate2 = NULL; + char *es = NULL; + + if (op == '~' && sldate2 == NULL) { + DBG_ERR("Date range query, but second date is NULL\n"); + return NULL; + } + + esdate1 = map_sldate_to_esdate(s->frame, sldate1); + if (esdate1 == NULL) { + DBG_ERR("map_sldate_to_esdate [%s] failed\n", sldate1); + return NULL; + } + if (sldate2 != NULL) { + esdate2 = map_sldate_to_esdate(s->frame, sldate2); + if (esdate2 == NULL) { + DBG_ERR("map_sldate_to_esdate [%s] failed\n", sldate2); + return NULL; + } + } + + switch (op) { + case '>': + es = talloc_asprintf(s->frame, + "%s:{%s TO *}", + attr->name, + esdate1); + break; + case '<': + es = talloc_asprintf(s->frame, + "%s:{* TO %s}", + attr->name, + esdate1); + break; + case '~': + es = talloc_asprintf(s->frame, + "%s:[%s TO %s]", + attr->name, + esdate1, + esdate2); + break; + case '=': + es = talloc_asprintf(s->frame, + "%s:%s", + attr->name, + esdate1); + break; + case '!': + es = talloc_asprintf(s->frame, + "(NOT %s:%s)", + attr->name, + esdate1); + break; + } + if (es == NULL) { + return NULL; + } + return es; +} + +static char *map_expr(const struct es_attr_map *attr, + char op, + const char *val1, + const char *val2) +{ + char *es = NULL; + + switch (attr->type) { + case ssmt_type: + es = map_type(attr, op, val1); + break; + case ssmt_num: + es = map_num(attr, op, val1, val2); + break; + case ssmt_fts: + es = map_fts(attr, op, val1); + break; + case ssmt_str: + es = map_str(attr, op, val1); + break; + case ssmt_date: + es = map_date(attr, op, val1, val2); + break; + default: + break; + } + if (es == NULL) { + DBG_DEBUG("Mapping [%s %c %s (%s)] failed\n", + attr->name, op, val1, val2 ? val2 : ""); + return NULL; + } + + return es; +} + +void mdsyylerror(const char *str) +{ + DBG_ERR("Parser failed: %s\n", str); +} + +int mdsyylwrap(void) +{ + return 1; +} + +/** + * Map a Spotlight RAW query string to a ES query string + **/ +bool map_spotlight_to_es_query(TALLOC_CTX *mem_ctx, + json_t *mappings, + const char *path_scope, + const char *query_string, + char **_es_query) +{ + struct es_parser_state s = { + .frame = talloc_stackframe(), + }; + int result; + char *es_query = NULL; + + s.kmd_map = json_object_get(mappings, "attribute_mappings"); + if (s.kmd_map == NULL) { + DBG_ERR("Failed to load attribute_mappings from JSON\n"); + return false; + } + s.mime_map = json_object_get(mappings, "mime_mappings"); + if (s.mime_map == NULL) { + DBG_ERR("Failed to load mime_mappings from JSON\n"); + return false; + } + + s.s = mdsyyl_scan_string(query_string); + if (s.s == NULL) { + DBG_WARNING("Failed to parse [%s]\n", query_string); + TALLOC_FREE(s.frame); + return false; + } + + s.ignore_unknown_attribute = lp_parm_bool(GLOBAL_SECTION_SNUM, + "elasticsearch", + "ignore unknown attribute", + false); + s.ignore_unknown_type = lp_parm_bool(GLOBAL_SECTION_SNUM, + "elasticsearch", + "ignore unknown type", + false); + + global_es_parser_state = &s; + result = mdsyylparse(); + global_es_parser_state = NULL; + mdsyyl_delete_buffer(s.s); + + if (result != 0) { + TALLOC_FREE(s.frame); + return false; + } + + es_query = talloc_asprintf(mem_ctx, + "(%s) AND path.real.fulltext:\\\"%s\\\"", + s.result, path_scope); + TALLOC_FREE(s.frame); + if (es_query == NULL) { + return false; + } + + *_es_query = es_query; + return true; +} diff --git a/source3/rpc_server/mdssvc/es_parser_test.c b/source3/rpc_server/mdssvc/es_parser_test.c new file mode 100644 index 0000000..7d88c67 --- /dev/null +++ b/source3/rpc_server/mdssvc/es_parser_test.c @@ -0,0 +1,97 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / ES backend + + Copyright (C) Ralph Boehme 2019 + + 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 "rpc_server/mdssvc/mdssvc.h" +#include "rpc_server/mdssvc/mdssvc_es.h" +#include "rpc_server/mdssvc/es_parser.tab.h" +#include "rpc_server/mdssvc/es_mapping.h" + +/* + * Examples: + * + * $ ./spotlight2es '_kMDItemGroupId=="11"' + * ... + * $ ./spotlight2es '*=="test*"||kMDItemTextContent=="test*"' + * ... + */ + +int main(int argc, char **argv) +{ + TALLOC_CTX *mem_ctx = NULL; + json_t *mappings = NULL; + json_error_t json_error; + char *default_path = NULL; + const char *path = NULL; + const char *query_string = NULL; + const char *path_scope = NULL; + char *es_query = NULL; + bool ok; + + if (argc != 2) { + printf("usage: %s QUERY\n", argv[0]); + return 1; + } + query_string = argv[1]; + path_scope = "/foo/bar"; + + lp_load_global(get_dyn_CONFIGFILE()); + + mem_ctx = talloc_init("es_parser_test"); + if (mem_ctx == NULL) { + return 1; + } + + default_path = talloc_asprintf(mem_ctx, + "%s/mdssvc/elasticsearch_mappings.json", + get_dyn_SAMBA_DATADIR()); + if (default_path == NULL) { + TALLOC_FREE(mem_ctx); + return 1; + } + + path = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "elasticsearch", + "mappings", + default_path); + if (path == NULL) { + TALLOC_FREE(mem_ctx); + return 1; + } + + mappings = json_load_file(path, 0, &json_error); + if (mappings == NULL) { + DBG_ERR("Opening mapping file [%s] failed: %s\n", + path, strerror(errno)); + TALLOC_FREE(mem_ctx); + return 1; + } + + ok = map_spotlight_to_es_query(mem_ctx, + mappings, + path_scope, + query_string, + &es_query); + printf("%s\n", ok ? es_query : "*mapping failed*"); + + json_decref(mappings); + talloc_free(mem_ctx); + return ok ? 0 : 1; +} diff --git a/source3/rpc_server/mdssvc/marshalling.c b/source3/rpc_server/mdssvc/marshalling.c new file mode 100644 index 0000000..c85fae7 --- /dev/null +++ b/source3/rpc_server/mdssvc/marshalling.c @@ -0,0 +1,1422 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 "dalloc.h" +#include "marshalling.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/* + * This is used to talloc an array that will hold the table of + * contents of a marshalled Spotlight RPC (S-RPC) reply. Each ToC + * entry is 8 bytes, so we allocate space for 1024 entries which + * should be sufficient for even the largest S-RPC replies. + * + * The total buffersize for S-RPC packets is typically limited to 64k, + * so we can only store so many elements there anyway. + */ +#define MAX_SLQ_TOC 1024*64 +#define MAX_SLQ_TOCIDX 1024*8 +#define MAX_SLQ_COUNT 1024*64 +#define MAX_SL_STRLEN 1024 + +/****************************************************************************** + * RPC data marshalling and unmarshalling + ******************************************************************************/ + +/* Spotlight epoch is 1.1.2001 00:00 UTC */ +#define SPOTLIGHT_TIME_DELTA 978307200 /* Diff from UNIX epoch to Spotlight epoch */ + +#define SQ_TYPE_NULL 0x0000 +#define SQ_TYPE_COMPLEX 0x0200 +#define SQ_TYPE_INT64 0x8400 +#define SQ_TYPE_BOOL 0x0100 +#define SQ_TYPE_FLOAT 0x8500 +#define SQ_TYPE_DATA 0x0700 +#define SQ_TYPE_CNIDS 0x8700 +#define SQ_TYPE_UUID 0x0e00 +#define SQ_TYPE_DATE 0x8600 +#define SQ_TYPE_TOC 0x8800 + +#define SQ_CPX_TYPE_ARRAY 0x0a00 +#define SQ_CPX_TYPE_STRING 0x0c00 +#define SQ_CPX_TYPE_UTF16_STRING 0x1c00 +#define SQ_CPX_TYPE_DICT 0x0d00 +#define SQ_CPX_TYPE_CNIDS 0x1a00 +#define SQ_CPX_TYPE_FILEMETA 0x1b00 + +struct sl_tag { + int type; + int count; + size_t length; + size_t size; +}; + +static ssize_t sl_pack_loop(DALLOC_CTX *query, char *buf, + ssize_t offset, size_t bufsize, + char *toc_buf, int *toc_idx, int *count); +static ssize_t sl_unpack_loop(DALLOC_CTX *query, const char *buf, + ssize_t offset, size_t bufsize, + int count, ssize_t toc_offset, + int encoding); +static ssize_t sl_pack(DALLOC_CTX *query, char *buf, size_t bufsize); + +/****************************************************************************** + * Wrapper functions for the *VAL macros with bound checking + ******************************************************************************/ + +static ssize_t sl_push_uint64_val(char *buf, + ssize_t offset, + size_t max_offset, + uint64_t val) +{ + if (offset + 8 > max_offset) { + DEBUG(1, ("%s: offset: %zd, max_offset: %zu\n", + __func__, offset, max_offset)); + return -1; + } + + SBVAL(buf, offset, val); + return offset + 8; +} + +static ssize_t sl_pull_uint64_val(const char *buf, + ssize_t offset, + size_t bufsize, + uint encoding, + uint64_t *presult) +{ + uint64_t val; + + if (offset + 8 > bufsize) { + DEBUG(1,("%s: buffer overflow\n", __func__)); + return -1; + } + + if (encoding == SL_ENC_LITTLE_ENDIAN) { + val = BVAL(buf, offset); + } else { + val = RBVAL(buf, offset); + } + + *presult = val; + + return offset + 8; +} + +/* + * Returns the UTF-16 string encoding, by checking the 2-byte byte order mark. + * If there is no byte order mark, -1 is returned. + */ +static int spotlight_get_utf16_string_encoding(const char *buf, ssize_t offset, + size_t query_length, int encoding) +{ + int utf16_encoding; + + /* Assumed encoding in absence of a bom is little endian */ + utf16_encoding = SL_ENC_LITTLE_ENDIAN; + + if (query_length >= 2) { + uint8_t le_bom[] = {0xff, 0xfe}; + uint8_t be_bom[] = {0xfe, 0xff}; + if (memcmp(le_bom, buf + offset, sizeof(uint16_t)) == 0) { + utf16_encoding = SL_ENC_LITTLE_ENDIAN | SL_ENC_UTF_16; + } else if (memcmp(be_bom, buf + offset, sizeof(uint16_t)) == 0) { + utf16_encoding = SL_ENC_BIG_ENDIAN | SL_ENC_UTF_16; + } + } + + return utf16_encoding; +} + +/****************************************************************************** + * marshalling functions + ******************************************************************************/ + +static inline uint64_t sl_pack_tag(uint16_t type, uint16_t size_or_count, uint32_t val) +{ + uint64_t tag = ((uint64_t)val << 32) | ((uint64_t)type << 16) | size_or_count; + return tag; +} + +static ssize_t sl_pack_float(double d, char *buf, ssize_t offset, size_t bufsize) +{ + union { + double d; + uint64_t w; + } ieee_fp_union; + + ieee_fp_union.d = d; + + offset = sl_push_uint64_val(buf, offset, bufsize, sl_pack_tag(SQ_TYPE_FLOAT, 2, 1)); + if (offset == -1) { + return -1; + } + offset = sl_push_uint64_val(buf, offset, bufsize, ieee_fp_union.w); + if (offset == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_uint64(uint64_t u, char *buf, ssize_t offset, size_t bufsize) +{ + uint64_t tag; + + tag = sl_pack_tag(SQ_TYPE_INT64, 2, 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + offset = sl_push_uint64_val(buf, offset, bufsize, u); + if (offset == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_uint64_array(uint64_t *u, char *buf, ssize_t offset, size_t bufsize, int *toc_count) +{ + int count, i; + uint64_t tag; + + count = talloc_array_length(u); + + tag = sl_pack_tag(SQ_TYPE_INT64, count + 1, count); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < count; i++) { + offset = sl_push_uint64_val(buf, offset, bufsize, u[i]); + if (offset == -1) { + return -1; + } + } + + if (count > 1) { + *toc_count += (count - 1); + } + + return offset; +} + +static ssize_t sl_pack_bool(sl_bool_t val, char *buf, ssize_t offset, size_t bufsize) +{ + uint64_t tag; + + tag = sl_pack_tag(SQ_TYPE_BOOL, 1, val ? 1 : 0); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_nil(char *buf, ssize_t offset, size_t bufsize) +{ + uint64_t tag; + + tag = sl_pack_tag(SQ_TYPE_NULL, 1, 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_date(sl_time_t t, char *buf, ssize_t offset, size_t bufsize) +{ + uint64_t data; + uint64_t tag; + union { + double d; + uint64_t w; + } ieee_fp_union; + + tag = sl_pack_tag(SQ_TYPE_DATE, 2, 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + ieee_fp_union.d = (double)(t.tv_sec - SPOTLIGHT_TIME_DELTA); + ieee_fp_union.d += (double)t.tv_usec / 1000000; + + data = ieee_fp_union.w; + offset = sl_push_uint64_val(buf, offset, bufsize, data); + if (offset == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_uuid(sl_uuid_t *uuid, char *buf, ssize_t offset, size_t bufsize) +{ + uint64_t tag; + + tag = sl_pack_tag(SQ_TYPE_UUID, 3, 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + if (offset + 16 > bufsize) { + return -1; + } + memcpy(buf + offset, uuid, 16); + + return offset + 16; +} + +static ssize_t sl_pack_CNID(sl_cnids_t *cnids, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx) +{ + ssize_t result; + int len, i; + int cnid_count = dalloc_size(cnids->ca_cnids); + uint64_t tag; + uint64_t id; + void *p; + + tag = sl_pack_tag(SQ_CPX_TYPE_CNIDS, offset / 8, 0); + result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + *toc_idx += 1; + + len = cnid_count + 1; + if (cnid_count > 0) { + len ++; + } + + /* unknown meaning, but always 8 */ + tag = sl_pack_tag(SQ_TYPE_CNIDS, len, 8 ); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + if (cnid_count > 0) { + tag = sl_pack_tag(cnids->ca_unkn1, cnid_count, cnids->ca_context); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < cnid_count; i++) { + p = dalloc_get_object(cnids->ca_cnids, i); + if (p == NULL) { + return -1; + } + memcpy(&id, p, sizeof(uint64_t)); + offset = sl_push_uint64_val(buf, offset, bufsize, id); + if (offset == -1) { + return -1; + } + } + } + + return offset; +} + +static ssize_t sl_pack_array(sl_array_t *array, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx) +{ + ssize_t result; + int count = dalloc_size(array); + int octets = offset / 8; + uint64_t tag; + int toc_idx_save = *toc_idx; + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + *toc_idx += 1; + + offset = sl_pack_loop(array, buf, offset, bufsize - offset, toc_buf, toc_idx, &count); + + tag = sl_pack_tag(SQ_CPX_TYPE_ARRAY, octets, count); + result = sl_push_uint64_val(toc_buf, toc_idx_save * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + return offset; +} + +static ssize_t sl_pack_dict(sl_array_t *dict, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx, int *count) +{ + ssize_t result; + uint64_t tag; + + tag = sl_pack_tag(SQ_CPX_TYPE_DICT, offset / 8, + dalloc_size(dict)); + result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + *toc_idx += 1; + + offset = sl_pack_loop(dict, buf, offset, bufsize - offset, toc_buf, toc_idx, count); + + return offset; +} + +static ssize_t sl_pack_filemeta(sl_filemeta_t *fm, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx) +{ + ssize_t result; + ssize_t fmlen; + ssize_t saveoff = offset; + uint64_t tag; + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + offset += 8; + + fmlen = sl_pack(fm, buf + offset, bufsize - offset); + if (fmlen == -1) { + return -1; + } + + /* + * Check for empty filemeta array, if it's only 40 bytes, it's + * only the header but no content + */ + if (fmlen > 40) { + offset += fmlen; + } else { + fmlen = 0; + } + + /* unknown meaning, but always 8 */ + tag = sl_pack_tag(SQ_TYPE_DATA, (fmlen / 8) + 1, 8); + result = sl_push_uint64_val(buf, saveoff + 8, bufsize, tag); + if (result == -1) { + return -1; + } + + tag = sl_pack_tag(SQ_CPX_TYPE_FILEMETA, saveoff / 8, fmlen / 8); + result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + *toc_idx += 1; + + return offset; +} + +static ssize_t sl_pack_string(char *s, char *buf, ssize_t offset, size_t bufsize, + char *toc_buf, int *toc_idx) +{ + ssize_t result; + size_t len, octets, used_in_last_octet; + uint64_t tag; + + len = strlen(s); + if (len > MAX_SL_STRLEN) { + return -1; + } + octets = (len + 7) / 8; + used_in_last_octet = len % 8; + if (used_in_last_octet == 0) { + used_in_last_octet = 8; + } + + tag = sl_pack_tag(SQ_CPX_TYPE_STRING, offset / 8, used_in_last_octet); + result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + *toc_idx += 1; + + tag = sl_pack_tag(SQ_TYPE_DATA, octets + 1, used_in_last_octet); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + return -1; + } + + if (offset + (octets * 8) > bufsize) { + return -1; + } + + memset(buf + offset, 0, octets * 8); + memcpy(buf + offset, s, len); + offset += octets * 8; + + return offset; +} + +static ssize_t sl_pack_string_as_utf16(char *s, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx) +{ + ssize_t result; + int utf16_plus_bom_len, octets, used_in_last_octet; + char *utf16string = NULL; + char bom[] = { 0xff, 0xfe }; + size_t slen, utf16len; + uint64_t tag; + bool ok; + + slen = strlen(s); + if (slen > MAX_SL_STRLEN) { + return -1; + } + + ok = convert_string_talloc(talloc_tos(), + CH_UTF8, + CH_UTF16LE, + s, + slen, + &utf16string, + &utf16len); + if (!ok) { + return -1; + } + + utf16_plus_bom_len = utf16len + 2; + octets = (utf16_plus_bom_len + 7) / 8; + used_in_last_octet = utf16_plus_bom_len % 8; + if (used_in_last_octet == 0) { + used_in_last_octet = 8; + } + + tag = sl_pack_tag(SQ_CPX_TYPE_UTF16_STRING, offset / 8, used_in_last_octet); + result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag); + if (result == -1) { + offset = -1; + goto done; + } + + tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + goto done; + } + + *toc_idx += 1; + + tag = sl_pack_tag(SQ_TYPE_DATA, octets + 1, used_in_last_octet); + offset = sl_push_uint64_val(buf, offset, bufsize, tag); + if (offset == -1) { + goto done; + } + + if (offset + (octets * 8) > bufsize) { + offset = -1; + goto done; + } + + memset(buf + offset, 0, octets * 8); + memcpy(buf + offset, &bom, sizeof(bom)); + memcpy(buf + offset + 2, utf16string, utf16len); + offset += octets * 8; + +done: + TALLOC_FREE(utf16string); + return offset; +} + +static ssize_t sl_pack_loop(DALLOC_CTX *query, char *buf, ssize_t offset, + size_t bufsize, char *toc_buf, int *toc_idx, int *count) +{ + const char *type; + int n; + uint64_t i; + sl_bool_t bl; + double d; + sl_time_t t; + void *p; + + for (n = 0; n < dalloc_size(query); n++) { + + type = dalloc_get_name(query, n); + if (type == NULL) { + return -1; + } + p = dalloc_get_object(query, n); + if (p == NULL) { + return -1; + } + + if (strcmp(type, "sl_array_t") == 0) { + offset = sl_pack_array(p, buf, offset, bufsize, + toc_buf, toc_idx); + } else if (strcmp(type, "sl_dict_t") == 0) { + offset = sl_pack_dict(p, buf, offset, bufsize, + toc_buf, toc_idx, count); + } else if (strcmp(type, "sl_filemeta_t") == 0) { + offset = sl_pack_filemeta(p, buf, offset, bufsize, + toc_buf, toc_idx); + } else if (strcmp(type, "uint64_t") == 0) { + memcpy(&i, p, sizeof(uint64_t)); + offset = sl_pack_uint64(i, buf, offset, bufsize); + } else if (strcmp(type, "uint64_t *") == 0) { + offset = sl_pack_uint64_array(p, buf, offset, + bufsize, count); + } else if (strcmp(type, "char *") == 0) { + offset = sl_pack_string(p, buf, offset, bufsize, + toc_buf, toc_idx); + } else if (strcmp(type, "smb_ucs2_t *") == 0) { + offset = sl_pack_string_as_utf16(p, buf, offset, bufsize, + toc_buf, toc_idx); + } else if (strcmp(type, "sl_bool_t") == 0) { + memcpy(&bl, p, sizeof(sl_bool_t)); + offset = sl_pack_bool(bl, buf, offset, bufsize); + } else if (strcmp(type, "double") == 0) { + memcpy(&d, p, sizeof(double)); + offset = sl_pack_float(d, buf, offset, bufsize); + } else if (strcmp(type, "sl_nil_t") == 0) { + offset = sl_pack_nil(buf, offset, bufsize); + } else if (strcmp(type, "sl_time_t") == 0) { + memcpy(&t, p, sizeof(sl_time_t)); + offset = sl_pack_date(t, buf, offset, bufsize); + } else if (strcmp(type, "sl_uuid_t") == 0) { + offset = sl_pack_uuid(p, buf, offset, bufsize); + } else if (strcmp(type, "sl_cnids_t") == 0) { + offset = sl_pack_CNID(p, buf, offset, + bufsize, toc_buf, toc_idx); + } else { + DEBUG(1, ("unknown type: %s\n", type)); + return -1; + } + if (offset == -1) { + DEBUG(1, ("error packing type: %s\n", type)); + return -1; + } + } + + return offset; +} + +/****************************************************************************** + * unmarshalling functions + ******************************************************************************/ + +static ssize_t sl_unpack_tag(const char *buf, + ssize_t offset, + size_t bufsize, + uint encoding, + struct sl_tag *tag) +{ + uint64_t val; + + if (offset + 8 > bufsize) { + DEBUG(1,("%s: buffer overflow\n", __func__)); + return -1; + } + + if (encoding == SL_ENC_LITTLE_ENDIAN) { + val = BVAL(buf, offset); + } else { + val = RBVAL(buf, offset); + } + + tag->size = (val & 0xffff) * 8; + tag->type = (val & 0xffff0000) >> 16; + tag->count = val >> 32; + tag->length = tag->count * 8; + + if (tag->size > MAX_MDSCMD_SIZE) { + DEBUG(1,("%s: size limit %zu\n", __func__, tag->size)); + return -1; + } + + if (tag->length > MAX_MDSCMD_SIZE) { + DEBUG(1,("%s: length limit %zu\n", __func__, tag->length)); + return -1; + } + + if (tag->count > MAX_SLQ_COUNT) { + DEBUG(1,("%s: count limit %d\n", __func__, tag->count)); + return -1; + } + + return offset + 8; +} + +static int sl_unpack_ints(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int encoding) +{ + int i, result; + struct sl_tag tag; + uint64_t query_data64; + + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < tag.count; i++) { + offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64); + if (offset == -1) { + return -1; + } + result = dalloc_add_copy(query, &query_data64, uint64_t); + if (result != 0) { + return -1; + } + } + + return tag.count; +} + +static int sl_unpack_date(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int encoding) +{ + int i, result; + struct sl_tag tag; + uint64_t query_data64; + union { + double d; + uint64_t w; + } ieee_fp_union; + double fraction; + sl_time_t t; + + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < tag.count; i++) { + offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64); + if (offset == -1) { + return -1; + } + ieee_fp_union.w = query_data64; + fraction = ieee_fp_union.d - (uint64_t)ieee_fp_union.d; + + t = (sl_time_t) { + .tv_sec = ieee_fp_union.d + SPOTLIGHT_TIME_DELTA, + .tv_usec = fraction * 1000000 + }; + + result = dalloc_add_copy(query, &t, sl_time_t); + if (result != 0) { + return -1; + } + } + + return tag.count; +} + +static int sl_unpack_uuid(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int encoding) +{ + int i, result; + sl_uuid_t uuid; + struct sl_tag tag; + + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < tag.count; i++) { + if (offset + 16 > bufsize) { + DEBUG(1,("%s: buffer overflow\n", __func__)); + return -1; + } + memcpy(uuid.sl_uuid, buf + offset, 16); + result = dalloc_add_copy(query, &uuid, sl_uuid_t); + if (result != 0) { + return -1; + } + offset += 16; + } + + return tag.count; +} + +static int sl_unpack_floats(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int encoding) +{ + int i, result; + union { + double d; + uint32_t w[2]; + } ieee_fp_union; + struct sl_tag tag; + + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + for (i = 0; i < tag.count; i++) { + if (offset + 8 > bufsize) { + DEBUG(1,("%s: buffer overflow\n", __func__)); + return -1; + } + if (encoding == SL_ENC_LITTLE_ENDIAN) { +#ifdef WORDS_BIGENDIAN + ieee_fp_union.w[0] = IVAL(buf, offset + 4); + ieee_fp_union.w[1] = IVAL(buf, offset); +#else + ieee_fp_union.w[0] = IVAL(buf, offset); + ieee_fp_union.w[1] = IVAL(buf, offset + 4); +#endif + } else { +#ifdef WORDS_BIGENDIAN + ieee_fp_union.w[0] = RIVAL(buf, offset); + ieee_fp_union.w[1] = RIVAL(buf, offset + 4); +#else + ieee_fp_union.w[0] = RIVAL(buf, offset + 4); + ieee_fp_union.w[1] = RIVAL(buf, offset); +#endif + } + result = dalloc_add_copy(query, &ieee_fp_union.d, double); + if (result != 0) { + return -1; + } + offset += 8; + } + + return tag.count; +} + +static int sl_unpack_CNID(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int length, + int encoding) +{ + int i, count, result; + uint64_t query_data64; + sl_cnids_t *cnids; + + cnids = talloc_zero(query, sl_cnids_t); + if (cnids == NULL) { + return -1; + } + cnids->ca_cnids = dalloc_new(cnids); + if (cnids->ca_cnids == NULL) { + return -1; + } + + if (length < 8) { + return -1; + } + if (length == 8) { + /* + * That's permitted, length=8 is an empty CNID array. + */ + result = dalloc_add(query, cnids, sl_cnids_t); + if (result != 0) { + return -1; + } + return 0; + } + + offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64); + if (offset == -1) { + return -1; + } + + /* + * Note: ca_unkn1 and ca_context could be taken from the tag + * type and count members, but the fields are packed + * differently in this context, so we can't use + * sl_unpack_tag(). + */ + count = query_data64 & 0xffff;; + cnids->ca_unkn1 = (query_data64 & 0xffff0000) >> 16; + cnids->ca_context = query_data64 >> 32; + + for (i = 0; i < count; i++) { + offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64); + if (offset == -1) { + return -1; + } + + result = dalloc_add_copy(cnids->ca_cnids, &query_data64, uint64_t); + if (result != 0) { + return -1; + } + } + + result = dalloc_add(query, cnids, sl_cnids_t); + if (result != 0) { + return -1; + } + + return 0; +} + +static ssize_t sl_unpack_cpx(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int cpx_query_type, + int cpx_query_count, + ssize_t toc_offset, + int encoding) +{ + int result; + ssize_t roffset = offset; + int unicode_encoding; + bool mark_exists; + char *p; + size_t slen, tmp_len; + sl_array_t *sl_array; + sl_dict_t *sl_dict; + sl_filemeta_t *sl_fm; + bool ok; + struct sl_tag tag; + + switch (cpx_query_type) { + case SQ_CPX_TYPE_ARRAY: + sl_array = dalloc_zero(query, sl_array_t); + if (sl_array == NULL) { + return -1; + } + roffset = sl_unpack_loop(sl_array, buf, offset, bufsize, + cpx_query_count, toc_offset, encoding); + if (roffset == -1) { + return -1; + } + result = dalloc_add(query, sl_array, sl_array_t); + if (result != 0) { + return -1; + } + break; + + case SQ_CPX_TYPE_DICT: + sl_dict = dalloc_zero(query, sl_dict_t); + if (sl_dict == NULL) { + return -1; + } + roffset = sl_unpack_loop(sl_dict, buf, offset, bufsize, + cpx_query_count, toc_offset, encoding); + if (roffset == -1) { + return -1; + } + result = dalloc_add(query, sl_dict, sl_dict_t); + if (result != 0) { + return -1; + } + break; + + case SQ_CPX_TYPE_STRING: + case SQ_CPX_TYPE_UTF16_STRING: + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + if (tag.size < 16) { + DEBUG(1,("%s: string buffer too small\n", __func__)); + return -1; + } + slen = tag.size - 16 + tag.count; + if (slen > MAX_MDSCMD_SIZE) { + return -1; + } + + if (offset + slen > bufsize) { + DEBUG(1,("%s: buffer overflow\n", __func__)); + return -1; + } + + if (cpx_query_type == SQ_CPX_TYPE_STRING) { + p = talloc_strndup(query, buf + offset, slen); + if (p == NULL) { + return -1; + } + } else { + unicode_encoding = spotlight_get_utf16_string_encoding( + buf, offset, slen, encoding); + mark_exists = (unicode_encoding & SL_ENC_UTF_16) ? true : false; + if (unicode_encoding & SL_ENC_BIG_ENDIAN) { + DEBUG(1, ("Unsupported big endian UTF16 string\n")); + return -1; + } + slen -= mark_exists ? 2 : 0; + ok = convert_string_talloc( + query, + CH_UTF16LE, + CH_UTF8, + buf + offset + (mark_exists ? 2 : 0), + slen, + &p, + &tmp_len); + if (!ok) { + return -1; + } + } + + result = dalloc_stradd(query, p); + if (result != 0) { + return -1; + } + roffset += tag.size; + break; + + case SQ_CPX_TYPE_FILEMETA: + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + if (tag.size < 8) { + DBG_WARNING("size too mall: %zu\n", tag.size); + return -1; + } + + sl_fm = dalloc_zero(query, sl_filemeta_t); + if (sl_fm == NULL) { + return -1; + } + + if (tag.size >= 16) { + result = sl_unpack(sl_fm, + buf + offset, + bufsize - offset ); + if (result == -1) { + return -1; + } + } + result = dalloc_add(query, sl_fm, sl_filemeta_t); + if (result != 0) { + return -1; + } + roffset += tag.size; + break; + + case SQ_CPX_TYPE_CNIDS: + offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (offset == -1) { + return -1; + } + + result = sl_unpack_CNID(query, buf, offset, bufsize, + tag.size, encoding); + if (result == -1) { + return -1; + } + roffset += tag.size; + break; + + default: + DEBUG(1, ("unknown complex query type: %u\n", cpx_query_type)); + return -1; + } + + return roffset; +} + +static ssize_t sl_unpack_loop(DALLOC_CTX *query, + const char *buf, + ssize_t offset, + size_t bufsize, + int count, + ssize_t toc_offset, + int encoding) +{ + int i, toc_index, subcount; + uint64_t result; + + while (count > 0) { + struct sl_tag tag; + + if (offset >= toc_offset) { + return -1; + } + + result = sl_unpack_tag(buf, offset, bufsize, encoding, &tag); + if (result == -1) { + return -1; + } + + switch (tag.type) { + case SQ_TYPE_COMPLEX: { + struct sl_tag cpx_tag; + + if (tag.count < 1) { + DEBUG(1,("%s: invalid tag.count: %d\n", + __func__, tag.count)); + return -1; + } + toc_index = tag.count - 1; + if (toc_index > MAX_SLQ_TOCIDX) { + DEBUG(1,("%s: toc_index too large: %d\n", + __func__, toc_index)); + return -1; + } + result = sl_unpack_tag(buf, toc_offset + (toc_index * 8), + bufsize, encoding, &cpx_tag); + if (result == -1) { + return -1; + } + + offset = sl_unpack_cpx(query, buf, offset + 8, bufsize, cpx_tag.type, + cpx_tag.count, toc_offset, encoding); + if (offset == -1) { + return -1; + } + /* + * tag.size is not the size here, so we need + * to use the offset returned from sl_unpack_cpx() + * instead of offset += tag.size; + */ + count--; + break; + } + + case SQ_TYPE_NULL: { + sl_nil_t nil = 0; + + subcount = tag.count; + if (subcount < 1 || subcount > count) { + return -1; + } + for (i = 0; i < subcount; i++) { + result = dalloc_add_copy(query, &nil, sl_nil_t); + if (result != 0) { + return -1; + } + } + offset += tag.size; + count -= subcount; + break; + } + + case SQ_TYPE_BOOL: { + sl_bool_t b = (tag.count != 0); + + result = dalloc_add_copy(query, &b, sl_bool_t); + if (result != 0) { + return -1; + } + offset += tag.size; + count--; + break; + } + + case SQ_TYPE_INT64: + subcount = sl_unpack_ints(query, buf, offset, bufsize, encoding); + if (subcount < 1 || subcount > count) { + return -1; + } + offset += tag.size; + count -= subcount; + break; + + case SQ_TYPE_UUID: + subcount = sl_unpack_uuid(query, buf, offset, bufsize, encoding); + if (subcount < 1 || subcount > count) { + return -1; + } + offset += tag.size; + count -= subcount; + break; + + case SQ_TYPE_FLOAT: + subcount = sl_unpack_floats(query, buf, offset, bufsize, encoding); + if (subcount < 1 || subcount > count) { + return -1; + } + offset += tag.size; + count -= subcount; + break; + + case SQ_TYPE_DATE: + subcount = sl_unpack_date(query, buf, offset, bufsize, encoding); + if (subcount < 1 || subcount > count) { + return -1; + } + offset += tag.size; + count -= subcount; + break; + + default: + DEBUG(1, ("unknown query type: %d\n", tag.type)); + return -1; + } + } + + return offset; +} + +static ssize_t sl_pack(DALLOC_CTX *query, char *buf, size_t bufsize) +{ + ssize_t result; + char *toc_buf; + int toc_index = 0; + int toc_count = 0; + ssize_t offset, len; + uint64_t hdr; + uint32_t total_octets; + uint32_t data_octets; + uint64_t tag; + + memset(buf, 0, bufsize); + + toc_buf = talloc_zero_size(query, MAX_SLQ_TOC + 8); + if (toc_buf == NULL) { + return -1; + } + + offset = sl_pack_loop(query, buf, 16, bufsize, toc_buf + 8, &toc_index, &toc_count); + if (offset == -1 || offset < 16) { + DEBUG(10,("%s: sl_pack_loop error\n", __func__)); + return -1; + } + len = offset - 16; + + /* + * Marshalling overview: + * + * 16 bytes at the start of buf: + * + * 8 bytes byte order mark + * 4 bytes total octets + * 4 bytes table of content octets + * + * x bytes total octets * 8 from sl_pack_loop + * x bytes ToC octets * 8 from toc_buf + */ + + /* Byte-order mark - we are using little endian only for now */ + memcpy(buf, "432130dm", strlen("432130dm")); + + /* + * The data buffer and ToC buffer sizes are enocoded in number + * of octets (size / 8), plus one, because the octet encoding + * the sizes is included. + */ + data_octets = (len / 8) + 1; + total_octets = data_octets + toc_index + 1; + + hdr = total_octets; + hdr |= ((uint64_t)data_octets << 32); + + /* HDR */ + result = sl_push_uint64_val(buf, 8, bufsize, hdr); + if (result == -1) { + return -1; + } + + /* + * ToC tag with number of ToC entries plus one, the ToC tag + * header. + */ + tag = sl_pack_tag(SQ_TYPE_TOC, toc_index + 1, 0); + result = sl_push_uint64_val(toc_buf, 0, MAX_SLQ_TOC, tag); + if (result == -1) { + return -1; + } + + if ((16 + len + ((toc_index + 1 ) * 8)) > bufsize) { + DEBUG(1, ("%s: exceeding size limit %zu\n", __func__, bufsize)); + return -1; + } + + memcpy(buf + 16 + len, toc_buf, (toc_index + 1 ) * 8); + len += 16 + (toc_index + 1 ) * 8; + + return len; +} + +/****************************************************************************** + * Global functions for packing und unpacking + ******************************************************************************/ + +NTSTATUS sl_pack_alloc(TALLOC_CTX *mem_ctx, + DALLOC_CTX *d, + struct mdssvc_blob *b, + size_t max_fragment_size) +{ + ssize_t len; + + b->spotlight_blob = talloc_zero_array(mem_ctx, + uint8_t, + max_fragment_size); + if (b->spotlight_blob == NULL) { + return NT_STATUS_NO_MEMORY; + } + + len = sl_pack(d, (char *)b->spotlight_blob, max_fragment_size); + if (len == -1) { + return NT_STATUS_DATA_ERROR; + } + + b->length = len; + b->size = len; + return NT_STATUS_OK; +} + +bool sl_unpack(DALLOC_CTX *query, const char *buf, size_t bufsize) +{ + ssize_t result; + ssize_t offset = 0; + int encoding; + uint64_t hdr; + uint32_t total_octets; + uint64_t total_bytes; + uint32_t data_octets; + uint64_t data_bytes; + uint64_t toc_offset; + struct sl_tag toc_tag; + + if (bufsize > MAX_MDSCMD_SIZE) { + return false; + } + + if (bufsize < 8) { + return false; + } + if (strncmp(buf + offset, "md031234", 8) == 0) { + encoding = SL_ENC_BIG_ENDIAN; + } else { + encoding = SL_ENC_LITTLE_ENDIAN; + } + offset += 8; + + offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &hdr); + if (offset == -1) { + return false; + } + + total_octets = hdr & UINT32_MAX; + data_octets = hdr >> 32; + + /* + * Both fields contain the number of octets of the + * corresponding buffer plus the tag octet. We adjust the + * values to match just the number of octets in the buffers. + */ + if (total_octets < 1) { + return false; + } + if (data_octets < 1) { + return false; + } + total_octets--; + data_octets--; + data_bytes = ((uint64_t)data_octets) * 8; + total_bytes = ((uint64_t)total_octets) * 8; + + if (data_bytes >= total_bytes) { + DEBUG(1,("%s: data_bytes: %" PRIu64 ", total_bytes: %" PRIu64 "\n", + __func__, data_bytes, total_bytes)); + return false; + } + + if (total_bytes > (bufsize - offset)) { + return false; + } + + toc_offset = data_bytes; + + toc_offset = sl_unpack_tag(buf + offset, toc_offset, + bufsize - offset, encoding, &toc_tag); + if (toc_offset == -1) { + return false; + } + + if (toc_tag.type != SQ_TYPE_TOC) { + DEBUG(1,("%s: unknown tag type %d\n", __func__, toc_tag.type)); + return false; + } + + /* + * Check toc_tag.size even though we don't use it when unmarshalling + */ + if (toc_tag.size > MAX_SLQ_TOC) { + DEBUG(1,("%s: bad size %zu\n", __func__, toc_tag.size)); + return false; + } + if (toc_tag.size > (total_bytes - data_bytes)) { + DEBUG(1,("%s: bad size %zu\n", __func__, toc_tag.size)); + return false; + } + + if (toc_tag.count != 0) { + DEBUG(1,("%s: bad count %u\n", __func__, toc_tag.count)); + return false; + } + + /* + * We already consumed 16 bytes from the buffer (BOM and size + * tag), so we start at buf + offset. + */ + result = sl_unpack_loop(query, buf + offset, 0, bufsize - offset, + 1, toc_offset, encoding); + if (result == -1) { + DEBUG(1,("%s: sl_unpack_loop failed\n", __func__)); + return false; + } + + return true; +} diff --git a/source3/rpc_server/mdssvc/marshalling.h b/source3/rpc_server/mdssvc/marshalling.h new file mode 100644 index 0000000..ccc8b44 --- /dev/null +++ b/source3/rpc_server/mdssvc/marshalling.h @@ -0,0 +1,63 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 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 _MDSSVC_MARSHALLING_H +#define _MDSSVC_MARSHALLING_H + +#include "dalloc.h" +#include "libcli/util/ntstatus.h" +#include "lib/util/data_blob.h" +#include "librpc/gen_ndr/mdssvc.h" + +#define MAX_SL_FRAGMENT_SIZE 0xFFFFF +#define MAX_MDSCMD_SIZE 0xFFFFFF + +/* Can be ored and used as flags */ +#define SL_ENC_LITTLE_ENDIAN 1 +#define SL_ENC_BIG_ENDIAN 2 +#define SL_ENC_UTF_16 4 + +typedef DALLOC_CTX sl_array_t; /* an array of elements */ +typedef DALLOC_CTX sl_dict_t; /* an array of key/value elements */ +typedef DALLOC_CTX sl_filemeta_t; /* contains one sl_array_t */ +typedef int sl_nil_t; /* a nil element */ +typedef bool sl_bool_t; +typedef struct timeval sl_time_t; +typedef struct { + char sl_uuid[16]; +} sl_uuid_t; +typedef struct { + uint16_t ca_unkn1; + uint32_t ca_context; + DALLOC_CTX *ca_cnids; +} sl_cnids_t; /* an array of CNIDs */ + +/****************************************************************************** + * Function declarations + ******************************************************************************/ + +extern NTSTATUS sl_pack_alloc(TALLOC_CTX *mem_ctx, + DALLOC_CTX *d, + struct mdssvc_blob *b, + size_t max_fragment_size); + +extern bool sl_unpack(DALLOC_CTX *query, const char *buf, size_t bufsize); + +#endif diff --git a/source3/rpc_server/mdssvc/mdssvc.c b/source3/rpc_server/mdssvc/mdssvc.c new file mode 100644 index 0000000..23a3088 --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc.c @@ -0,0 +1,1893 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 "smbd/proto.h" +#include "librpc/gen_ndr/auth.h" +#include "dbwrap/dbwrap.h" +#include "lib/util/dlinklist.h" +#include "lib/util/util_tdb.h" +#include "lib/util/time_basic.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security.h" +#include "mdssvc.h" +#include "mdssvc_noindex.h" +#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER +#include "mdssvc_tracker.h" +#endif +#ifdef HAVE_SPOTLIGHT_BACKEND_ES +#include "mdssvc_es.h" +#endif +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +struct slrpc_cmd { + const char *name; + bool (*function)(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, + DALLOC_CTX *reply); +}; + +struct slq_destroy_state { + struct tevent_context *ev; + struct sl_query *slq; +}; + +/* + * This is a static global because we may be called multiple times and + * we only want one mdssvc_ctx per connection to Tracker. + * + * The client will bind multiple times to the mdssvc RPC service, once + * for every tree connect. + */ +static struct mdssvc_ctx *mdssvc_ctx = NULL; + +/* + * If these functions return an error, they hit something like a non + * recoverable talloc error. Most errors are dealt with by returning + * an error code in the Spotlight RPC reply. + */ +static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_open_query(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_store_attributes(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); +static bool slrpc_close_query(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply); + +/************************************************ + * Misc utility functions + ************************************************/ + +/** + * Add requested metadata for a query result element + * + * This could be rewritten to something more sophisticated like + * querying metadata from Tracker. + * + * If path or sp is NULL, simply add nil values for all attributes. + **/ +static bool add_filemeta(struct mds_ctx *mds_ctx, + sl_array_t *reqinfo, + sl_array_t *fm_array, + const char *path, + const struct stat_ex *sp) +{ + sl_array_t *meta; + sl_nil_t nil; + int i, metacount, result; + uint64_t uint64var; + sl_time_t sl_time; + char *p; + const char *attribute; + size_t nfc_len; + const char *nfc_path = path; + size_t nfd_buf_size; + char *nfd_path = NULL; + char *dest = NULL; + size_t dest_remaining; + size_t nconv; + + metacount = dalloc_size(reqinfo); + if (metacount == 0 || path == NULL || sp == NULL) { + result = dalloc_add_copy(fm_array, &nil, sl_nil_t); + if (result != 0) { + return false; + } + return true; + } + + meta = dalloc_zero(fm_array, sl_array_t); + if (meta == NULL) { + return false; + } + + nfc_len = strlen(nfc_path); + /* + * Simple heuristic, strlen by two should give enough room for NFC to + * NFD conversion. + */ + nfd_buf_size = nfc_len * 2; + nfd_path = talloc_array(meta, char, nfd_buf_size); + if (nfd_path == NULL) { + return false; + } + dest = nfd_path; + dest_remaining = talloc_array_length(dest); + + nconv = smb_iconv(mds_ctx->ic_nfc_to_nfd, + &nfc_path, + &nfc_len, + &dest, + &dest_remaining); + if (nconv == (size_t)-1) { + return false; + } + + for (i = 0; i < metacount; i++) { + attribute = dalloc_get_object(reqinfo, i); + if (attribute == NULL) { + return false; + } + if (strcmp(attribute, "kMDItemDisplayName") == 0 + || strcmp(attribute, "kMDItemFSName") == 0) { + p = strrchr(nfd_path, '/'); + if (p) { + result = dalloc_stradd(meta, p + 1); + if (result != 0) { + return false; + } + } + } else if (strcmp(attribute, "kMDItemPath") == 0) { + result = dalloc_stradd(meta, nfd_path); + if (result != 0) { + return false; + } + } else if (strcmp(attribute, "kMDItemFSSize") == 0) { + uint64var = sp->st_ex_size; + result = dalloc_add_copy(meta, &uint64var, uint64_t); + if (result != 0) { + return false; + } + } else if (strcmp(attribute, "kMDItemFSOwnerUserID") == 0) { + uint64var = sp->st_ex_uid; + result = dalloc_add_copy(meta, &uint64var, uint64_t); + if (result != 0) { + return false; + } + } else if (strcmp(attribute, "kMDItemFSOwnerGroupID") == 0) { + uint64var = sp->st_ex_gid; + result = dalloc_add_copy(meta, &uint64var, uint64_t); + if (result != 0) { + return false; + } + } else if (strcmp(attribute, "kMDItemFSContentChangeDate") == 0 || + strcmp(attribute, "kMDItemContentModificationDate") == 0) + { + sl_time = convert_timespec_to_timeval(sp->st_ex_mtime); + result = dalloc_add_copy(meta, &sl_time, sl_time_t); + if (result != 0) { + return false; + } + } else { + result = dalloc_add_copy(meta, &nil, sl_nil_t); + if (result != 0) { + return false; + } + } + } + + result = dalloc_add(fm_array, meta, sl_array_t); + if (result != 0) { + return false; + } + return true; +} + +static int cnid_comp_fn(const void *p1, const void *p2) +{ + const uint64_t *cnid1 = p1, *cnid2 = p2; + if (*cnid1 == *cnid2) { + return 0; + } + if (*cnid1 < *cnid2) { + return -1; + } + return 1; +} + +/** + * Create a sorted copy of a CNID array + **/ +static bool sort_cnids(struct sl_query *slq, const DALLOC_CTX *d) +{ + uint64_t *cnids = NULL; + int i; + const void *p; + + cnids = talloc_array(slq, uint64_t, dalloc_size(d)); + if (cnids == NULL) { + return false; + } + + for (i = 0; i < dalloc_size(d); i++) { + p = dalloc_get_object(d, i); + if (p == NULL) { + return NULL; + } + memcpy(&cnids[i], p, sizeof(uint64_t)); + } + qsort(cnids, dalloc_size(d), sizeof(uint64_t), cnid_comp_fn); + + slq->cnids = cnids; + slq->cnids_num = dalloc_size(d); + + return true; +} + +/** + * Allocate result handle used in the async Tracker cursor result + * handler for storing results + **/ +static bool create_result_handle(struct sl_query *slq) +{ + sl_nil_t nil = 0; + struct sl_rslts *query_results; + int result; + + if (slq->query_results) { + DEBUG(1, ("unexpected existing result handle\n")); + return false; + } + + query_results = talloc_zero(slq, struct sl_rslts); + if (query_results == NULL) { + return false; + } + + /* CNIDs */ + query_results->cnids = talloc_zero(query_results, sl_cnids_t); + if (query_results->cnids == NULL) { + return false; + } + query_results->cnids->ca_cnids = dalloc_new(query_results->cnids); + if (query_results->cnids->ca_cnids == NULL) { + return false; + } + + query_results->cnids->ca_unkn1 = 0xadd; + if (slq->ctx2 > UINT32_MAX) { + DEBUG(1,("64bit ctx2 id too large: 0x%jx\n", (uintmax_t)slq->ctx2)); + return false; + } + query_results->cnids->ca_context = (uint32_t)slq->ctx2; + + /* FileMeta */ + query_results->fm_array = dalloc_zero(query_results, sl_array_t); + if (query_results->fm_array == NULL) { + return false; + } + + /* For some reason the list of results always starts with a nil entry */ + result = dalloc_add_copy(query_results->fm_array, &nil, sl_nil_t); + if (result != 0) { + return false; + } + + slq->query_results = query_results; + return true; +} + +static bool add_results(sl_array_t *array, struct sl_query *slq) +{ + sl_filemeta_t *fm; + uint64_t status; + int result; + bool ok; + + /* + * Taken from network traces against a macOS SMB Spotlight server: if + * the search is not finished yet in the backend macOS returns 0x23, + * otherwise 0x0. + */ + if (slq->state >= SLQ_STATE_DONE) { + status = 0; + } else { + status = 0x23; + } + + /* FileMeta */ + fm = dalloc_zero(array, sl_filemeta_t); + if (fm == NULL) { + return false; + } + + result = dalloc_add_copy(array, &status, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(array, slq->query_results->cnids, sl_cnids_t); + if (result != 0) { + return false; + } + if (slq->query_results->num_results > 0) { + result = dalloc_add(fm, slq->query_results->fm_array, sl_array_t); + if (result != 0) { + return false; + } + } + result = dalloc_add(array, fm, sl_filemeta_t); + if (result != 0) { + return false; + } + + /* This ensure the results get clean up after been sent to the client */ + talloc_move(array, &slq->query_results); + + ok = create_result_handle(slq); + if (!ok) { + DEBUG(1, ("couldn't add result handle\n")); + slq->state = SLQ_STATE_ERROR; + return false; + } + + return true; +} + +static const struct slrpc_cmd *slrpc_cmd_by_name(const char *rpccmd) +{ + size_t i; + static const struct slrpc_cmd cmds[] = { + { "fetchPropertiesForContext:", slrpc_fetch_properties}, + { "openQueryWithParams:forContext:", slrpc_open_query}, + { "fetchQueryResultsForContext:", slrpc_fetch_query_results}, + { "storeAttributes:forOIDArray:context:", slrpc_store_attributes}, + { "fetchAttributeNamesForOIDArray:context:", slrpc_fetch_attributenames}, + { "fetchAttributes:forOIDArray:context:", slrpc_fetch_attributes}, + { "fetchAllAttributes:forOIDArray:context:", slrpc_fetch_attributes}, + { "closeQueryForContext:", slrpc_close_query}, + }; + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + int cmp; + + cmp = strcmp(cmds[i].name, rpccmd); + if (cmp == 0) { + return &cmds[i]; + } + } + + return NULL; +} + +/** + * Search the list of active queries given their context ids + **/ +static struct sl_query *slq_for_ctx(struct mds_ctx *mds_ctx, + uint64_t ctx1, uint64_t ctx2) +{ + struct sl_query *q; + + for (q = mds_ctx->query_list; q; q = q->next) { + if ((q->ctx1 == ctx1) && (q->ctx2 == ctx2)) { + return q; + } + } + + return NULL; +} + +static int slq_destructor_cb(struct sl_query *slq) +{ + SLQ_DEBUG(10, slq, "destroying"); + + /* Free all entries before freeing the slq handle! */ + TALLOC_FREE(slq->entries_ctx); + TALLOC_FREE(slq->te); + + if (slq->mds_ctx != NULL) { + DLIST_REMOVE(slq->mds_ctx->query_list, slq); + slq->mds_ctx = NULL; + } + + TALLOC_FREE(slq->backend_private); + + return 0; +} + +/** + * Remove talloc_refcounted entry from mapping db + * + * Multiple queries (via the slq handle) may reference a + * sl_inode_path_map entry, when the last reference goes away as the + * queries are closed and this gets called to remove the entry from + * the db. + **/ +static int ino_path_map_destr_cb(struct sl_inode_path_map *entry) +{ + NTSTATUS status; + TDB_DATA key; + + key = make_tdb_data((uint8_t *)&entry->ino, sizeof(entry->ino)); + + status = dbwrap_delete(entry->mds_ctx->ino_path_map, key); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to delete record: %s\n", nt_errstr(status))); + return -1; + } + + DBG_DEBUG("deleted [0x%"PRIx64"] [%s]\n", entry->ino, entry->path); + return 0; +} + +/** + * Add result to inode->path mapping dbwrap rbt db + * + * This is necessary as a CNID db substitute, ie we need a way to + * simulate unique, constant numerical identifiers for paths with an + * API that supports mapping from id to path. + * + * Entries are talloc'ed of the query, using talloc_reference() if + * multiple queries returned the same result. That way we can cleanup + * entries by calling talloc_free() on the query slq handles. + **/ + +static bool inode_map_add(struct sl_query *slq, + uint64_t ino, + const char *path, + struct stat_ex *st) +{ + NTSTATUS status; + struct sl_inode_path_map *entry; + TDB_DATA key, value; + void *p; + + key = make_tdb_data((uint8_t *)&ino, sizeof(ino)); + status = dbwrap_fetch(slq->mds_ctx->ino_path_map, slq, key, &value); + + if (NT_STATUS_IS_OK(status)) { + /* + * We have one db, so when different parallel queries + * return the same file, we have to refcount entries + * in the db. + */ + + if (value.dsize != sizeof(void *)) { + DEBUG(1, ("invalid dsize\n")); + return false; + } + memcpy(&p, value.dptr, sizeof(p)); + entry = talloc_get_type_abort(p, struct sl_inode_path_map); + + DEBUG(10, ("map: %s\n", entry->path)); + + entry = talloc_reference(slq->entries_ctx, entry); + if (entry == NULL) { + DEBUG(1, ("talloc_reference failed\n")); + return false; + } + return true; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(1, ("dbwrap_fetch failed %s\n", nt_errstr(status))); + return false; + } + + entry = talloc_zero(slq->entries_ctx, struct sl_inode_path_map); + if (entry == NULL) { + DEBUG(1, ("talloc failed\n")); + return false; + } + + entry->ino = ino; + entry->mds_ctx = slq->mds_ctx; + entry->st = *st; + entry->path = talloc_strdup(entry, path); + if (entry->path == NULL) { + DEBUG(1, ("talloc failed\n")); + TALLOC_FREE(entry); + return false; + } + + status = dbwrap_store(slq->mds_ctx->ino_path_map, key, + make_tdb_data((void *)&entry, sizeof(void *)), 0); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to store record: %s\n", nt_errstr(status))); + TALLOC_FREE(entry); + return false; + } + + talloc_set_destructor(entry, ino_path_map_destr_cb); + + return true; +} + +bool mds_add_result(struct sl_query *slq, const char *path) +{ + struct smb_filename *smb_fname = NULL; + const char *relative = NULL; + char *fake_path = NULL; + struct stat_ex sb; + uint64_t ino64; + int result; + NTSTATUS status; + bool sub; + bool ok; + + /* + * We're in a tevent callback which means in the case of + * running as external RPC service we're running as root and + * not as the user. + */ + if (!become_authenticated_pipe_user(slq->mds_ctx->pipe_session_info)) { + DBG_ERR("can't become authenticated user: %d\n", + slq->mds_ctx->uid); + smb_panic("can't become authenticated user"); + } + + if (geteuid() != slq->mds_ctx->uid) { + DBG_ERR("uid mismatch: %d/%d\n", geteuid(), slq->mds_ctx->uid); + smb_panic("uid mismatch"); + } + + /* + * We've changed identity to the authenticated pipe user, so + * any function exit below must ensure we switch back + */ + + status = synthetic_pathref(talloc_tos(), + slq->mds_ctx->conn->cwd_fsp, + path, + NULL, + NULL, + 0, + 0, + &smb_fname); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("synthetic_pathref [%s]: %s\n", + smb_fname_str_dbg(smb_fname), + nt_errstr(status)); + unbecome_authenticated_pipe_user(); + return true; + } + + sb = smb_fname->st; + + status = smbd_check_access_rights_fsp(slq->mds_ctx->conn->cwd_fsp, + smb_fname->fsp, + false, + FILE_READ_DATA); + unbecome_authenticated_pipe_user(); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname); + return true; + } + + /* Done with smb_fname now. */ + TALLOC_FREE(smb_fname); + + ino64 = SMB_VFS_FS_FILE_ID(slq->mds_ctx->conn, &sb); + + if (slq->cnids) { + bool found; + + /* + * Check whether the found element is in the requested + * set of IDs. Note that we're faking CNIDs by using + * filesystem inode numbers here + */ + found = bsearch(&ino64, + slq->cnids, + slq->cnids_num, + sizeof(uint64_t), + cnid_comp_fn); + if (!found) { + return true; + } + } + + sub = subdir_of(slq->mds_ctx->spath, + slq->mds_ctx->spath_len, + path, + &relative); + if (!sub) { + DBG_ERR("[%s] is not inside [%s]\n", + path, slq->mds_ctx->spath); + slq->state = SLQ_STATE_ERROR; + return false; + } + + /* + * Add inode number and filemeta to result set, this is what + * we return as part of the result set of a query + */ + result = dalloc_add_copy(slq->query_results->cnids->ca_cnids, + &ino64, + uint64_t); + if (result != 0) { + DBG_ERR("dalloc error\n"); + slq->state = SLQ_STATE_ERROR; + return false; + } + + fake_path = talloc_asprintf(slq, + "/%s/%s", + slq->mds_ctx->sharename, + relative); + if (fake_path == NULL) { + slq->state = SLQ_STATE_ERROR; + return false; + } + + ok = add_filemeta(slq->mds_ctx, + slq->reqinfo, + slq->query_results->fm_array, + fake_path, + &sb); + if (!ok) { + DBG_ERR("add_filemeta error\n"); + TALLOC_FREE(fake_path); + slq->state = SLQ_STATE_ERROR; + return false; + } + + ok = inode_map_add(slq, ino64, fake_path, &sb); + TALLOC_FREE(fake_path); + if (!ok) { + DEBUG(1, ("inode_map_add error\n")); + slq->state = SLQ_STATE_ERROR; + return false; + } + + slq->query_results->num_results++; + return true; +} + +/*********************************************************** + * Spotlight RPC functions + ***********************************************************/ + +static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply) +{ + sl_dict_t *dict; + sl_array_t *array; + char *s; + uint64_t u; + sl_bool_t b; + sl_uuid_t uuid; + int result; + + dict = dalloc_zero(reply, sl_dict_t); + if (dict == NULL) { + return false; + } + + /* kMDSStoreHasPersistentUUID = false */ + result = dalloc_stradd(dict, "kMDSStoreHasPersistentUUID"); + if (result != 0) { + return false; + } + b = false; + result = dalloc_add_copy(dict, &b, sl_bool_t); + if (result != 0) { + return false; + } + + /* kMDSStoreIsBackup = false */ + result = dalloc_stradd(dict, "kMDSStoreIsBackup"); + if (result != 0) { + return false; + } + b = false; + result = dalloc_add_copy(dict, &b, sl_bool_t); + if (result != 0) { + return false; + } + + /* kMDSStoreUUID = uuid */ + result = dalloc_stradd(dict, "kMDSStoreUUID"); + if (result != 0) { + return false; + } + memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid)); + result = dalloc_add_copy(dict, &uuid, sl_uuid_t); + if (result != 0) { + return false; + } + + /* kMDSStoreSupportsVolFS = true */ + result = dalloc_stradd(dict, "kMDSStoreSupportsVolFS"); + if (result != 0) { + return false; + } + b = true; + result = dalloc_add_copy(dict, &b, sl_bool_t); + if (result != 0) { + return false; + } + + /* kMDSVolumeUUID = uuid */ + result = dalloc_stradd(dict, "kMDSVolumeUUID"); + if (result != 0) { + return false; + } + memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid)); + result = dalloc_add_copy(dict, &uuid, sl_uuid_t); + if (result != 0) { + return false; + } + + /* kMDSDiskStoreSpindleNumber = 1 (fake) */ + result = dalloc_stradd(dict, "kMDSDiskStoreSpindleNumber"); + if (result != 0) { + return false; + } + u = 1; + result = dalloc_add_copy(dict, &u, uint64_t); + if (result != 0) { + return false; + } + + /* kMDSDiskStorePolicy = 3 (whatever that means, taken from OS X) */ + result = dalloc_stradd(dict, "kMDSDiskStorePolicy"); + if (result != 0) { + return false; + } + u = 3; + result = dalloc_add_copy(dict, &u, uint64_t); + if (result != 0) { + return false; + } + + /* kMDSStoreMetaScopes array */ + result = dalloc_stradd(dict, "kMDSStoreMetaScopes"); + if (result != 0) { + return false; + } + array = dalloc_zero(dict, sl_array_t); + if (array == NULL) { + return NULL; + } + result = dalloc_stradd(array, "kMDQueryScopeComputer"); + if (result != 0) { + return false; + } + result = dalloc_stradd(array, "kMDQueryScopeAllIndexed"); + if (result != 0) { + return false; + } + result = dalloc_stradd(array, "kMDQueryScopeComputerIndexed"); + if (result != 0) { + return false; + } + result = dalloc_add(dict, array, sl_array_t); + if (result != 0) { + return false; + } + + /* kMDSStoreDevice = 0x1000003 (whatever that means, taken from OS X) */ + result = dalloc_stradd(dict, "kMDSStoreDevice"); + if (result != 0) { + return false; + } + u = 0x1000003; + result = dalloc_add_copy(dict, &u, uint64_t); + if (result != 0) { + return false; + } + + /* kMDSStoreSupportsTCC = true (whatever that means, taken from OS X) */ + result = dalloc_stradd(dict, "kMDSStoreSupportsTCC"); + if (result != 0) { + return false; + } + b = true; + result = dalloc_add_copy(dict, &b, sl_bool_t); + if (result != 0) { + return false; + } + + /* kMDSStorePathScopes = ["/"] (whatever that means, taken from OS X) */ + result = dalloc_stradd(dict, "kMDSStorePathScopes"); + if (result != 0) { + return false; + } + array = dalloc_zero(dict, sl_array_t); + if (array == NULL) { + return false; + } + s = talloc_strdup(dict, "/"); + if (s == NULL) { + return false; + } + talloc_set_name(s, "smb_ucs2_t *"); + result = dalloc_add(array, s, smb_ucs2_t *); + if (result != 0) { + return false; + } + result = dalloc_add(dict, array, sl_array_t); + if (result != 0) { + return false; + } + + result = dalloc_add(reply, dict, sl_dict_t); + if (result != 0) { + return false; + } + + return true; +} + +static void slq_close_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct sl_query *slq = talloc_get_type_abort( + private_data, struct sl_query); + struct mds_ctx *mds_ctx = slq->mds_ctx; + + SLQ_DEBUG(10, slq, "expired"); + + TALLOC_FREE(slq); + + if (CHECK_DEBUGLVL(10)) { + for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) { + SLQ_DEBUG(10, slq, "pending"); + } + } +} + +/** + * Translate a fake scope from the client like /sharename/dir + * to the real server-side path, replacing the "/sharename" part + * with the absolute server-side path of the share. + **/ +static bool mdssvc_real_scope(struct sl_query *slq, const char *fake_scope) +{ + size_t sname_len = strlen(slq->mds_ctx->sharename); + size_t fake_scope_len = strlen(fake_scope); + + if (fake_scope_len < sname_len + 1) { + DBG_ERR("Short scope [%s] for share [%s]\n", + fake_scope, slq->mds_ctx->sharename); + return false; + } + + slq->path_scope = talloc_asprintf(slq, + "%s%s", + slq->mds_ctx->spath, + fake_scope + sname_len + 1); + if (slq->path_scope == NULL) { + return false; + } + return true; +} + +/** + * Begin a search query + **/ +static bool slrpc_open_query(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply) +{ + bool ok; + uint64_t sl_result; + uint64_t *uint64p; + DALLOC_CTX *reqinfo; + sl_array_t *array, *path_scope; + sl_cnids_t *cnids; + struct sl_query *slq = NULL; + int result; + const char *querystring = NULL; + size_t querystring_len; + char *dest = NULL; + size_t dest_remaining; + size_t nconv; + char *scope = NULL; + + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + + /* Allocate and initialize query object */ + slq = talloc_zero(mds_ctx, struct sl_query); + if (slq == NULL) { + return false; + } + slq->entries_ctx = talloc_named_const(slq, 0, "struct sl_query.entries_ctx"); + if (slq->entries_ctx == NULL) { + TALLOC_FREE(slq); + return false; + } + talloc_set_destructor(slq, slq_destructor_cb); + slq->state = SLQ_STATE_NEW; + slq->mds_ctx = mds_ctx; + + slq->last_used = timeval_current(); + slq->start_time = slq->last_used; + slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0); + slq->te = tevent_add_timer(global_event_context(), slq, + slq->expire_time, slq_close_timer, slq); + if (slq->te == NULL) { + DEBUG(1, ("tevent_add_timer failed\n")); + goto error; + } + + querystring = dalloc_value_for_key(query, "DALLOC_CTX", 0, + "DALLOC_CTX", 1, + "kMDQueryString", + "char *"); + if (querystring == NULL) { + DEBUG(1, ("missing kMDQueryString\n")); + goto error; + } + + querystring_len = talloc_array_length(querystring); + + slq->query_string = talloc_array(slq, char, querystring_len); + if (slq->query_string == NULL) { + DEBUG(1, ("out of memory\n")); + goto error; + } + dest = slq->query_string; + dest_remaining = talloc_array_length(dest); + + nconv = smb_iconv(mds_ctx->ic_nfd_to_nfc, + &querystring, + &querystring_len, + &dest, + &dest_remaining); + if (nconv == (size_t)-1) { + DBG_ERR("smb_iconv failed for: %s\n", querystring); + return false; + } + + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 1); + if (uint64p == NULL) { + goto error; + } + slq->ctx1 = *uint64p; + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 2); + if (uint64p == NULL) { + goto error; + } + slq->ctx2 = *uint64p; + + path_scope = dalloc_value_for_key(query, "DALLOC_CTX", 0, + "DALLOC_CTX", 1, + "kMDScopeArray", + "sl_array_t"); + if (path_scope == NULL) { + DBG_ERR("missing kMDScopeArray\n"); + goto error; + } + + scope = dalloc_get(path_scope, "char *", 0); + if (scope == NULL) { + scope = dalloc_get(path_scope, + "DALLOC_CTX", 0, + "char *", 0); + } + if (scope == NULL) { + DBG_ERR("Failed to parse kMDScopeArray\n"); + goto error; + } + + ok = mdssvc_real_scope(slq, scope); + if (!ok) { + goto error; + } + + reqinfo = dalloc_value_for_key(query, "DALLOC_CTX", 0, + "DALLOC_CTX", 1, + "kMDAttributeArray", + "sl_array_t"); + if (reqinfo == NULL) { + DBG_ERR("missing kMDAttributeArray\n"); + goto error; + } + + slq->reqinfo = talloc_steal(slq, reqinfo); + DEBUG(10, ("requested attributes: %s", dalloc_dump(reqinfo, 0))); + + cnids = dalloc_value_for_key(query, "DALLOC_CTX", 0, + "DALLOC_CTX", 1, + "kMDQueryItemArray", + "sl_array_t"); + if (cnids) { + ok = sort_cnids(slq, cnids->ca_cnids); + if (!ok) { + goto error; + } + } + + ok = create_result_handle(slq); + if (!ok) { + DEBUG(1, ("create_result_handle error\n")); + slq->state = SLQ_STATE_ERROR; + goto error; + } + + SLQ_DEBUG(10, slq, "new"); + + DLIST_ADD(mds_ctx->query_list, slq); + + ok = mds_ctx->backend->search_start(slq); + if (!ok) { + DBG_ERR("backend search_start failed\n"); + goto error; + } + + sl_result = 0; + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + goto error; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + goto error; + } + return true; + +error: + sl_result = UINT64_MAX; + TALLOC_FREE(slq); + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + return true; +} + +/** + * Fetch results of a query + **/ +static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, + DALLOC_CTX *reply) +{ + bool ok; + struct sl_query *slq = NULL; + uint64_t *uint64p, ctx1, ctx2; + uint64_t status; + sl_array_t *array; + int result; + + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + + /* Get query for context */ + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 1); + if (uint64p == NULL) { + goto error; + } + ctx1 = *uint64p; + + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 2); + if (uint64p == NULL) { + goto error; + } + ctx2 = *uint64p; + + slq = slq_for_ctx(mds_ctx, ctx1, ctx2); + if (slq == NULL) { + DEBUG(1, ("bad context: [0x%jx,0x%jx]\n", + (uintmax_t)ctx1, (uintmax_t)ctx2)); + goto error; + } + + TALLOC_FREE(slq->te); + slq->last_used = timeval_current(); + slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0); + slq->te = tevent_add_timer(global_event_context(), slq, + slq->expire_time, slq_close_timer, slq); + if (slq->te == NULL) { + DEBUG(1, ("tevent_add_timer failed\n")); + goto error; + } + + SLQ_DEBUG(10, slq, "fetch"); + + switch (slq->state) { + case SLQ_STATE_RUNNING: + case SLQ_STATE_RESULTS: + case SLQ_STATE_FULL: + case SLQ_STATE_DONE: + ok = add_results(array, slq); + if (!ok) { + DEBUG(1, ("error adding results\n")); + goto error; + } + if (slq->state == SLQ_STATE_FULL) { + slq->state = SLQ_STATE_RUNNING; + slq->mds_ctx->backend->search_cont(slq); + } + break; + + case SLQ_STATE_ERROR: + DEBUG(1, ("query in error state\n")); + goto error; + + default: + DEBUG(1, ("unexpected query state %d\n", slq->state)); + goto error; + } + + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + goto error; + } + return true; + +error: + status = UINT64_MAX; + TALLOC_FREE(slq); + result = dalloc_add_copy(array, &status, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + return true; +} + +/** + * Store metadata attributes for a CNID + **/ +static bool slrpc_store_attributes(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply) +{ + uint64_t sl_result; + sl_array_t *array; + int result; + + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + + /* + * FIXME: not implemented. Used by the client for eg setting + * the modification date of the shared directory which clients + * poll indicating changes on the share and cause the client + * to refresh view. + */ + + sl_result = 0; + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + + return true; +} + +/** + * Fetch supported metadata attributes for a CNID + **/ +static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, + DALLOC_CTX *reply) +{ + uint64_t id; + sl_cnids_t *cnids; + sl_array_t *array; + uint64_t sl_result; + sl_cnids_t *replycnids; + sl_array_t *mdattrs; + sl_filemeta_t *fmeta; + int result; + void *p; + + cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 1); + if (cnids == NULL) { + return false; + } + + p = dalloc_get_object(cnids->ca_cnids, 0); + if (p == NULL) { + return NULL; + } + memcpy(&id, p, sizeof(uint64_t)); + + /* Result array */ + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + + /* Return result value 0 */ + sl_result = 0; + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + return false; + } + + /* Return CNID array */ + replycnids = talloc_zero(reply, sl_cnids_t); + if (replycnids == NULL) { + return false; + } + + replycnids->ca_cnids = dalloc_new(cnids); + if (replycnids->ca_cnids == NULL) { + return false; + } + + replycnids->ca_unkn1 = 0xfec; + replycnids->ca_context = cnids->ca_context; + result = dalloc_add_copy(replycnids->ca_cnids, &id, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(array, replycnids, sl_cnids_t); + if (result != 0) { + return false; + } + + /* + * FIXME: this should return the real attributes from all + * known metadata sources (Tracker and filesystem) + */ + mdattrs = dalloc_zero(reply, sl_array_t); + if (mdattrs == NULL) { + return false; + } + + result = dalloc_stradd(mdattrs, "kMDItemFSName"); + if (result != 0) { + return false; + } + result = dalloc_stradd(mdattrs, "kMDItemDisplayName"); + if (result != 0) { + return false; + } + result = dalloc_stradd(mdattrs, "kMDItemFSSize"); + if (result != 0) { + return false; + } + result = dalloc_stradd(mdattrs, "kMDItemFSOwnerUserID"); + if (result != 0) { + return false; + } + result = dalloc_stradd(mdattrs, "kMDItemFSOwnerGroupID"); + if (result != 0) { + return false; + } + result = dalloc_stradd(mdattrs, "kMDItemFSContentChangeDate"); + if (result != 0) { + return false; + } + + fmeta = dalloc_zero(reply, sl_filemeta_t); + if (fmeta == NULL) { + return false; + } + result = dalloc_add(fmeta, mdattrs, sl_array_t); + if (result != 0) { + return false; + } + result = dalloc_add(array, fmeta, sl_filemeta_t); + if (result != 0) { + return false; + } + + return true; +} + +/** + * Fetch metadata attribute values for a CNID + **/ +static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply) +{ + int result; + bool ok; + sl_array_t *array; + sl_cnids_t *cnids; + sl_cnids_t *replycnids; + sl_array_t *reqinfo; + uint64_t ino; + uint64_t sl_result; + sl_filemeta_t *fm; + sl_array_t *fm_array; + sl_nil_t nil; + char *path = NULL; + struct smb_filename *smb_fname = NULL; + struct stat_ex *sp = NULL; + struct sl_inode_path_map *elem = NULL; + void *p; + TDB_DATA val = tdb_null; + NTSTATUS status; + + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + replycnids = talloc_zero(reply, sl_cnids_t); + if (replycnids == NULL) { + goto error; + } + replycnids->ca_cnids = dalloc_new(replycnids); + if (replycnids->ca_cnids == NULL) { + goto error; + } + fm = dalloc_zero(array, sl_filemeta_t); + if (fm == NULL) { + goto error; + } + fm_array = dalloc_zero(fm, sl_array_t); + if (fm_array == NULL) { + goto error; + } + /* For some reason the list of results always starts with a nil entry */ + result = dalloc_add_copy(fm_array, &nil, sl_nil_t); + if (result == -1) { + goto error; + } + + reqinfo = dalloc_get(query, "DALLOC_CTX", 0, "sl_array_t", 1); + if (reqinfo == NULL) { + goto error; + } + + cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 2); + if (cnids == NULL) { + goto error; + } + p = dalloc_get_object(cnids->ca_cnids, 0); + if (p == NULL) { + goto error; + } + memcpy(&ino, p, sizeof(uint64_t)); + + replycnids->ca_unkn1 = 0xfec; + replycnids->ca_context = cnids->ca_context; + result = dalloc_add_copy(replycnids->ca_cnids, &ino, uint64_t); + if (result != 0) { + goto error; + } + + status = dbwrap_fetch(mds_ctx->ino_path_map, reply, + make_tdb_data((void*)&ino, sizeof(uint64_t)), + &val); + if (NT_STATUS_IS_OK(status)) { + if (val.dsize != sizeof(p)) { + DBG_ERR("invalid record pointer size: %zd\n", val.dsize); + TALLOC_FREE(val.dptr); + goto error; + } + + memcpy(&p, val.dptr, sizeof(p)); + elem = talloc_get_type_abort(p, struct sl_inode_path_map); + path = elem->path; + + sp = &elem->st; + } + + ok = add_filemeta(mds_ctx, reqinfo, fm_array, path, sp); + if (!ok) { + goto error; + } + + sl_result = 0; + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + goto error; + } + result = dalloc_add(array, replycnids, sl_cnids_t); + if (result != 0) { + goto error; + } + result = dalloc_add(fm, fm_array, sl_array_t); + if (result != 0) { + goto error; + } + result = dalloc_add(array, fm, sl_filemeta_t); + if (result != 0) { + goto error; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + goto error; + } + + TALLOC_FREE(smb_fname); + return true; + +error: + + TALLOC_FREE(smb_fname); + sl_result = UINT64_MAX; + result = dalloc_add_copy(array, &sl_result, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + + return true; +} + +/** + * Close a query + **/ +static bool slrpc_close_query(struct mds_ctx *mds_ctx, + const DALLOC_CTX *query, DALLOC_CTX *reply) +{ + struct sl_query *slq = NULL; + uint64_t *uint64p, ctx1, ctx2; + sl_array_t *array; + uint64_t sl_res; + int result; + + array = dalloc_zero(reply, sl_array_t); + if (array == NULL) { + return false; + } + + /* Context */ + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 1); + if (uint64p == NULL) { + goto done; + } + ctx1 = *uint64p; + + uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "uint64_t", 2); + if (uint64p == NULL) { + goto done; + } + ctx2 = *uint64p; + + /* Get query for context and free it */ + slq = slq_for_ctx(mds_ctx, ctx1, ctx2); + if (slq == NULL) { + DEBUG(1, ("bad context: [0x%jx,0x%jx]\n", + (uintmax_t)ctx1, (uintmax_t)ctx2)); + goto done; + } + + SLQ_DEBUG(10, slq, "close"); + TALLOC_FREE(slq); + +done: + sl_res = UINT64_MAX; + result = dalloc_add_copy(array, &sl_res, uint64_t); + if (result != 0) { + return false; + } + result = dalloc_add(reply, array, sl_array_t); + if (result != 0) { + return false; + } + return true; +} + +static struct mdssvc_ctx *mdssvc_init(struct tevent_context *ev) +{ + bool ok; + + if (mdssvc_ctx != NULL) { + return mdssvc_ctx; + } + + mdssvc_ctx = talloc_zero(ev, struct mdssvc_ctx); + if (mdssvc_ctx == NULL) { + return NULL; + } + + mdssvc_ctx->ev_ctx = ev; + + ok = mdsscv_backend_noindex.init(mdssvc_ctx); + if (!ok) { + DBG_ERR("backend init failed\n"); + TALLOC_FREE(mdssvc_ctx); + return NULL; + } + +#ifdef HAVE_SPOTLIGHT_BACKEND_ES + ok = mdsscv_backend_es.init(mdssvc_ctx); + if (!ok) { + DBG_ERR("backend init failed\n"); + TALLOC_FREE(mdssvc_ctx); + return NULL; + } +#endif + +#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER + ok = mdsscv_backend_tracker.init(mdssvc_ctx); + if (!ok) { + DBG_ERR("backend init failed\n"); + TALLOC_FREE(mdssvc_ctx); + return NULL; + } +#endif + + return mdssvc_ctx; +} + +/** + * Init callbacks at startup + * + * This gets typically called in the main parent smbd which means we can't + * initialize our global state here. + **/ +bool mds_init(struct messaging_context *msg_ctx) +{ + return true; +} + +bool mds_shutdown(void) +{ + bool ok; + + if (mdssvc_ctx == NULL) { + return false; + } + + ok = mdsscv_backend_noindex.shutdown(mdssvc_ctx); + if (!ok) { + goto fail; + } + +#ifdef HAVE_SPOTLIGHT_BACKEND_ES + ok = mdsscv_backend_es.shutdown(mdssvc_ctx); + if (!ok) { + goto fail; + } +#endif + +#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER + ok = mdsscv_backend_tracker.shutdown(mdssvc_ctx); + if (!ok) { + goto fail; + } +#endif + + ok = true; +fail: + TALLOC_FREE(mdssvc_ctx); + return ok; +} + +/** + * Tear down connections and free all resources + **/ +static int mds_ctx_destructor_cb(struct mds_ctx *mds_ctx) +{ + /* + * We need to free query_list before ino_path_map + */ + while (mds_ctx->query_list != NULL) { + /* + * slq destructor removes element from list. + * Don't use TALLOC_FREE()! + */ + talloc_free(mds_ctx->query_list); + } + TALLOC_FREE(mds_ctx->ino_path_map); + + if (mds_ctx->conn != NULL) { + SMB_VFS_DISCONNECT(mds_ctx->conn); + conn_free(mds_ctx->conn); + } + + ZERO_STRUCTP(mds_ctx); + + return 0; +} + +/** + * Initialise a context per RPC bind + * + * This ends up being called for every tcon, because the client does a + * RPC bind for every tcon, so this is actually a per tcon context. + **/ +NTSTATUS mds_init_ctx(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + int snum, + const char *sharename, + const char *path, + struct mds_ctx **_mds_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct smb_filename conn_basedir; + struct mds_ctx *mds_ctx; + int backend; + int ret; + bool ok; + smb_iconv_t iconv_hnd = (smb_iconv_t)-1; + NTSTATUS status; + + if (!lp_spotlight(snum)) { + return NT_STATUS_WRONG_VOLUME; + } + + mds_ctx = talloc_zero(mem_ctx, struct mds_ctx); + if (mds_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(mds_ctx, mds_ctx_destructor_cb); + + mds_ctx->mdssvc_ctx = mdssvc_init(ev); + if (mds_ctx->mdssvc_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + backend = lp_spotlight_backend(snum); + switch (backend) { + case SPOTLIGHT_BACKEND_NOINDEX: + mds_ctx->backend = &mdsscv_backend_noindex; + break; + +#ifdef HAVE_SPOTLIGHT_BACKEND_ES + case SPOTLIGHT_BACKEND_ES: + mds_ctx->backend = &mdsscv_backend_es; + break; +#endif + +#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER + case SPOTLIGHT_BACKEND_TRACKER: + mds_ctx->backend = &mdsscv_backend_tracker; + break; +#endif + default: + DBG_ERR("Unknown backend %d\n", backend); + TALLOC_FREE(mdssvc_ctx); + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + iconv_hnd = smb_iconv_open_ex(mds_ctx, + "UTF8-NFD", + "UTF8-NFC", + false); + if (iconv_hnd == (smb_iconv_t)-1) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + mds_ctx->ic_nfc_to_nfd = iconv_hnd; + + iconv_hnd = smb_iconv_open_ex(mds_ctx, + "UTF8-NFC", + "UTF8-NFD", + false); + if (iconv_hnd == (smb_iconv_t)-1) { + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + mds_ctx->ic_nfd_to_nfc = iconv_hnd; + + mds_ctx->sharename = talloc_strdup(mds_ctx, sharename); + if (mds_ctx->sharename == NULL) { + status = NT_STATUS_NO_MEMORY; + goto error; + } + + mds_ctx->spath = talloc_strdup(mds_ctx, path); + if (mds_ctx->spath == NULL) { + status = NT_STATUS_NO_MEMORY; + goto error; + } + mds_ctx->spath_len = strlen(path); + + mds_ctx->snum = snum; + mds_ctx->pipe_session_info = session_info; + + if (session_info->security_token->num_sids < 1) { + status = NT_STATUS_BAD_LOGON_SESSION_STATE; + goto error; + } + sid_copy(&mds_ctx->sid, &session_info->security_token->sids[0]); + mds_ctx->uid = session_info->unix_token->uid; + + mds_ctx->ino_path_map = db_open_rbt(mds_ctx); + if (mds_ctx->ino_path_map == NULL) { + DEBUG(1,("open inode map db failed\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = create_conn_struct_cwd(mds_ctx, + ev, + msg_ctx, + session_info, + snum, + lp_path(talloc_tos(), lp_sub, snum), + &mds_ctx->conn); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("failed to create conn for vfs: %s\n", + nt_errstr(status)); + goto error; + } + + conn_basedir = (struct smb_filename) { + .base_name = mds_ctx->conn->connectpath, + }; + + ret = vfs_ChDir(mds_ctx->conn, &conn_basedir); + if (ret != 0) { + DBG_ERR("vfs_ChDir [%s] failed: %s\n", + conn_basedir.base_name, strerror(errno)); + status = map_nt_error_from_unix(errno); + goto error; + } + + ok = mds_ctx->backend->connect(mds_ctx); + if (!ok) { + DBG_ERR("backend connect failed\n"); + status = NT_STATUS_CONNECTION_RESET; + goto error; + } + + *_mds_ctx = mds_ctx; + return NT_STATUS_OK; + +error: + if (mds_ctx->ic_nfc_to_nfd != NULL) { + smb_iconv_close(mds_ctx->ic_nfc_to_nfd); + } + if (mds_ctx->ic_nfd_to_nfc != NULL) { + smb_iconv_close(mds_ctx->ic_nfd_to_nfc); + } + + TALLOC_FREE(mds_ctx); + return status; +} + +/** + * Dispatch a Spotlight RPC command + **/ +bool mds_dispatch(struct mds_ctx *mds_ctx, + struct mdssvc_blob *request_blob, + struct mdssvc_blob *response_blob, + size_t max_fragment_size) +{ + bool ok; + int ret; + DALLOC_CTX *query = NULL; + DALLOC_CTX *reply = NULL; + char *rpccmd; + const struct slrpc_cmd *slcmd; + const struct smb_filename conn_basedir = { + .base_name = mds_ctx->conn->connectpath, + }; + NTSTATUS status; + + if (CHECK_DEBUGLVL(10)) { + const struct sl_query *slq; + + for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) { + SLQ_DEBUG(10, slq, "pending"); + } + } + + response_blob->length = 0; + + DEBUG(10, ("share path: %s\n", mds_ctx->spath)); + + query = dalloc_new(mds_ctx); + if (query == NULL) { + ok = false; + goto cleanup; + } + reply = dalloc_new(mds_ctx); + if (reply == NULL) { + ok = false; + goto cleanup; + } + + ok = sl_unpack(query, (char *)request_blob->spotlight_blob, + request_blob->length); + if (!ok) { + DEBUG(1, ("error unpacking Spotlight RPC blob\n")); + goto cleanup; + } + + DEBUG(5, ("%s", dalloc_dump(query, 0))); + + rpccmd = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0, + "char *", 0); + if (rpccmd == NULL) { + DEBUG(1, ("missing primary Spotlight RPC command\n")); + ok = false; + goto cleanup; + } + + DEBUG(10, ("Spotlight RPC cmd: %s\n", rpccmd)); + + slcmd = slrpc_cmd_by_name(rpccmd); + if (slcmd == NULL) { + DEBUG(1, ("unsupported primary Spotlight RPC command %s\n", + rpccmd)); + ok = false; + goto cleanup; + } + + ret = vfs_ChDir(mds_ctx->conn, &conn_basedir); + if (ret != 0) { + DBG_ERR("vfs_ChDir [%s] failed: %s\n", + conn_basedir.base_name, strerror(errno)); + ok = false; + goto cleanup; + } + + ok = slcmd->function(mds_ctx, query, reply); + if (!ok) { + goto cleanup; + } + + DBG_DEBUG("%s", dalloc_dump(reply, 0)); + + status = sl_pack_alloc(response_blob, + reply, + response_blob, + max_fragment_size); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("sl_pack_alloc() failed\n"); + goto cleanup; + } + +cleanup: + talloc_free(query); + talloc_free(reply); + return ok; +} diff --git a/source3/rpc_server/mdssvc/mdssvc.h b/source3/rpc_server/mdssvc/mdssvc.h new file mode 100644 index 0000000..6d4e684 --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc.h @@ -0,0 +1,168 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 _MDSSVC_H +#define _MDSSVC_H + +#include "dalloc.h" +#include "marshalling.h" +#include "lib/util/dlinklist.h" +#include "librpc/gen_ndr/mdssvc.h" + +/* + * glib uses TRUE and FALSE which was redefined by "includes.h" to be + * unusable, undefine so glib can establish its own working + * replacement. + */ +#undef TRUE +#undef FALSE + +#define MAX_SL_RESULTS 100 +#define SL_PAGESIZE 50 +#define MAX_SL_RUNTIME 30 +#define MDS_TRACKER_ASYNC_TIMEOUT_MS 250 + +#define SLQ_DEBUG(lvl, _slq, state) do { if (CHECK_DEBUGLVL(lvl)) { \ + const struct sl_query *__slq = _slq; \ + struct timeval_buf start_buf; \ + const char *start; \ + struct timeval_buf last_used_buf; \ + const char *last_used; \ + struct timeval_buf expire_buf; \ + const char *expire; \ + start = timeval_str_buf(&__slq->start_time, false, \ + true, &start_buf); \ + last_used = timeval_str_buf(&__slq->last_used, false, \ + true, &last_used_buf); \ + expire = timeval_str_buf(&__slq->expire_time, false, \ + true, &expire_buf); \ + DEBUG(lvl,("%s slq[0x%jx,0x%jx], start: %s, last_used: %s, " \ + "expires: %s, query: '%s'\n", state, \ + (uintmax_t)__slq->ctx1, (uintmax_t)__slq->ctx2, \ + start, last_used, expire, __slq->query_string)); \ +}} while(0) + +/****************************************************************************** + * Some helper stuff dealing with queries + ******************************************************************************/ + +/* query state */ +typedef enum { + SLQ_STATE_NEW, /* Query received from client */ + SLQ_STATE_RUNNING, /* Query dispatched to Tracker */ + SLQ_STATE_RESULTS, /* Async Tracker query read */ + SLQ_STATE_FULL, /* the max amount of result has been queued */ + SLQ_STATE_DONE, /* Got all results from Tracker */ + SLQ_STATE_END, /* Query results returned to client */ + SLQ_STATE_ERROR /* an error happened somewhere */ +} slq_state_t; + +/* query structure */ +struct sl_query { + struct sl_query *prev, *next; /* list pointers */ + struct mds_ctx *mds_ctx; /* context handle */ + void *backend_private; /* search backend private data */ + slq_state_t state; /* query state */ + struct timeval start_time; /* Query start time */ + struct timeval last_used; /* Time of last result fetch */ + struct timeval expire_time; /* Query expiration time */ + struct tevent_timer *te; /* query timeout */ + uint64_t ctx1; /* client context 1 */ + uint64_t ctx2; /* client context 2 */ + sl_array_t *reqinfo; /* array with requested metadata */ + char *query_string; /* the Spotlight query string */ + uint64_t *cnids; /* restrict query to these CNIDs */ + size_t cnids_num; /* Size of slq_cnids array */ + const char *path_scope; /* path to directory to search */ + struct sl_rslts *query_results; /* query results */ + TALLOC_CTX *entries_ctx; /* talloc parent of the search results */ +}; + +struct sl_rslts { + int num_results; + sl_cnids_t *cnids; + sl_array_t *fm_array; +}; + +struct sl_inode_path_map { + struct mds_ctx *mds_ctx; + uint64_t ino; + char *path; + struct stat_ex st; +}; + +/* Per process state */ +struct mdssvc_ctx { + struct tevent_context *ev_ctx; + void *backend_private; +}; + +/* Per tree connect state */ +struct mds_ctx { + struct mdssvc_backend *backend; + struct mdssvc_ctx *mdssvc_ctx; + void *backend_private; + struct auth_session_info *pipe_session_info; + struct dom_sid sid; + uid_t uid; + smb_iconv_t ic_nfc_to_nfd; + smb_iconv_t ic_nfd_to_nfc; + int snum; + const char *sharename; + const char *spath; + size_t spath_len; + struct connection_struct *conn; + struct sl_query *query_list; /* list of active queries */ + struct db_context *ino_path_map; /* dbwrap rbt for storing inode->path mappings */ +}; + +struct mdssvc_backend { + bool (*init)(struct mdssvc_ctx *mdssvc_ctx); + bool (*connect)(struct mds_ctx *mds_ctx); + bool (*search_map)(struct sl_query *slq); + bool (*search_start)(struct sl_query *slq); + bool (*search_cont)(struct sl_query *slq); + bool (*shutdown)(struct mdssvc_ctx *mdssvc_ctx); +}; + +/****************************************************************************** + * Function declarations + ******************************************************************************/ + +/* + * mdssvc.c + */ +extern bool mds_init(struct messaging_context *msg_ctx); +extern bool mds_shutdown(void); +NTSTATUS mds_init_ctx(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + int snum, + const char *sharename, + const char *path, + struct mds_ctx **_mds_ctx); +extern bool mds_dispatch(struct mds_ctx *mds_ctx, + struct mdssvc_blob *request_blob, + struct mdssvc_blob *response_blob, + size_t max_fragment_size); +bool mds_add_result(struct sl_query *slq, const char *path); + +#endif /* _MDSSVC_H */ diff --git a/source3/rpc_server/mdssvc/mdssvc_es.c b/source3/rpc_server/mdssvc/mdssvc_es.c new file mode 100644 index 0000000..8460b48 --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_es.c @@ -0,0 +1,865 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / ES backend + + Copyright (C) Ralph Boehme 2019 + + 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 "system/filesys.h" +#include "lib/util/time_basic.h" +#include "lib/tls/tls.h" +#include "lib/util/tevent_ntstatus.h" +#include "libcli/http/http.h" +#include "lib/util/tevent_unix.h" +#include "credentials.h" +#include "mdssvc.h" +#include "mdssvc_es.h" +#include "rpc_server/mdssvc/es_parser.tab.h" + +#include <jansson.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define MDSSVC_ELASTIC_QUERY_TEMPLATE \ + "{" \ + " \"from\": %zu," \ + " \"size\": %zu," \ + " \"_source\": [%s]," \ + " \"query\": {" \ + " \"query_string\": {" \ + " \"query\": \"%s\"" \ + " }" \ + " }" \ + "}" + +#define MDSSVC_ELASTIC_SOURCES \ + "\"path.real\"" + +static bool mdssvc_es_init(struct mdssvc_ctx *mdssvc_ctx) +{ + struct mdssvc_es_ctx *mdssvc_es_ctx = NULL; + json_error_t json_error; + char *default_path = NULL; + const char *path = NULL; + + mdssvc_es_ctx = talloc_zero(mdssvc_ctx, struct mdssvc_es_ctx); + if (mdssvc_es_ctx == NULL) { + return false; + } + mdssvc_es_ctx->mdssvc_ctx = mdssvc_ctx; + + mdssvc_es_ctx->creds = cli_credentials_init_anon(mdssvc_es_ctx); + if (mdssvc_es_ctx->creds == NULL) { + TALLOC_FREE(mdssvc_es_ctx); + return false; + } + + default_path = talloc_asprintf( + mdssvc_es_ctx, + "%s/mdssvc/elasticsearch_mappings.json", + get_dyn_SAMBA_DATADIR()); + if (default_path == NULL) { + TALLOC_FREE(mdssvc_es_ctx); + return false; + } + + path = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "elasticsearch", + "mappings", + default_path); + if (path == NULL) { + TALLOC_FREE(mdssvc_es_ctx); + return false; + } + + mdssvc_es_ctx->mappings = json_load_file(path, 0, &json_error); + if (mdssvc_es_ctx->mappings == NULL) { + DBG_ERR("Opening mapping file [%s] failed: %s\n", + path, json_error.text); + TALLOC_FREE(mdssvc_es_ctx); + return false; + } + TALLOC_FREE(default_path); + + mdssvc_ctx->backend_private = mdssvc_es_ctx; + return true; +} + +static bool mdssvc_es_shutdown(struct mdssvc_ctx *mdssvc_ctx) +{ + return true; +} + +static struct tevent_req *mds_es_connect_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct mds_es_ctx *mds_es_ctx); +static int mds_es_connect_recv(struct tevent_req *req); +static void mds_es_connected(struct tevent_req *subreq); +static bool mds_es_next_search_trigger(struct mds_es_ctx *mds_es_ctx); +static void mds_es_search_set_pending(struct sl_es_search *s); +static void mds_es_search_unset_pending(struct sl_es_search *s); + +static int mds_es_ctx_destructor(struct mds_es_ctx *mds_es_ctx) +{ + struct sl_es_search *s = mds_es_ctx->searches; + + /* + * The per tree-connect state mds_es_ctx (a child of mds_ctx) is about + * to go away and has already freed all waiting searches. If there's a + * search remaining that's when the search is already active. Reset the + * mds_es_ctx pointer, so we can detect this when the search completes. + */ + + if (s == NULL) { + return 0; + } + + s->mds_es_ctx = NULL; + + return 0; +} + +static bool mds_es_connect(struct mds_ctx *mds_ctx) +{ + struct mdssvc_es_ctx *mdssvc_es_ctx = talloc_get_type_abort( + mds_ctx->mdssvc_ctx->backend_private, struct mdssvc_es_ctx); + struct mds_es_ctx *mds_es_ctx = NULL; + struct tevent_req *subreq = NULL; + + mds_es_ctx = talloc_zero(mds_ctx, struct mds_es_ctx); + if (mds_es_ctx == NULL) { + return false; + } + *mds_es_ctx = (struct mds_es_ctx) { + .mdssvc_es_ctx = mdssvc_es_ctx, + .mds_ctx = mds_ctx, + }; + + mds_ctx->backend_private = mds_es_ctx; + talloc_set_destructor(mds_es_ctx, mds_es_ctx_destructor); + + subreq = mds_es_connect_send( + mds_es_ctx, + mdssvc_es_ctx->mdssvc_ctx->ev_ctx, + mds_es_ctx); + if (subreq == NULL) { + TALLOC_FREE(mds_es_ctx); + return false; + } + tevent_req_set_callback(subreq, mds_es_connected, mds_es_ctx); + return true; +} + +static void mds_es_connected(struct tevent_req *subreq) +{ + struct mds_es_ctx *mds_es_ctx = tevent_req_callback_data( + subreq, struct mds_es_ctx); + int ret; + bool ok; + + ret = mds_es_connect_recv(subreq); + TALLOC_FREE(subreq); + if (ret != 0) { + DBG_ERR("HTTP connect failed\n"); + return; + } + + ok = mds_es_next_search_trigger(mds_es_ctx); + if (!ok) { + DBG_ERR("mds_es_next_search_trigger failed\n"); + } + return; +} + +struct mds_es_connect_state { + struct tevent_context *ev; + struct mds_es_ctx *mds_es_ctx; + struct tevent_queue_entry *qe; + const char *server_addr; + uint16_t server_port; + struct tstream_tls_params *tls_params; +}; + +static void mds_es_http_connect_done(struct tevent_req *subreq); +static void mds_es_http_waited(struct tevent_req *subreq); + +static struct tevent_req *mds_es_connect_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct mds_es_ctx *mds_es_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct mds_es_connect_state *state = NULL; + const char *server_addr = NULL; + bool use_tls; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, struct mds_es_connect_state); + if (req == NULL) { + return NULL; + } + *state = (struct mds_es_connect_state) { + .ev = ev, + .mds_es_ctx = mds_es_ctx, + }; + + server_addr = lp_parm_const_string( + mds_es_ctx->mds_ctx->snum, + "elasticsearch", + "address", + "localhost"); + state->server_addr = talloc_strdup(state, server_addr); + if (tevent_req_nomem(state->server_addr, req)) { + return tevent_req_post(req, ev); + } + + state->server_port = lp_parm_int( + mds_es_ctx->mds_ctx->snum, + "elasticsearch", + "port", + 9200); + + use_tls = lp_parm_bool( + mds_es_ctx->mds_ctx->snum, + "elasticsearch", + "use tls", + false); + + DBG_DEBUG("Connecting to HTTP%s [%s] port [%"PRIu16"]\n", + use_tls ? "S" : "", state->server_addr, state->server_port); + + if (use_tls) { + const char *ca_file = lp__tls_cafile(); + const char *crl_file = lp__tls_crlfile(); + const char *tls_priority = lp_tls_priority(); + enum tls_verify_peer_state verify_peer = lp_tls_verify_peer(); + + status = tstream_tls_params_client(state, + ca_file, + crl_file, + tls_priority, + verify_peer, + state->server_addr, + &state->tls_params); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed tstream_tls_params_client - %s\n", + nt_errstr(status)); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + } + + subreq = http_connect_send(state, + state->ev, + state->server_addr, + state->server_port, + mds_es_ctx->mdssvc_es_ctx->creds, + state->tls_params); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, mds_es_http_connect_done, req); + return req; +} + +static void mds_es_http_connect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct mds_es_connect_state *state = tevent_req_data( + req, struct mds_es_connect_state); + int error; + + error = http_connect_recv(subreq, + state->mds_es_ctx, + &state->mds_es_ctx->http_conn); + TALLOC_FREE(subreq); + if (error != 0) { + DBG_ERR("HTTP connect failed, retrying...\n"); + + subreq = tevent_wakeup_send( + state->mds_es_ctx, + state->mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx, + tevent_timeval_current_ofs(10, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + mds_es_http_waited, + req); + return; + } + + DBG_DEBUG("Connected to HTTP%s [%s] port [%"PRIu16"]\n", + state->tls_params ? "S" : "", + state->server_addr, state->server_port); + + tevent_req_done(req); + return; +} + +static void mds_es_http_waited(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct mds_es_connect_state *state = tevent_req_data( + req, struct mds_es_connect_state); + bool ok; + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, ETIMEDOUT); + return; + } + + subreq = mds_es_connect_send( + state->mds_es_ctx, + state->mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx, + state->mds_es_ctx); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, mds_es_connected, state->mds_es_ctx); +} + +static int mds_es_connect_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static void mds_es_reconnect_on_error(struct sl_es_search *s) +{ + struct mds_es_ctx *mds_es_ctx = s->mds_es_ctx; + struct tevent_req *subreq = NULL; + + if (s->slq != NULL) { + s->slq->state = SLQ_STATE_ERROR; + } + + DBG_WARNING("Reconnecting HTTP...\n"); + TALLOC_FREE(mds_es_ctx->http_conn); + + subreq = mds_es_connect_send( + mds_es_ctx, + mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx, + mds_es_ctx); + if (subreq == NULL) { + DBG_ERR("mds_es_connect_send failed\n"); + return; + } + tevent_req_set_callback(subreq, mds_es_connected, mds_es_ctx); +} + +static int search_destructor(struct sl_es_search *s) +{ + if (s->mds_es_ctx == NULL) { + return 0; + } + DLIST_REMOVE(s->mds_es_ctx->searches, s); + return 0; +} + +static struct tevent_req *mds_es_search_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sl_es_search *s); +static int mds_es_search_recv(struct tevent_req *req); +static void mds_es_search_done(struct tevent_req *subreq); + +static bool mds_es_search(struct sl_query *slq) +{ + struct mds_es_ctx *mds_es_ctx = talloc_get_type_abort( + slq->mds_ctx->backend_private, struct mds_es_ctx); + struct sl_es_search *s = NULL; + bool ok; + + s = talloc_zero(slq, struct sl_es_search); + if (s == NULL) { + return false; + } + *s = (struct sl_es_search) { + .ev = mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx, + .mds_es_ctx = mds_es_ctx, + .slq = slq, + .size = SL_PAGESIZE, + }; + + /* 0 would mean no limit */ + s->max = lp_parm_ulonglong(s->slq->mds_ctx->snum, + "elasticsearch", + "max results", + MAX_SL_RESULTS); + + DBG_DEBUG("Spotlight query: '%s'\n", slq->query_string); + + ok = map_spotlight_to_es_query( + s, + mds_es_ctx->mdssvc_es_ctx->mappings, + slq->path_scope, + slq->query_string, + &s->es_query); + if (!ok) { + TALLOC_FREE(s); + return false; + } + DBG_DEBUG("Elasticsearch query: '%s'\n", s->es_query); + + slq->backend_private = s; + slq->state = SLQ_STATE_RUNNING; + DLIST_ADD_END(mds_es_ctx->searches, s); + talloc_set_destructor(s, search_destructor); + + return mds_es_next_search_trigger(mds_es_ctx); +} + +static bool mds_es_next_search_trigger(struct mds_es_ctx *mds_es_ctx) +{ + struct tevent_req *subreq = NULL; + struct sl_es_search *s = mds_es_ctx->searches; + + if (mds_es_ctx->http_conn == NULL) { + DBG_DEBUG("Waiting for HTTP connection...\n"); + return true; + } + if (s == NULL) { + DBG_DEBUG("No pending searches, idling...\n"); + return true; + } + if (s->pending) { + DBG_DEBUG("Search pending [%p]\n", s); + return true; + } + + subreq = mds_es_search_send(s, s->ev, s); + if (subreq == NULL) { + return false; + } + tevent_req_set_callback(subreq, mds_es_search_done, s); + mds_es_search_set_pending(s); + return true; +} + +static void mds_es_search_done(struct tevent_req *subreq) +{ + struct sl_es_search *s = tevent_req_callback_data( + subreq, struct sl_es_search); + struct mds_es_ctx *mds_es_ctx = s->mds_es_ctx; + struct sl_query *slq = s->slq; + int ret; + bool ok; + + DBG_DEBUG("Search done for search [%p]\n", s); + + mds_es_search_unset_pending(s); + + if (mds_es_ctx == NULL) { + /* + * Search connection closed by the user while s was pending. + */ + TALLOC_FREE(s); + return; + } + + DLIST_REMOVE(mds_es_ctx->searches, s); + + ret = mds_es_search_recv(subreq); + TALLOC_FREE(subreq); + if (ret != 0) { + mds_es_reconnect_on_error(s); + return; + } + + if (slq == NULL) { + /* + * Closed by the user. Explicitly free "s" here because the + * talloc parent slq is already gone. + */ + TALLOC_FREE(s); + goto trigger; + } + + SLQ_DEBUG(10, slq, "search done"); + + if (s->total == 0 || s->from >= s->max) { + slq->state = SLQ_STATE_DONE; + goto trigger; + } + + if (slq->query_results->num_results >= SL_PAGESIZE) { + slq->state = SLQ_STATE_FULL; + goto trigger; + } + + /* + * Reschedule this query as there are more results waiting in the + * Elasticsearch server and the client result queue has room as + * well. But put it at the end of the list of active queries as a simple + * heuristic that should ensure all client queries are dispatched to the + * server. + */ + DLIST_ADD_END(mds_es_ctx->searches, s); + +trigger: + ok = mds_es_next_search_trigger(mds_es_ctx); + if (!ok) { + DBG_ERR("mds_es_next_search_trigger failed\n"); + } +} + +static void mds_es_search_http_send_done(struct tevent_req *subreq); +static void mds_es_search_http_read_done(struct tevent_req *subreq); + +struct mds_es_search_state { + struct tevent_context *ev; + struct sl_es_search *s; + struct tevent_queue_entry *qe; + struct http_request http_request; + struct http_request *http_response; +}; + +static int mds_es_search_pending_destructor(struct sl_es_search *s) +{ + /* + * s is a child of slq which may get freed when a user closes a + * query. To maintain the HTTP request/response sequence on the HTTP + * channel, we keep processing pending requests and free s when we + * receive the HTTP response for pending requests. + */ + DBG_DEBUG("Preserving pending search [%p]\n", s); + s->slq = NULL; + return -1; +} + +static void mds_es_search_set_pending(struct sl_es_search *s) +{ + DBG_DEBUG("Set pending [%p]\n", s); + SLQ_DEBUG(10, s->slq, "pending"); + + s->pending = true; + talloc_set_destructor(s, mds_es_search_pending_destructor); +} + +static void mds_es_search_unset_pending(struct sl_es_search *s) +{ + DBG_DEBUG("Unset pending [%p]\n", s); + if (s->slq != NULL) { + SLQ_DEBUG(10, s->slq, "unset pending"); + } + + s->pending = false; + talloc_set_destructor(s, search_destructor); +} + +static struct tevent_req *mds_es_search_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sl_es_search *s) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct mds_es_search_state *state = NULL; + const char *index = NULL; + char *elastic_query = NULL; + char *uri = NULL; + size_t elastic_query_len; + char *elastic_query_len_str = NULL; + char *hostname = NULL; + bool pretty = false; + + req = tevent_req_create(mem_ctx, &state, struct mds_es_search_state); + if (req == NULL) { + return NULL; + } + *state = (struct mds_es_search_state) { + .ev = ev, + .s = s, + }; + + if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(60, 0))) { + return tevent_req_post(req, s->ev); + } + + index = lp_parm_const_string(s->slq->mds_ctx->snum, + "elasticsearch", + "index", + "_all"); + if (tevent_req_nomem(index, req)) { + return tevent_req_post(req, ev); + } + + if (DEBUGLVL(10)) { + pretty = true; + } + + uri = talloc_asprintf(state, + "/%s/_search%s", + index, + pretty ? "?pretty" : ""); + if (tevent_req_nomem(uri, req)) { + return tevent_req_post(req, ev); + } + + elastic_query = talloc_asprintf(state, + MDSSVC_ELASTIC_QUERY_TEMPLATE, + s->from, + s->size, + MDSSVC_ELASTIC_SOURCES, + s->es_query); + if (tevent_req_nomem(elastic_query, req)) { + return tevent_req_post(req, ev); + } + DBG_DEBUG("Elastic query: '%s'\n", elastic_query); + + elastic_query_len = strlen(elastic_query); + + state->http_request = (struct http_request) { + .type = HTTP_REQ_POST, + .uri = uri, + .body = data_blob_const(elastic_query, elastic_query_len), + .major = '1', + .minor = '1', + }; + + elastic_query_len_str = talloc_asprintf(state, "%zu", elastic_query_len); + if (tevent_req_nomem(elastic_query_len_str, req)) { + return tevent_req_post(req, ev); + } + + hostname = get_myname(state); + if (tevent_req_nomem(hostname, req)) { + return tevent_req_post(req, ev); + } + + http_add_header(state, &state->http_request.headers, + "Content-Type", "application/json"); + http_add_header(state, &state->http_request.headers, + "Accept", "application/json"); + http_add_header(state, &state->http_request.headers, + "User-Agent", "Samba/mdssvc"); + http_add_header(state, &state->http_request.headers, + "Host", hostname); + http_add_header(state, &state->http_request.headers, + "Content-Length", elastic_query_len_str); + + subreq = http_send_request_send(state, + ev, + s->mds_es_ctx->http_conn, + &state->http_request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, mds_es_search_http_send_done, req); + return req; +} + +static void mds_es_search_http_send_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct mds_es_search_state *state = tevent_req_data( + req, struct mds_es_search_state); + NTSTATUS status; + + DBG_DEBUG("Sent out search [%p]\n", state->s); + + status = http_send_request_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_error(req, map_errno_from_nt_status(status)); + return; + } + + if (state->s->mds_es_ctx == NULL || state->s->slq == NULL) { + tevent_req_done(req); + return; + } + + subreq = http_read_response_send(state, + state->ev, + state->s->mds_es_ctx->http_conn, + SL_PAGESIZE * 8192); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, mds_es_search_http_read_done, req); +} + +static void mds_es_search_http_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct mds_es_search_state *state = tevent_req_data( + req, struct mds_es_search_state); + struct sl_es_search *s = state->s; + struct sl_query *slq = s->slq; + json_t *root = NULL; + json_t *matches = NULL; + json_t *match = NULL; + size_t i; + json_error_t error; + size_t hits; + NTSTATUS status; + int ret; + bool ok; + + DBG_DEBUG("Got response for search [%p]\n", s); + + status = http_read_response_recv(subreq, state, &state->http_response); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("HTTP response failed: %s\n", nt_errstr(status)); + tevent_req_error(req, map_errno_from_nt_status(status)); + return; + } + + if (slq == NULL || s->mds_es_ctx == NULL) { + tevent_req_done(req); + return; + } + + switch (state->http_response->response_code) { + case 200: + break; + default: + DBG_ERR("HTTP server response: %u\n", + state->http_response->response_code); + goto fail; + } + + DBG_DEBUG("JSON response:\n%s\n", + talloc_strndup(talloc_tos(), + (char *)state->http_response->body.data, + state->http_response->body.length)); + + root = json_loadb((char *)state->http_response->body.data, + state->http_response->body.length, + 0, + &error); + if (root == NULL) { + DBG_ERR("json_loadb failed\n"); + goto fail; + } + + if (s->total == 0) { + /* + * Get the total number of results the first time, format + * used by Elasticsearch 7.0 or newer + */ + ret = json_unpack(root, "{s: {s: {s: i}}}", + "hits", "total", "value", &s->total); + if (ret != 0) { + /* Format used before 7.0 */ + ret = json_unpack(root, "{s: {s: i}}", + "hits", "total", &s->total); + if (ret != 0) { + DBG_ERR("json_unpack failed\n"); + goto fail; + } + } + + DBG_DEBUG("Total: %zu\n", s->total); + + if (s->total == 0) { + json_decref(root); + tevent_req_done(req); + return; + } + } + + if (s->max == 0 || s->max > s->total) { + s->max = s->total; + } + + ret = json_unpack(root, "{s: {s:o}}", + "hits", "hits", &matches); + if (ret != 0 || matches == NULL) { + DBG_ERR("json_unpack hits failed\n"); + goto fail; + } + + hits = json_array_size(matches); + if (hits == 0) { + DBG_ERR("Hu?! No results?\n"); + goto fail; + } + DBG_DEBUG("Hits: %zu\n", hits); + + for (i = 0; i < hits && s->from + i < s->max; i++) { + const char *path = NULL; + + match = json_array_get(matches, i); + if (match == NULL) { + DBG_ERR("Hu?! No value for index %zu\n", i); + goto fail; + } + ret = json_unpack(match, + "{s: {s: {s: s}}}", + "_source", + "path", + "real", + &path); + if (ret != 0) { + DBG_ERR("Missing path.real in JSON result\n"); + goto fail; + } + + ok = mds_add_result(slq, path); + if (!ok) { + DBG_ERR("error adding result for path: %s\n", path); + goto fail; + } + } + json_decref(root); + + s->from += hits; + slq->state = SLQ_STATE_RESULTS; + tevent_req_done(req); + return; + +fail: + if (root != NULL) { + json_decref(root); + } + slq->state = SLQ_STATE_ERROR; + tevent_req_error(req, EINVAL); + return; +} + +static int mds_es_search_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static bool mds_es_search_cont(struct sl_query *slq) +{ + struct sl_es_search *s = talloc_get_type_abort( + slq->backend_private, struct sl_es_search); + + SLQ_DEBUG(10, slq, "continue"); + DLIST_ADD_END(s->mds_es_ctx->searches, s); + return mds_es_next_search_trigger(s->mds_es_ctx); +} + +struct mdssvc_backend mdsscv_backend_es = { + .init = mdssvc_es_init, + .shutdown = mdssvc_es_shutdown, + .connect = mds_es_connect, + .search_start = mds_es_search, + .search_cont = mds_es_search_cont, +}; diff --git a/source3/rpc_server/mdssvc/mdssvc_es.h b/source3/rpc_server/mdssvc/mdssvc_es.h new file mode 100644 index 0000000..19797fa --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_es.h @@ -0,0 +1,108 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / HTTP/ES/JSON backend + + Copyright (C) Ralph Boehme 2019 + + 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 _MDSSVC_ES_H_ +#define _MDSSVC_ES_H_ + +#include <jansson.h> + +/* + * Some global state + */ +struct mdssvc_es_ctx { + struct mdssvc_ctx *mdssvc_ctx; + struct cli_credentials *creds; + json_t *mappings; +}; + +/* + * Per mdssvc RPC bind state + */ +struct mds_es_ctx { + /* + * Pointer to higher level mds_ctx + */ + struct mds_ctx *mds_ctx; + + /* + * Pointer to our global context + */ + struct mdssvc_es_ctx *mdssvc_es_ctx; + + /* + * The HTTP connection handle to the ES server + */ + struct http_conn *http_conn; + + /* + * List of pending searches + */ + struct sl_es_search *searches; +}; + +/* Per search request */ +struct sl_es_search { + /* + * List pointers + */ + struct sl_es_search *prev, *next; + + /* + * Search is being executed. Only the list head can be pending. + */ + bool pending; + + /* + * Shorthand to our tevent context + */ + struct tevent_context *ev; + + /* + * Pointer to the RPC connection ctx the request is using + */ + struct mds_es_ctx *mds_es_ctx; + + /* + * The upper mdssvc.c level query context + */ + struct sl_query *slq; + + /* + * Maximum number of results we process and total number of + * results of a query. + */ + size_t total; + size_t max; + + /* + * For paging results + */ + size_t from; + size_t size; + + /* + * The translated Es query + */ + char *es_query; +}; + +extern struct mdssvc_backend mdsscv_backend_es; + +#endif /* _MDSSVC_ES_H_ */ diff --git a/source3/rpc_server/mdssvc/mdssvc_noindex.c b/source3/rpc_server/mdssvc/mdssvc_noindex.c new file mode 100644 index 0000000..ff466af --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_noindex.c @@ -0,0 +1,57 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / noindex backend + + Copyright (C) Ralph Boehme 2019 + + 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 "mdssvc.h" + +static bool mdssvc_noindex_init(struct mdssvc_ctx *mdssvc_ctx) +{ + return true; +} + +static bool mdssvc_noindex_shutdown(struct mdssvc_ctx *mdssvc_ctx) +{ + return true; +} + +static bool mds_noindex_connect(struct mds_ctx *mds_ctx) +{ + return true; +} + +static bool mds_noindex_search_start(struct sl_query *slq) +{ + slq->state = SLQ_STATE_DONE; + return true; +} + +static bool mds_noindex_search_cont(struct sl_query *slq) +{ + slq->state = SLQ_STATE_DONE; + return true; +} + +struct mdssvc_backend mdsscv_backend_noindex = { + .init = mdssvc_noindex_init, + .shutdown = mdssvc_noindex_shutdown, + .connect = mds_noindex_connect, + .search_start = mds_noindex_search_start, + .search_cont = mds_noindex_search_cont, +}; diff --git a/source3/rpc_server/mdssvc/mdssvc_noindex.h b/source3/rpc_server/mdssvc/mdssvc_noindex.h new file mode 100644 index 0000000..750ee44 --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_noindex.h @@ -0,0 +1,26 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / noindex backend + + Copyright (C) Ralph Boehme 2019 + + 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 _MDSSVC_NOINDEX_H_ +#define _MDSSVC_NOINDEX_H_ + +extern struct mdssvc_backend mdsscv_backend_noindex; + +#endif /* _MDSSVC_VOID_H_ */ diff --git a/source3/rpc_server/mdssvc/mdssvc_tracker.c b/source3/rpc_server/mdssvc/mdssvc_tracker.c new file mode 100644 index 0000000..54f391e --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_tracker.c @@ -0,0 +1,499 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Tracker backend + + Copyright (C) Ralph Boehme 2019 + + 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 "lib/util/time_basic.h" +#include "mdssvc.h" +#include "mdssvc_tracker.h" +#include "lib/tevent_glib_glue.h" +#include "rpc_server/mdssvc/sparql_parser.tab.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static struct mdssvc_tracker_ctx *mdssvc_tracker_ctx; + +/************************************************ + * Tracker async callbacks + ************************************************/ + +static void tracker_con_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + struct mds_tracker_ctx *ctx = NULL; + TrackerSparqlConnection *tracker_con = NULL; + GError *error = NULL; + + tracker_con = tracker_sparql_connection_get_finish(res, &error); + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + DBG_ERR("Tracker connection cancelled\n"); + g_error_free(error); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + ctx = talloc_get_type_abort(user_data, struct mds_tracker_ctx); + ctx->async_pending = false; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Could not connect to Tracker: %s\n", error->message); + g_error_free(error); + return; + } + + ctx->tracker_con = tracker_con; + + DBG_DEBUG("connected to Tracker\n"); +} + +static void tracker_cursor_cb(GObject *object, + GAsyncResult *res, + gpointer user_data); + +static void tracker_query_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + struct sl_tracker_query *tq = NULL; + struct sl_query *slq = NULL; + TrackerSparqlConnection *conn = NULL; + TrackerSparqlCursor *cursor = NULL; + GError *error = NULL; + + conn = TRACKER_SPARQL_CONNECTION(object); + + cursor = tracker_sparql_connection_query_finish(conn, res, &error); + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + DBG_ERR("Tracker query cancelled\n"); + if (cursor != NULL) { + g_object_unref(cursor); + } + g_error_free(error); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + tq = talloc_get_type_abort(user_data, struct sl_tracker_query); + tq->async_pending = false; + slq = tq->slq; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Tracker query error: %s\n", error->message); + g_error_free(error); + slq->state = SLQ_STATE_ERROR; + return; + } + + tq->cursor = cursor; + slq->state = SLQ_STATE_RESULTS; + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; +} + +static char *tracker_to_unix_path(TALLOC_CTX *mem_ctx, const char *uri) +{ + GFile *f = NULL; + char *path = NULL; + char *talloc_path = NULL; + + f = g_file_new_for_uri(uri); + if (f == NULL) { + return NULL; + } + + path = g_file_get_path(f); + g_object_unref(f); + + if (path == NULL) { + return NULL; + } + + talloc_path = talloc_strdup(mem_ctx, path); + g_free(path); + if (talloc_path == NULL) { + return NULL; + } + + return talloc_path; +} + +static void tracker_cursor_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + TrackerSparqlCursor *cursor = NULL; + struct sl_tracker_query *tq = NULL; + struct sl_query *slq = NULL; + const gchar *uri = NULL; + GError *error = NULL; + char *path = NULL; + gboolean more_results; + bool ok; + + cursor = TRACKER_SPARQL_CURSOR(object); + more_results = tracker_sparql_cursor_next_finish(cursor, + res, + &error); + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + g_object_unref(cursor); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + tq = talloc_get_type_abort(user_data, struct sl_tracker_query); + tq->async_pending = false; + slq = tq->slq; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Tracker cursor: %s\n", error->message); + g_error_free(error); + slq->state = SLQ_STATE_ERROR; + return; + } + + SLQ_DEBUG(10, slq, "results"); + + if (!more_results) { + slq->state = SLQ_STATE_DONE; + + g_object_unref(tq->cursor); + tq->cursor = NULL; + + g_object_unref(tq->gcancellable); + tq->gcancellable = NULL; + return; + } + + uri = tracker_sparql_cursor_get_string(tq->cursor, 0, NULL); + if (uri == NULL) { + DBG_ERR("error fetching Tracker URI\n"); + slq->state = SLQ_STATE_ERROR; + return; + } + + path = tracker_to_unix_path(slq->query_results, uri); + if (path == NULL) { + DBG_ERR("error converting Tracker URI to path: %s\n", uri); + slq->state = SLQ_STATE_ERROR; + return; + } + + ok = mds_add_result(slq, path); + if (!ok) { + DBG_ERR("error adding result for path: %s\n", uri); + slq->state = SLQ_STATE_ERROR; + return; + } + + if (slq->query_results->num_results >= MAX_SL_RESULTS) { + slq->state = SLQ_STATE_FULL; + SLQ_DEBUG(10, slq, "full"); + return; + } + + slq->state = SLQ_STATE_RESULTS; + SLQ_DEBUG(10, slq, "cursor next"); + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; +} + +/* + * This gets called once, even if the backend is not configured by the user + */ +static bool mdssvc_tracker_init(struct mdssvc_ctx *mdssvc_ctx) +{ + if (mdssvc_tracker_ctx != NULL) { + return true; + } + +#if (GLIB_MAJOR_VERSION < 3) && (GLIB_MINOR_VERSION < 36) + g_type_init(); +#endif + + mdssvc_tracker_ctx = talloc_zero(mdssvc_ctx, struct mdssvc_tracker_ctx); + if (mdssvc_tracker_ctx == NULL) { + return false; + } + mdssvc_tracker_ctx->mdssvc_ctx = mdssvc_ctx; + + return true; +} + +/* + * This gets called per mdscmd_open / tcon. This runs initialisation code that + * should only run if the tracker backend is actually used. + */ +static bool mdssvc_tracker_prepare(void) +{ + if (mdssvc_tracker_ctx->gmain_ctx != NULL) { + /* + * Assuming everything is setup if gmain_ctx is. + */ + return true; + } + + mdssvc_tracker_ctx->gmain_ctx = g_main_context_new(); + if (mdssvc_tracker_ctx->gmain_ctx == NULL) { + DBG_ERR("error from g_main_context_new\n"); + return false; + } + + mdssvc_tracker_ctx->glue = samba_tevent_glib_glue_create( + mdssvc_tracker_ctx, + mdssvc_tracker_ctx->mdssvc_ctx->ev_ctx, + mdssvc_tracker_ctx->gmain_ctx); + if (mdssvc_tracker_ctx->glue == NULL) { + DBG_ERR("samba_tevent_glib_glue_create failed\n"); + g_object_unref(mdssvc_tracker_ctx->gmain_ctx); + mdssvc_tracker_ctx->gmain_ctx = NULL; + return false; + } + + return true; +} + +static bool mdssvc_tracker_shutdown(struct mdssvc_ctx *mdssvc_ctx) +{ + if (mdssvc_tracker_ctx == NULL) { + return true; + } + + if (mdssvc_tracker_ctx->gmain_ctx == NULL) { + return true; + } + + samba_tevent_glib_glue_quit(mdssvc_tracker_ctx->glue); + TALLOC_FREE(mdssvc_tracker_ctx->glue); + + g_object_unref(mdssvc_tracker_ctx->gmain_ctx); + mdssvc_tracker_ctx->gmain_ctx = NULL; + return true; +} + +static int mds_tracker_ctx_destructor(struct mds_tracker_ctx *ctx) +{ + /* + * Don't g_object_unref() the connection if there's an async request + * pending, it's used in the async callback and will be unreferenced + * there. + */ + if (ctx->async_pending) { + g_cancellable_cancel(ctx->gcancellable); + ctx->gcancellable = NULL; + return 0; + } + + if (ctx->tracker_con == NULL) { + return 0; + } + g_object_unref(ctx->tracker_con); + ctx->tracker_con = NULL; + + return 0; +} + +static bool mds_tracker_connect(struct mds_ctx *mds_ctx) +{ + struct mds_tracker_ctx *ctx = NULL; + bool ok; + + ok = mdssvc_tracker_prepare(); + if (!ok) { + return false; + } + + ctx = talloc_zero(mds_ctx, struct mds_tracker_ctx); + if (ctx == NULL) { + return false; + } + talloc_set_destructor(ctx, mds_tracker_ctx_destructor); + + ctx->mds_ctx = mds_ctx; + + ctx->gcancellable = g_cancellable_new(); + if (ctx->gcancellable == NULL) { + DBG_ERR("error from g_cancellable_new\n"); + TALLOC_FREE(ctx); + return false; + } + + tracker_sparql_connection_get_async(ctx->gcancellable, + tracker_con_cb, + ctx); + ctx->async_pending = true; + + mds_ctx->backend_private = ctx; + + return true; +} + +static int tq_destructor(struct sl_tracker_query *tq) +{ + /* + * Don't g_object_unref() the cursor if there's an async request + * pending, it's used in the async callback and will be unreferenced + * there. + */ + if (tq->async_pending) { + g_cancellable_cancel(tq->gcancellable); + tq->gcancellable = NULL; + return 0; + } + + if (tq->cursor == NULL) { + return 0; + } + g_object_unref(tq->cursor); + tq->cursor = NULL; + return 0; +} + +static bool mds_tracker_search_start(struct sl_query *slq) +{ + struct mds_tracker_ctx *tmds_ctx = talloc_get_type_abort( + slq->mds_ctx->backend_private, struct mds_tracker_ctx); + struct sl_tracker_query *tq = NULL; + char *escaped_scope = NULL; + bool ok; + + if (tmds_ctx->tracker_con == NULL) { + DBG_ERR("no connection to Tracker\n"); + return false; + } + + tq = talloc_zero(slq, struct sl_tracker_query); + if (tq == NULL) { + return false; + } + tq->slq = slq; + talloc_set_destructor(tq, tq_destructor); + + tq->gcancellable = g_cancellable_new(); + if (tq->gcancellable == NULL) { + DBG_ERR("g_cancellable_new() failed\n"); + goto error; + } + + escaped_scope = g_uri_escape_string( + slq->path_scope, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, + TRUE); + if (escaped_scope == NULL) { + goto error; + } + + tq->path_scope = talloc_strdup(tq, escaped_scope); + g_free(escaped_scope); + escaped_scope = NULL; + if (tq->path_scope == NULL) { + goto error; + } + + slq->backend_private = tq; + + ok = map_spotlight_to_sparql_query(slq); + if (!ok) { + /* + * Two cases: + * + * 1) the query string is "false", the parser returns + * an error for that. We're supposed to return -1 + * here. + * + * 2) the parsing really failed, in that case we're + * probably supposed to return -1 too, this needs + * verification though + */ + goto error; + } + + DBG_DEBUG("SPARQL query: \"%s\"\n", tq->sparql_query); + + tracker_sparql_connection_query_async(tmds_ctx->tracker_con, + tq->sparql_query, + tq->gcancellable, + tracker_query_cb, + tq); + tq->async_pending = true; + + slq->state = SLQ_STATE_RUNNING; + return true; +error: + g_object_unref(tq->gcancellable); + TALLOC_FREE(tq); + slq->backend_private = NULL; + return false; +} + +static bool mds_tracker_search_cont(struct sl_query *slq) +{ + struct sl_tracker_query *tq = talloc_get_type_abort( + slq->backend_private, struct sl_tracker_query); + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; + + return true; +} + +struct mdssvc_backend mdsscv_backend_tracker = { + .init = mdssvc_tracker_init, + .shutdown = mdssvc_tracker_shutdown, + .connect = mds_tracker_connect, + .search_start = mds_tracker_search_start, + .search_cont = mds_tracker_search_cont, +}; diff --git a/source3/rpc_server/mdssvc/mdssvc_tracker.h b/source3/rpc_server/mdssvc/mdssvc_tracker.h new file mode 100644 index 0000000..54a4a33 --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_tracker.h @@ -0,0 +1,62 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Tracker backend + + Copyright (C) Ralph Boehme 2019 + + 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/>. +*/ + +/* allow building with --enable-developer */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#include <gio/gio.h> +#include <tracker-sparql.h> +#pragma GCC diagnostic pop + +/* Global */ +struct mdssvc_tracker_ctx { + struct mdssvc_ctx *mdssvc_ctx; + GMainContext *gmain_ctx; + struct tevent_glib_glue *glue; +}; + +/* Per tree connect state */ +struct mds_tracker_ctx { + struct mds_ctx *mds_ctx; + GCancellable *gcancellable; + bool async_pending; + TrackerSparqlConnection *tracker_con; +}; + +/* Per query */ +struct sl_tracker_query { + struct sl_query *slq; + const char *path_scope; + const char *sparql_query; + + /* + * Notes on the lifetime of cursor: we hold a reference on the object + * and have to call g_object_unref(cursor) at the right place. This is + * either done in the talloc destructor on a struct sl_tracker_query + * talloc object when there are no tracker glib async requests + * running. Or in the glib callback after cancelling the glib async + * request. + */ + TrackerSparqlCursor *cursor; + GCancellable *gcancellable; + bool async_pending; +}; + +extern struct mdssvc_backend mdsscv_backend_tracker; diff --git a/source3/rpc_server/mdssvc/sparql_lexer.l b/source3/rpc_server/mdssvc/sparql_lexer.l new file mode 100644 index 0000000..b638350 --- /dev/null +++ b/source3/rpc_server/mdssvc/sparql_lexer.l @@ -0,0 +1,67 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 "rpc_server/mdssvc/sparql_parser.tab.h" + +#define YY_NO_INPUT +%} + +%option nounput noyyalloc noyyrealloc prefix="mdsyy" + +ASC [a-zA-Z0-9_\*\:\-\.] +U [\x80-\xbf] +U2 [\xc2-\xdf] +U3 [\xe0-\xef] +U4 [\xf0-\xf4] + +UANY {ASC}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} +UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} + +%% +InRange return FUNC_INRANGE; +\$time\.iso return DATE_ISO; +false {mdsyylval.bval = false; return BOOL;} +true {mdsyylval.bval = true; return BOOL;} +\" return QUOTE; +\( return OBRACE; +\) return CBRACE; +\&\& return AND; +\|\| return OR; +\=\= return EQUAL; +\!\= return UNEQUAL; +\= return EQUAL; +\< return LT; +\> return GT; +\, return COMMA; +{UANY}+ {mdsyylval.sval = talloc_strdup(talloc_tos(), mdsyytext); return WORD;} +[ \t\n] /* ignore */ +%% + +void *yyalloc(yy_size_t bytes) +{ + return SMB_MALLOC(bytes); +} + +void *yyrealloc(void *ptr, yy_size_t bytes) +{ + return SMB_REALLOC(ptr, bytes); +} diff --git a/source3/rpc_server/mdssvc/sparql_mapping.c b/source3/rpc_server/mdssvc/sparql_mapping.c new file mode 100644 index 0000000..c71c7a5 --- /dev/null +++ b/source3/rpc_server/mdssvc/sparql_mapping.c @@ -0,0 +1,378 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 "replace.h" +#include "sparql_mapping.h" + +const struct sl_attr_map *sl_attr_map_by_spotlight(const char *sl_attr) +{ + static const struct sl_attr_map spotlight_sparql_attr_map[] = { + { + .spotlight_attr = "*", + .type = ssmt_fts, + .sparql_attr = "fts:match", + }, + + /* Filesystem metadata */ + { + .spotlight_attr = "kMDItemFSLabel", + .type = ssmt_num, + .sparql_attr = NULL, + }, + { + .spotlight_attr = "kMDItemDisplayName", + .type = ssmt_str, + .sparql_attr = "nfo:fileName", + }, + { + .spotlight_attr = "kMDItemFSName", + .type = ssmt_str, + .sparql_attr = "nfo:fileName", + }, + { + .spotlight_attr = "kMDItemFSContentChangeDate", + .type = ssmt_date, + .sparql_attr = "nfo:fileLastModified", + }, + { + .spotlight_attr = "kMDItemLastUsedDate", + .type = ssmt_date, + .sparql_attr = "nfo:fileLastAccessed", + }, + + /* Common metadata */ + { + .spotlight_attr = "kMDItemTextContent", + .type = ssmt_fts, + .sparql_attr = "fts:match", + }, + { + .spotlight_attr = "kMDItemContentCreationDate", + .type = ssmt_date, + .sparql_attr = "nie:contentCreated", + }, + { + .spotlight_attr = "kMDItemContentModificationDate", + .type = ssmt_date, + .sparql_attr = "nfo:fileLastModified", + }, + { + .spotlight_attr = "kMDItemAttributeChangeDate", + .type = ssmt_date, + .sparql_attr = "nfo:fileLastModified", + }, + { + .spotlight_attr = "kMDItemAuthors", + .type = ssmt_str, + .sparql_attr = "dc:creator", + }, + { + .spotlight_attr = "kMDItemCopyright", + .type = ssmt_str, + .sparql_attr = "nie:copyright", + }, + { + .spotlight_attr = "kMDItemCountry", + .type = ssmt_str, + .sparql_attr = "nco:country", + }, + { + .spotlight_attr = "kMDItemCreator", + .type = ssmt_str, + .sparql_attr = "dc:creator", + }, + { + .spotlight_attr = "kMDItemDurationSeconds", + .type = ssmt_num, + .sparql_attr = "nfo:duration", + }, + { + .spotlight_attr = "kMDItemNumberOfPages", + .type = ssmt_num, + .sparql_attr = "nfo:pageCount", + }, + { + .spotlight_attr = "kMDItemTitle", + .type = ssmt_str, + .sparql_attr = "nie:title", + }, + { + .spotlight_attr = "kMDItemCity", + .type = ssmt_str, + .sparql_attr = "nco:locality", + }, + { + .spotlight_attr = "kMDItemCoverage", + .type = ssmt_str, + .sparql_attr = "nco:locality", + }, + { + .spotlight_attr = "_kMDItemGroupId", + .type = ssmt_type, + .sparql_attr = NULL, + }, + { + .spotlight_attr = "kMDItemContentTypeTree", + .type = ssmt_type, + .sparql_attr = NULL, + }, + { + .spotlight_attr = "kMDItemContentType", + .type = ssmt_type, + .sparql_attr = NULL, + }, + + /* Image metadata */ + { + .spotlight_attr = "kMDItemPixelWidth", + .type = ssmt_num, + .sparql_attr = "nfo:width", + }, + { + .spotlight_attr = "kMDItemPixelHeight", + .type = ssmt_num, + .sparql_attr = "nfo:height", + }, + { + .spotlight_attr = "kMDItemColorSpace", + .type = ssmt_str, + .sparql_attr = "nexif:colorSpace", + }, + { + .spotlight_attr = "kMDItemBitsPerSample", + .type = ssmt_num, + .sparql_attr = "nfo:colorDepth", + }, + { + .spotlight_attr = "kMDItemFocalLength", + .type = ssmt_num, + .sparql_attr = "nmm:focalLength", + }, + { + .spotlight_attr = "kMDItemISOSpeed", + .type = ssmt_num, + .sparql_attr = "nmm:isoSpeed", + }, + { + .spotlight_attr = "kMDItemOrientation", + .type = ssmt_bool, + .sparql_attr = "nfo:orientation", + }, + { + .spotlight_attr = "kMDItemResolutionWidthDPI", + .type = ssmt_num, + .sparql_attr = "nfo:horizontalResolution", + }, + { + .spotlight_attr = "kMDItemResolutionHeightDPI", + .type = ssmt_num, + .sparql_attr = "nfo:verticalResolution", + }, + { + .spotlight_attr = "kMDItemExposureTimeSeconds", + .type = ssmt_num, + .sparql_attr = "nmm:exposureTime", + }, + + /* Audio metadata */ + { + .spotlight_attr = "kMDItemComposer", + .type = ssmt_str, + .sparql_attr = "nmm:composer", + }, + { + .spotlight_attr = "kMDItemMusicalGenre", + .type = ssmt_str, + .sparql_attr = "nfo:genre", + }, + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(spotlight_sparql_attr_map); i++) { + const struct sl_attr_map *m = &spotlight_sparql_attr_map[i]; + int cmp; + + cmp = strcmp(m->spotlight_attr, sl_attr); + if (cmp == 0) { + return m; + } + } + + return NULL; +} + +const struct sl_type_map *sl_type_map_by_spotlight(const char *sl_type) +{ + static const struct sl_type_map spotlight_sparql_type_map[] = { + { + .spotlight_type = "1", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#Email", + }, + { + .spotlight_type = "2", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact", + }, + { + .spotlight_type = "3", + .type = kMDTypeMapNotSup, + .sparql_type = NULL, /*PrefPane*/ + }, + { + .spotlight_type = "4", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Font", + }, + { + .spotlight_type = "5", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Bookmark", + }, + { + .spotlight_type = "6", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact", + }, + { + .spotlight_type = "7", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Video", + }, + { + .spotlight_type = "8", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Executable", + }, + { + .spotlight_type = "9", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Folder", + }, + { + .spotlight_type = "10", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio", + }, + { + .spotlight_type = "11", + .type = kMDTypeMapMime, + .sparql_type = "application/pdf", + }, + { + .spotlight_type = "12", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Presentation", + }, + { + .spotlight_type = "13", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image", + }, + { + .spotlight_type = "public.jpeg", + .type = kMDTypeMapMime, + .sparql_type = "image/jpeg", + }, + { + .spotlight_type = "public.tiff", + .type = kMDTypeMapMime, + .sparql_type = "image/tiff", + }, + { + .spotlight_type = "com.compuserve.gif", + .type = kMDTypeMapMime, + .sparql_type = "image/gif", + }, + { + .spotlight_type = "public.png", + .type = kMDTypeMapMime, + .sparql_type = "image/png", + }, + { + .spotlight_type = "com.microsoft.bmp", + .type = kMDTypeMapMime, + .sparql_type = "image/bmp", + }, + { + .spotlight_type = "public.content", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Document", + }, + { + .spotlight_type = "public.mp3", + .type = kMDTypeMapMime, + .sparql_type = "audio/mpeg", + }, + { + .spotlight_type = "public.mpeg-4-audio", + .type = kMDTypeMapMime, + .sparql_type = "audio/x-aac", + }, + { + .spotlight_type = "com.apple.application", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Software", + }, + { + .spotlight_type = "public.text", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#TextDocument", + }, + { + .spotlight_type = "public.plain-text", + .type = kMDTypeMapMime, + .sparql_type = "text/plain", + }, + { + .spotlight_type = "public.rtf", + .type = kMDTypeMapMime, + .sparql_type = "text/rtf", + }, + { + .spotlight_type = "public.html", + .type = kMDTypeMapMime, + .sparql_type = "text/html", + }, + { + .spotlight_type = "public.xml", + .type = kMDTypeMapMime, + .sparql_type = "text/xml", + }, + { + .spotlight_type = "public.source-code", + .type = kMDTypeMapRDF, + .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#SourceCode", + }, + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(spotlight_sparql_type_map); i++) { + const struct sl_type_map *m = &spotlight_sparql_type_map[i]; + int cmp; + + cmp = strcmp(m->spotlight_type, sl_type); + if (cmp == 0) { + return m; + } + } + + return NULL; +} diff --git a/source3/rpc_server/mdssvc/sparql_mapping.h b/source3/rpc_server/mdssvc/sparql_mapping.h new file mode 100644 index 0000000..14fab67 --- /dev/null +++ b/source3/rpc_server/mdssvc/sparql_mapping.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2012 Ralph Boehme + + 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 2 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. +*/ + +#ifndef SPOTLIGHT_SPARQL_MAP_H +#define SPOTLIGHT_SPARQL_MAP_H + +enum ssm_type { + ssmt_bool, /* a boolean value that doesn't requires a SPARQL FILTER */ + ssmt_num, /* a numeric value that requires a SPARQL FILTER */ + ssmt_str, /* a string value that requires a SPARQL FILTER */ + ssmt_fts, /* a string value that will be queried with SPARQL 'fts:match' */ + ssmt_date, /* date values are handled in a special map function map_daterange() */ + ssmt_type /* kMDItemContentType, requires special mapping */ +}; + +struct sl_attr_map { + const char *spotlight_attr; + enum ssm_type type; + const char *sparql_attr; +}; + +enum kMDTypeMap { + kMDTypeMapNotSup, /* not supported */ + kMDTypeMapRDF, /* query with rdf:type */ + kMDTypeMapMime /* query with nie:mimeType */ +}; + +struct sl_type_map { + /* + * MD query value of attributes '_kMDItemGroupId' and + * 'kMDItemContentTypeTree + */ + const char *spotlight_type; + + /* + * Whether SPARQL query must search attribute rdf:type or + * nie:mime_Type + */ + enum kMDTypeMap type; + + /* the SPARQL query match string */ + const char *sparql_type; +}; + +const struct sl_attr_map *sl_attr_map_by_spotlight(const char *sl_attr); +const struct sl_type_map *sl_type_map_by_spotlight(const char *sl_type); +#endif diff --git a/source3/rpc_server/mdssvc/sparql_parser.y b/source3/rpc_server/mdssvc/sparql_parser.y new file mode 100644 index 0000000..68d4d87 --- /dev/null +++ b/source3/rpc_server/mdssvc/sparql_parser.y @@ -0,0 +1,483 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines + + Copyright (C) Ralph Boehme 2012-2014 + + 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 "rpc_server/mdssvc/mdssvc.h" + #include "rpc_server/mdssvc/mdssvc_tracker.h" + #include "rpc_server/mdssvc/sparql_parser.tab.h" + #include "rpc_server/mdssvc/sparql_mapping.h" + + #define YYMALLOC SMB_MALLOC + #define YYREALLOC SMB_REALLOC + + struct yy_buffer_state; + typedef struct yy_buffer_state *YY_BUFFER_STATE; + extern int mdsyylex (void); + extern void mdsyyerror (char const *); + extern void *mdsyyterminate(void); + extern YY_BUFFER_STATE mdsyy_scan_string( const char *str); + extern void mdsyy_delete_buffer ( YY_BUFFER_STATE buffer ); + + /* forward declarations */ + static const char *map_expr(const char *attr, char op, const char *val); + static const char *map_daterange(const char *dateattr, + time_t date1, time_t date2); + static time_t isodate2unix(const char *s); + + /* global vars, eg needed by the lexer */ + struct sparql_parser_state { + TALLOC_CTX *frame; + YY_BUFFER_STATE s; + char var; + const char *result; + } *global_sparql_parser_state; +%} + +%code provides { + #include <stdbool.h> + #include "rpc_server/mdssvc/mdssvc.h" + #define SPRAW_TIME_OFFSET 978307200 + extern int mdsyywrap(void); + extern bool map_spotlight_to_sparql_query(struct sl_query *slq); +} + +%union { + int ival; + const char *sval; + bool bval; + time_t tval; +} + +%name-prefix "mdsyy" +%expect 5 +%error-verbose + +%type <sval> match expr line function +%type <tval> date + +%token <sval> WORD +%token <bval> BOOL +%token FUNC_INRANGE +%token DATE_ISO +%token OBRACE CBRACE EQUAL UNEQUAL GT LT COMMA QUOTE +%left AND +%left OR +%% + +input: +/* empty */ +| input line +; + +line: +expr { + global_sparql_parser_state->result = $1; +} +; + +expr: +BOOL { + /* + * We can't properly handle these in expressions, fortunately this + * is probably only ever used by OS X as sole element in an + * expression ie "False" (when Finder window selected our share + * but no search string entered yet). Packet traces showed that OS + * X Spotlight server then returns a failure (ie -1) which is what + * we do here too by calling YYABORT. + */ + YYABORT; +} +/* + * We have "match OR match" and "expr OR expr", because the former is + * supposed to catch and coalesque expressions of the form + * + * MDSattribute1="hello"||MDSattribute2="hello" + * + * into a single SPARQL expression for the case where both + * MDSattribute1 and MDSattribute2 map to the same SPARQL attribute, + * which is eg the case for "*" and "kMDItemTextContent" which both + * map to SPARQL "fts:match". + */ + +| match OR match { + if (strcmp($1, $3) != 0) { + $$ = talloc_asprintf(talloc_tos(), "{ %s } UNION { %s }", $1, $3); + } else { + $$ = talloc_asprintf(talloc_tos(), "%s", $1); + } +} +| match { + $$ = $1; +} +| function { + $$ = $1; +} +| OBRACE expr CBRACE { + $$ = talloc_asprintf(talloc_tos(), "%s", $2); +} +| expr AND expr { + $$ = talloc_asprintf(talloc_tos(), "%s . %s", $1, $3); +} +| expr OR expr { + if (strcmp($1, $3) != 0) { + $$ = talloc_asprintf(talloc_tos(), "{ %s } UNION { %s }", $1, $3); + } else { + $$ = talloc_asprintf(talloc_tos(), "%s", $1); + } +} +; + +match: +WORD EQUAL QUOTE WORD QUOTE { + $$ = map_expr($1, '=', $4); + if ($$ == NULL) YYABORT; +} +| WORD UNEQUAL QUOTE WORD QUOTE { + $$ = map_expr($1, '!', $4); + if ($$ == NULL) YYABORT; +} +| WORD LT QUOTE WORD QUOTE { + $$ = map_expr($1, '<', $4); + if ($$ == NULL) YYABORT; +} +| WORD GT QUOTE WORD QUOTE { + $$ = map_expr($1, '>', $4); + if ($$ == NULL) YYABORT; +} +| WORD EQUAL QUOTE WORD QUOTE WORD { + $$ = map_expr($1, '=', $4); + if ($$ == NULL) YYABORT; +} +| WORD UNEQUAL QUOTE WORD QUOTE WORD { + $$ = map_expr($1, '!', $4); + if ($$ == NULL) YYABORT; +} +| WORD LT QUOTE WORD QUOTE WORD { + $$ = map_expr($1, '<', $4); + if ($$ == NULL) YYABORT; +} +| WORD GT QUOTE WORD QUOTE WORD { + $$ = map_expr($1, '>', $4); + if ($$ == NULL) YYABORT; +} +; + +function: +FUNC_INRANGE OBRACE WORD COMMA date COMMA date CBRACE { + $$ = map_daterange($3, $5, $7); + if ($$ == NULL) YYABORT; +} +; + +date: +DATE_ISO OBRACE WORD CBRACE {$$ = isodate2unix($3);} +| WORD {$$ = atoi($1) + SPRAW_TIME_OFFSET;} +; + +%% + +static time_t isodate2unix(const char *s) +{ + struct tm tm = {}; + const char *p; + + p = strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm); + if (p == NULL) { + return (time_t)-1; + } + return mktime(&tm); +} + +static const char *map_daterange(const char *dateattr, + time_t date1, time_t date2) +{ + struct sparql_parser_state *s = global_sparql_parser_state; + int result = 0; + char *sparql = NULL; + const struct sl_attr_map *p; + struct tm *tmp; + char buf1[64], buf2[64]; + + if (s->var == 'z') { + return NULL; + } + + tmp = localtime(&date1); + if (tmp == NULL) { + return NULL; + } + result = strftime(buf1, sizeof(buf1), "%Y-%m-%dT%H:%M:%SZ", tmp); + if (result == 0) { + return NULL; + } + + tmp = localtime(&date2); + if (tmp == NULL) { + return NULL; + } + result = strftime(buf2, sizeof(buf2), "%Y-%m-%dT%H:%M:%SZ", tmp); + if (result == 0) { + return NULL; + } + + p = sl_attr_map_by_spotlight(dateattr); + if (p == NULL) { + return NULL; + } + + sparql = talloc_asprintf(talloc_tos(), + "?obj %s ?%c FILTER (?%c > '%s' && ?%c < '%s')", + p->sparql_attr, + s->var, + s->var, + buf1, + s->var, + buf2); + if (sparql == NULL) { + return NULL; + } + + s->var++; + return sparql; +} + +static char *map_type_search(const char *attr, char op, const char *val) +{ + char *result = NULL; + const char *sparqlAttr; + const struct sl_type_map *p; + + p = sl_type_map_by_spotlight(val); + if (p == NULL) { + return NULL; + } + + switch (p->type) { + case kMDTypeMapRDF: + sparqlAttr = "rdf:type"; + break; + case kMDTypeMapMime: + sparqlAttr = "nie:mimeType"; + break; + default: + return NULL; + } + + result = talloc_asprintf(talloc_tos(), "?obj %s '%s'", + sparqlAttr, + p->sparql_type); + if (result == NULL) { + return NULL; + } + + return result; +} + +static const char *map_expr(const char *attr, char op, const char *val) +{ + struct sparql_parser_state *s = global_sparql_parser_state; + int result = 0; + char *sparql = NULL; + const struct sl_attr_map *p; + time_t t; + struct tm *tmp; + char buf1[64]; + char *q; + const char *start; + + if (s->var == 'z') { + return NULL; + } + + p = sl_attr_map_by_spotlight(attr); + if (p == NULL) { + return NULL; + } + + if ((p->type != ssmt_type) && (p->sparql_attr == NULL)) { + yyerror("unsupported Spotlight attribute"); + return NULL; + } + + switch (p->type) { + case ssmt_bool: + sparql = talloc_asprintf(talloc_tos(), "?obj %s '%s'", + p->sparql_attr, val); + if (sparql == NULL) { + return NULL; + } + break; + + case ssmt_num: + sparql = talloc_asprintf(talloc_tos(), + "?obj %s ?%c FILTER(?%c %c%c '%s')", + p->sparql_attr, + s->var, + s->var, + op, + /* append '=' to '!' */ + op == '!' ? '=' : ' ', + val); + if (sparql == NULL) { + return NULL; + } + s->var++; + break; + + case ssmt_str: + q = talloc_strdup(talloc_tos(), ""); + if (q == NULL) { + return NULL; + } + start = val; + while (*val) { + if (*val != '*') { + val++; + continue; + } + if (val > start) { + q = talloc_strndup_append(q, start, val - start); + if (q == NULL) { + return NULL; + } + } + q = talloc_strdup_append(q, ".*"); + if (q == NULL) { + return NULL; + } + val++; + start = val; + } + if (val > start) { + q = talloc_strndup_append(q, start, val - start); + if (q == NULL) { + return NULL; + } + } + sparql = talloc_asprintf(talloc_tos(), + "?obj %s ?%c " + "FILTER(regex(?%c, '^%s$', 'i'))", + p->sparql_attr, + s->var, + s->var, + q); + TALLOC_FREE(q); + if (sparql == NULL) { + return NULL; + } + s->var++; + break; + + case ssmt_fts: + sparql = talloc_asprintf(talloc_tos(), "?obj %s '%s'", + p->sparql_attr, val); + if (sparql == NULL) { + return NULL; + } + break; + + case ssmt_date: + t = atoi(val) + SPRAW_TIME_OFFSET; + tmp = localtime(&t); + if (tmp == NULL) { + return NULL; + } + result = strftime(buf1, sizeof(buf1), + "%Y-%m-%dT%H:%M:%SZ", tmp); + if (result == 0) { + return NULL; + } + sparql = talloc_asprintf(talloc_tos(), + "?obj %s ?%c FILTER(?%c %c '%s')", + p->sparql_attr, + s->var, + s->var, + op, + buf1); + if (sparql == NULL) { + return NULL; + } + s->var++; + break; + + case ssmt_type: + sparql = map_type_search(attr, op, val); + if (sparql == NULL) { + return NULL; + } + break; + + default: + return NULL; + } + + return sparql; +} + +void mdsyyerror(const char *str) +{ + DEBUG(1, ("mdsyyerror: %s\n", str)); +} + +int mdsyywrap(void) +{ + return 1; +} + +/** + * Map a Spotlight RAW query string to a SPARQL query string + **/ +bool map_spotlight_to_sparql_query(struct sl_query *slq) +{ + struct sl_tracker_query *tq = talloc_get_type_abort( + slq->backend_private, struct sl_tracker_query); + struct sparql_parser_state s = { + .frame = talloc_stackframe(), + .var = 'a', + }; + int result; + + s.s = mdsyy_scan_string(slq->query_string); + if (s.s == NULL) { + TALLOC_FREE(s.frame); + return false; + } + global_sparql_parser_state = &s; + result = mdsyyparse(); + global_sparql_parser_state = NULL; + mdsyy_delete_buffer(s.s); + + if (result != 0) { + TALLOC_FREE(s.frame); + return false; + } + + tq->sparql_query = talloc_asprintf(slq, + "SELECT ?url WHERE { %s . ?obj nie:url ?url . " + "FILTER(tracker:uri-is-descendant('file://%s/', ?url)) }", + s.result, tq->path_scope); + TALLOC_FREE(s.frame); + if (tq->sparql_query == NULL) { + return false; + } + + return true; +} diff --git a/source3/rpc_server/mdssvc/sparql_parser_test.c b/source3/rpc_server/mdssvc/sparql_parser_test.c new file mode 100644 index 0000000..0a0f625 --- /dev/null +++ b/source3/rpc_server/mdssvc/sparql_parser_test.c @@ -0,0 +1,47 @@ +#include "includes.h" +#include "mdssvc.h" +#include "rpc_server/mdssvc/sparql_parser.tab.h" +#include "rpc_server/mdssvc/mdssvc_tracker.h" + +/* + * Examples: + * + * $ ./spotlight2sparql '_kMDItemGroupId=="11"' + * ... + * $ ./spotlight2sparql '*=="test*"cwd||kMDItemTextContent=="test*"cwd' + * ... + */ + +int main(int argc, char **argv) +{ + struct sl_tracker_query *tq = NULL; + bool ok; + struct sl_query *slq; + + if (argc != 2) { + printf("usage: %s QUERY\n", argv[0]); + return 1; + } + + slq = talloc_zero(NULL, struct sl_query); + if (slq == NULL) { + printf("talloc error\n"); + return 1; + } + + slq->query_string = argv[1]; + slq->path_scope = "/foo/bar"; + + tq = talloc_zero(slq, struct sl_tracker_query); + if (tq == NULL) { + printf("talloc error\n"); + return 1; + } + slq->backend_private = tq; + + ok = map_spotlight_to_sparql_query(slq); + printf("%s\n", ok ? tq->sparql_query : "*mapping failed*"); + + talloc_free(slq); + return ok ? 0 : 1; +} diff --git a/source3/rpc_server/mdssvc/srv_mdssvc_nt.c b/source3/rpc_server/mdssvc/srv_mdssvc_nt.c new file mode 100644 index 0000000..9a16624 --- /dev/null +++ b/source3/rpc_server/mdssvc/srv_mdssvc_nt.c @@ -0,0 +1,319 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines for mdssvc + * Copyright (C) Ralph Boehme 2014 + * + * 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 "messages.h" +#include "ntdomain.h" +#include "rpc_server/rpc_server.h" +#include "rpc_server/rpc_config.h" +#include "rpc_server/mdssvc/srv_mdssvc_nt.h" +#include "libcli/security/security_token.h" +#include "libcli/security/dom_sid.h" +#include "gen_ndr/auth.h" +#include "mdssvc.h" +#include "smbd/globals.h" + +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_mdssvc.h" +#include "librpc/gen_ndr/ndr_mdssvc_scompat.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static NTSTATUS create_mdssvc_policy_handle(TALLOC_CTX *mem_ctx, + struct pipes_struct *p, + int snum, + const char *sharename, + const char *path, + struct policy_handle *handle) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct mds_ctx *mds_ctx; + NTSTATUS status; + + ZERO_STRUCTP(handle); + + status = mds_init_ctx(mem_ctx, + messaging_tevent_context(p->msg_ctx), + p->msg_ctx, + session_info, + snum, + sharename, + path, + &mds_ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("mds_init_ctx() path [%s] failed: %s\n", + path, nt_errstr(status)); + return status; + } + + if (!create_policy_hnd(p, handle, 0, mds_ctx)) { + talloc_free(mds_ctx); + ZERO_STRUCTP(handle); + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +void _mdssvc_open(struct pipes_struct *p, struct mdssvc_open *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int snum; + char *outpath = discard_const_p(char, r->out.share_path); + char *fake_path = NULL; + char *path; + NTSTATUS status; + + DBG_DEBUG("[%s]\n", r->in.share_name); + + *r->out.device_id = *r->in.device_id; + *r->out.unkn2 = *r->in.unkn2; + *r->out.unkn3 = *r->in.unkn3; + outpath[0] = '\0'; + + snum = lp_servicenumber(r->in.share_name); + if (!VALID_SNUM(snum)) { + return; + } + + path = lp_path(talloc_tos(), lp_sub, snum); + if (path == NULL) { + DBG_ERR("Couldn't create path for %s\n", + r->in.share_name); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + fake_path = talloc_asprintf(p->mem_ctx, "/%s", r->in.share_name); + if (fake_path == NULL) { + DBG_ERR("Couldn't create fake share path for %s\n", + r->in.share_name); + talloc_free(path); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + status = create_mdssvc_policy_handle(p->mem_ctx, p, + snum, + r->in.share_name, + path, + r->out.handle); + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_VOLUME)) { + ZERO_STRUCTP(r->out.handle); + talloc_free(path); + talloc_free(fake_path); + return; + } + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Couldn't create policy handle for %s\n", + r->in.share_name); + talloc_free(path); + talloc_free(fake_path); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + strlcpy(outpath, fake_path, 1024); + talloc_free(path); + talloc_free(fake_path); + return; +} + +void _mdssvc_unknown1(struct pipes_struct *p, struct mdssvc_unknown1 *r) +{ + struct mds_ctx *mds_ctx; + NTSTATUS status; + + mds_ctx = find_policy_by_hnd(p, + r->in.handle, + DCESRV_HANDLE_ANY, + struct mds_ctx, + &status); + if (!NT_STATUS_IS_OK(status)) { + if (ndr_policy_handle_empty(r->in.handle)) { + p->fault_state = 0; + } else { + p->fault_state = DCERPC_NCA_S_PROTO_ERROR; + } + *r->out.status = 0; + *r->out.flags = 0; + *r->out.unkn7 = 0; + return; + } + + DEBUG(10, ("%s: path: %s\n", __func__, mds_ctx->spath)); + + *r->out.status = 0; + *r->out.flags = 0x6b000001; + *r->out.unkn7 = 0; + + return; +} + +void _mdssvc_cmd(struct pipes_struct *p, struct mdssvc_cmd *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + bool ok; + struct mds_ctx *mds_ctx; + NTSTATUS status; + + mds_ctx = find_policy_by_hnd(p, + r->in.handle, + DCESRV_HANDLE_ANY, + struct mds_ctx, + &status); + if (!NT_STATUS_IS_OK(status)) { + if (ndr_policy_handle_empty(r->in.handle)) { + p->fault_state = 0; + } else { + p->fault_state = DCERPC_NCA_S_PROTO_ERROR; + } + r->out.response_blob->size = 0; + *r->out.fragment = 0; + *r->out.unkn9 = 0; + return; + } + + DEBUG(10, ("%s: path: %s\n", __func__, mds_ctx->spath)); + + ok = security_token_is_sid(session_info->security_token, + &mds_ctx->sid); + if (!ok) { + struct dom_sid_buf buf; + DBG_WARNING("not the same sid: %s\n", + dom_sid_str_buf(&mds_ctx->sid, &buf)); + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return; + } + + if (geteuid() != mds_ctx->uid) { + DEBUG(0, ("uid mismatch: %d/%d\n", geteuid(), mds_ctx->uid)); + smb_panic("uid mismatch"); + } + + if (r->in.request_blob.size > MAX_SL_FRAGMENT_SIZE) { + DEBUG(1, ("%s: request size too large\n", __func__)); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + if (r->in.request_blob.length > MAX_SL_FRAGMENT_SIZE) { + DEBUG(1, ("%s: request length too large\n", __func__)); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + if (r->in.max_fragment_size1 > MAX_SL_FRAGMENT_SIZE) { + DEBUG(1, ("%s: request fragment size too large: %u\n", + __func__, (unsigned)r->in.max_fragment_size1)); + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return; + } + + /* We currently don't use fragmentation at the mdssvc RPC layer */ + *r->out.fragment = 0; + + ok = mds_dispatch(mds_ctx, + &r->in.request_blob, + r->out.response_blob, + r->in.max_fragment_size1); + if (ok) { + *r->out.unkn9 = 0; + } else { + /* FIXME: just interpolating from AFP, needs verification */ + *r->out.unkn9 = UINT32_MAX; + } + + return; +} + +void _mdssvc_close(struct pipes_struct *p, struct mdssvc_close *r) +{ + struct mds_ctx *mds_ctx; + NTSTATUS status; + + mds_ctx = find_policy_by_hnd(p, + r->in.in_handle, + DCESRV_HANDLE_ANY, + struct mds_ctx, + &status); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("invalid handle\n"); + if (ndr_policy_handle_empty(r->in.in_handle)) { + p->fault_state = 0; + } else { + p->fault_state = DCERPC_NCA_S_PROTO_ERROR; + } + return; + } + + DBG_DEBUG("Close mdssvc handle for path: %s\n", mds_ctx->spath); + TALLOC_FREE(mds_ctx); + + *r->out.out_handle = *r->in.in_handle; + close_policy_hnd(p, r->in.in_handle); + + *r->out.status = 0; + + return; +} + +static NTSTATUS mdssvc__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +static NTSTATUS mdssvc__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_MDSSVC_INIT_SERVER \ + mdssvc_init_server + +#define DCESRV_INTERFACE_MDSSVC_SHUTDOWN_SERVER \ + mdssvc_shutdown_server + +static NTSTATUS mdssvc_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + bool ok; + + ok = mds_init(msg_ctx); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + return mdssvc__op_init_server(dce_ctx, ep_server); +} + +static NTSTATUS mdssvc_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + mds_shutdown(); + + return mdssvc__op_shutdown_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_mdssvc_scompat.c" diff --git a/source3/rpc_server/mdssvc/srv_mdssvc_nt.h b/source3/rpc_server/mdssvc/srv_mdssvc_nt.h new file mode 100644 index 0000000..8b78f5e --- /dev/null +++ b/source3/rpc_server/mdssvc/srv_mdssvc_nt.h @@ -0,0 +1,27 @@ +/* + * Unix SMB/CIFS implementation. + * MDSSVC RPC pipe initialisation routines + * + * Copyright (C) Ralph Boehme 2014 + * + * 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_MDSSVC_NT_H +#define _SRV_MDSSVC_NT_H + +bool init_service_mdssvc(struct messaging_context *msg_ctx); +bool shutdown_service_mdssvc(void); + +#endif /* _SRV_MDSSVC_NT_H */ diff --git a/source3/rpc_server/mdssvc/test_mdsparser_es.c b/source3/rpc_server/mdssvc/test_mdsparser_es.c new file mode 100644 index 0000000..02270a9 --- /dev/null +++ b/source3/rpc_server/mdssvc/test_mdsparser_es.c @@ -0,0 +1,304 @@ +/* + * Unix SMB/CIFS implementation. + * Copyright (C) Ralph Boehme 2019 + * + * 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 <setjmp.h> +#include <cmocka.h> +#include <jansson.h> +#include <talloc.h> +#include "lib/cmdline/cmdline.h" +#include "libcli/util/ntstatus.h" +#include "lib/util/samba_util.h" +#include "lib/torture/torture.h" +#include "lib/param/param.h" +#include "rpc_server/mdssvc/es_parser.tab.h" + +#define PATH_QUERY_SUBEXPR \ + " AND path.real.fulltext:\\\"/foo/bar\\\"" + +static struct { + const char *mds; + const char *es; +} map[] = { + { + "*==\"samba\"", + "(samba)" PATH_QUERY_SUBEXPR + }, { + "kMDItemTextContent==\"samba\"", + "(content:samba)" PATH_QUERY_SUBEXPR + }, { + "_kMDItemGroupId==\"11\"", + "(file.content_type:(application\\\\/pdf))" PATH_QUERY_SUBEXPR + }, { + "kMDItemContentType==\"1\"", + "(file.content_type:(message\\\\/rfc822))" PATH_QUERY_SUBEXPR + }, { + "kMDItemContentType==\"public.content\"", + "(file.content_type:(message\\\\/rfc822 application\\\\/pdf application\\\\/vnd.oasis.opendocument.presentation image\\\\/* text\\\\/*))" PATH_QUERY_SUBEXPR + }, { + "kMDItemContentTypeTree==\"1\"", + "(file.content_type:(message\\\\/rfc822))" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSContentChangeDate==$time.iso(2018-10-01T10:00:00Z)", + "(file.last_modified:2018\\\\-10\\\\-01T10\\\\:00\\\\:00Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSContentChangeDate==\"1\"", + "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSCreationDate==\"1\"", + "(file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSName==\"samba*\"", + "(file.filename:samba*)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSOwnerGroupID==\"0\"", + "(attributes.owner:0)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSOwnerUserID==\"0\"", + "(attributes.group:0)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSSize==\"1\"", + "(file.filesize:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemPath==\"/foo/bar\"", + "(path.real:\\\\/foo\\\\/bar)" PATH_QUERY_SUBEXPR + }, { + "kMDItemAttributeChangeDate==\"1\"", + "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemAuthors==\"Chouka\"", + "(meta.author:Chouka)" PATH_QUERY_SUBEXPR + }, { + "kMDItemContentCreationDate==\"1\"", + "(file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemContentModificationDate==\"1\"", + "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR + }, { + "kMDItemCreator==\"Chouka\"", + "(meta.raw.creator:Chouka)" PATH_QUERY_SUBEXPR + }, { + "kMDItemDescription==\"Dog\"", + "(meta.raw.description:Dog)" PATH_QUERY_SUBEXPR + }, { + "kMDItemDisplayName==\"Samba\"", + "(file.filename:Samba)" PATH_QUERY_SUBEXPR + }, { + "kMDItemDurationSeconds==\"1\"", + "(meta.raw.xmpDM\\\\:duration:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemNumberOfPages==\"1\"", + "(meta.raw.xmpTPg\\\\:NPages:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemTitle==\"Samba\"", + "(meta.title:Samba)" PATH_QUERY_SUBEXPR + }, { + "kMDItemAlbum==\"Red Roses for Me\"", + "(meta.raw.xmpDM\\\\:album:Red\\\\ Roses\\\\ for\\\\ Me)" PATH_QUERY_SUBEXPR + }, { + "kMDItemBitsPerSample==\"1\"", + "(meta.raw.tiff\\\\:BitsPerSample:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemPixelHeight==\"1\"", + "(meta.raw.Image\\\\ Height:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemPixelWidth==\"1\"", + "(meta.raw.Image\\\\ Width:1)" PATH_QUERY_SUBEXPR + }, { + "kMDItemResolutionHeightDPI==\"72\"", + "(meta.raw.Y\\\\ Resolution:72)" PATH_QUERY_SUBEXPR + }, { + "kMDItemResolutionWidthDPI==\"72\"", + "(meta.raw.X\\\\ Resolution:72)" PATH_QUERY_SUBEXPR + },{ + "*!=\"samba\"", + "((NOT samba))" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSSize!=\"1\"", + "((NOT file.filesize:1))" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSSize>\"1\"", + "(file.filesize:{1 TO *})" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSSize<\"1\"", + "(file.filesize:{* TO 1})" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSCreationDate!=\"1\"", + "((NOT file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z))" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSCreationDate>\"1\"", + "(file.created:{2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z TO *})" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSCreationDate<\"1\"", + "(file.created:{* TO 2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z})" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSName==\"Samba\"||kMDItemTextContent==\"Samba\"", + "(file.filename:Samba OR content:Samba)" PATH_QUERY_SUBEXPR + }, { + "kMDItemFSName==\"Samba\"&&kMDItemTextContent==\"Samba\"", + "((file.filename:Samba) AND (content:Samba))" PATH_QUERY_SUBEXPR + }, { + "InRange(kMDItemFSCreationDate,1,2)", + "(file.created:[2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z TO 2001\\\\-01\\\\-01T00\\\\:00\\\\:02Z])" PATH_QUERY_SUBEXPR + }, { + "InRange(kMDItemFSSize,1,2)", + "(file.filesize:[1 TO 2])" PATH_QUERY_SUBEXPR + } +}; + +static struct { + const char *mds; + const char *es; +} map_ignore_failures[] = { + { + "*==\"Samba\"||foo==\"bar\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"&&foo==\"bar\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"||kMDItemContentType==\"666\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"&&kMDItemContentType==\"666\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"||foo==\"bar\"||kMDItemContentType==\"666\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"&&foo==\"bar\"&&kMDItemContentType==\"666\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "foo==\"bar\"||kMDItemContentType==\"666\"||*==\"Samba\"||x!=\"6\"", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"||InRange(foo,1,2)", + "(Samba)" PATH_QUERY_SUBEXPR + }, { + "*==\"Samba\"||foo==$time.iso(2018-10-01T10:00:00Z)", + "(Samba)" PATH_QUERY_SUBEXPR + } +}; + +static void test_mdsparser_es(void **state) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const char *path_scope = "/foo/bar"; + char *es_query = NULL; + const char *path = NULL; + json_t *mappings = NULL; + json_error_t json_error; + int i; + bool ok; + + path = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "elasticsearch", + "mappings", + NULL); + assert_non_null(path); + + mappings = json_load_file(path, 0, &json_error); + assert_non_null(mappings); + + for (i = 0; i < ARRAY_SIZE(map); i++) { + DBG_DEBUG("Mapping: %s\n", map[i].mds); + ok = map_spotlight_to_es_query(frame, + mappings, + path_scope, + map[i].mds, + &es_query); + assert_true(ok); + assert_string_equal(es_query, map[i].es); + } + + if (!lp_parm_bool(GLOBAL_SECTION_SNUM, + "elasticsearch", + "test mapping failures", + false)) + { + goto done; + } + + for (i = 0; i < ARRAY_SIZE(map_ignore_failures); i++) { + DBG_DEBUG("Mapping: %s\n", map_ignore_failures[i].mds); + ok = map_spotlight_to_es_query(frame, + mappings, + path_scope, + map_ignore_failures[i].mds, + &es_query); + assert_true(ok); + assert_string_equal(es_query, map_ignore_failures[i].es); + } + +done: + json_decref(mappings); + TALLOC_FREE(frame); +} + +int main(int argc, const char *argv[]) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_mdsparser_es), + }; + struct poptOption long_options[] = { + POPT_AUTOHELP + POPT_COMMON_SAMBA + POPT_TABLEEND + }; + poptContext pc; + int opt; + bool ok; + TALLOC_CTX *frame = talloc_stackframe(); + struct loadparm_context *lp_ctx = NULL; + + smb_init_locale(); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_CLIENT, + false /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(frame); + exit(1); + } + lp_ctx = samba_cmdline_get_lp_ctx(); + lpcfg_set_cmdline(lp_ctx, "log level", "1"); + + pc = samba_popt_get_context(getprogname(), + argc, + argv, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(frame); + exit(1); + } + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "Unknown Option: %c\n", opt); + exit(1); + } + } + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c new file mode 100644 index 0000000..fa3e597 --- /dev/null +++ b/source3/rpc_server/netlogon/srv_netlog_nt.c @@ -0,0 +1,2937 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Paul Ashton 1997. + * Copyright (C) Jeremy Allison 1998-2001. + * Copyright (C) Andrew Bartlett 2001. + * Copyright (C) Guenther Deschner 2008-2009. + * + * 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/>. + */ + +/* This is the implementation of the netlogon pipe. */ + +#include "includes.h" +#include "system/passwd.h" /* uid_wrapper */ +#include "ntdomain.h" +#include "../libcli/auth/schannel.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_scompat.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "rpc_client/cli_lsarpc.h" +#include "rpc_client/init_lsa.h" +#include "rpc_client/init_samr.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "../libcli/security/security.h" +#include "../libcli/security/dom_sid.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "lib/crypto/md4.h" +#include "nsswitch/libwbclient/wbclient.h" +#include "../libcli/registry/util_reg.h" +#include "passdb.h" +#include "auth.h" +#include "messages.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/param/param.h" +#include "libsmb/dsgetdcname.h" +#include "lib/util/util_str_escape.h" +#include "source3/lib/substitute.h" +#include "librpc/rpc/server/netlogon/schannel_util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/************************************************************************* + _netr_LogonControl + *************************************************************************/ + +WERROR _netr_LogonControl(struct pipes_struct *p, + struct netr_LogonControl *r) +{ + struct netr_LogonControl2Ex l; + + switch (r->in.level) { + case 1: + break; + case 2: + return WERR_NOT_SUPPORTED; + default: + return WERR_INVALID_LEVEL; + } + + switch (r->in.function_code) { + case NETLOGON_CONTROL_QUERY: + case NETLOGON_CONTROL_REPLICATE: + case NETLOGON_CONTROL_SYNCHRONIZE: + case NETLOGON_CONTROL_PDC_REPLICATE: + case NETLOGON_CONTROL_BREAKPOINT: + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + case NETLOGON_CONTROL_TRUNCATE_LOG: + break; + default: + return WERR_NOT_SUPPORTED; + } + + l.in.logon_server = r->in.logon_server; + l.in.function_code = r->in.function_code; + l.in.level = r->in.level; + l.in.data = NULL; + l.out.query = r->out.query; + + return _netr_LogonControl2Ex(p, &l); +} + +/************************************************************************* + _netr_LogonControl2 + *************************************************************************/ + +WERROR _netr_LogonControl2(struct pipes_struct *p, + struct netr_LogonControl2 *r) +{ + struct netr_LogonControl2Ex l; + + l.in.logon_server = r->in.logon_server; + l.in.function_code = r->in.function_code; + l.in.level = r->in.level; + l.in.data = r->in.data; + l.out.query = r->out.query; + + return _netr_LogonControl2Ex(p, &l); +} + +/************************************************************************* + *************************************************************************/ + +static bool wb_change_trust_creds(const char *domain, WERROR *tc_status) +{ + wbcErr result; + struct wbcAuthErrorInfo *error = NULL; + + result = wbcChangeTrustCredentials(domain, &error); + switch (result) { + case WBC_ERR_WINBIND_NOT_AVAILABLE: + return false; + case WBC_ERR_DOMAIN_NOT_FOUND: + *tc_status = WERR_NO_SUCH_DOMAIN; + return true; + case WBC_ERR_SUCCESS: + *tc_status = WERR_OK; + return true; + default: + break; + } + + if (error && error->nt_status != 0) { + *tc_status = ntstatus_to_werror(NT_STATUS(error->nt_status)); + } else { + *tc_status = WERR_TRUST_FAILURE; + } + wbcFreeMemory(error); + return true; +} + +/************************************************************************* + *************************************************************************/ + +static bool wb_check_trust_creds(const char *domain, WERROR *tc_status) +{ + wbcErr result; + struct wbcAuthErrorInfo *error = NULL; + + result = wbcCheckTrustCredentials(domain, &error); + switch (result) { + case WBC_ERR_WINBIND_NOT_AVAILABLE: + return false; + case WBC_ERR_DOMAIN_NOT_FOUND: + *tc_status = WERR_NO_SUCH_DOMAIN; + return true; + case WBC_ERR_SUCCESS: + *tc_status = WERR_OK; + return true; + default: + break; + } + + if (error && error->nt_status != 0) { + *tc_status = ntstatus_to_werror(NT_STATUS(error->nt_status)); + } else { + *tc_status = WERR_TRUST_FAILURE; + } + wbcFreeMemory(error); + return true; +} + +/**************************************************************** + _netr_LogonControl2Ex +****************************************************************/ + +WERROR _netr_LogonControl2Ex(struct pipes_struct *p, + struct netr_LogonControl2Ex *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + uint32_t flags = 0x0; + WERROR pdc_connection_status = WERR_OK; + uint32_t logon_attempts = 0x0; + WERROR tc_status; + fstring dc_name2; + const char *dc_name = NULL; + struct sockaddr_storage dc_ss; + const char *domain = NULL; + struct netr_NETLOGON_INFO_1 *info1; + struct netr_NETLOGON_INFO_2 *info2; + struct netr_NETLOGON_INFO_3 *info3; + struct netr_NETLOGON_INFO_4 *info4; + const char *fn; + NTSTATUS status; + struct netr_DsRGetDCNameInfo *dc_info; + + switch (dce_call->pkt.u.request.opnum) { + case NDR_NETR_LOGONCONTROL: + fn = "_netr_LogonControl"; + break; + case NDR_NETR_LOGONCONTROL2: + fn = "_netr_LogonControl2"; + break; + case NDR_NETR_LOGONCONTROL2EX: + fn = "_netr_LogonControl2Ex"; + break; + default: + return WERR_INVALID_PARAMETER; + } + + switch (r->in.level) { + case 1: + case 2: + case 3: + case 4: + break; + default: + return WERR_INVALID_LEVEL; + } + + switch (r->in.function_code) { + case NETLOGON_CONTROL_QUERY: + break; + default: + if ((geteuid() != sec_initial_uid()) && + !nt_token_check_domain_rid( + session_info->security_token, DOMAIN_RID_ADMINS) && + !nt_token_check_sid( + &global_sid_Builtin_Administrators, + session_info->security_token)) + { + return WERR_ACCESS_DENIED; + } + break; + } + + tc_status = WERR_NO_SUCH_DOMAIN; + + switch (r->in.function_code) { + case NETLOGON_CONTROL_QUERY: + switch (r->in.level) { + case 1: + case 3: + break; + default: + return WERR_INVALID_PARAMETER; + } + + tc_status = WERR_OK; + break; + case NETLOGON_CONTROL_REPLICATE: + case NETLOGON_CONTROL_SYNCHRONIZE: + case NETLOGON_CONTROL_PDC_REPLICATE: + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + case NETLOGON_CONTROL_BREAKPOINT: + case NETLOGON_CONTROL_TRUNCATE_LOG: + case NETLOGON_CONTROL_TRANSPORT_NOTIFY: + case NETLOGON_CONTROL_FORCE_DNS_REG: + case NETLOGON_CONTROL_QUERY_DNS_REG: + return WERR_NOT_SUPPORTED; + + case NETLOGON_CONTROL_FIND_USER: + if (!r->in.data || !r->in.data->user) { + return WERR_NOT_SUPPORTED; + } + break; + case NETLOGON_CONTROL_SET_DBFLAG: + if (!r->in.data) { + return WERR_NOT_SUPPORTED; + } + break; + case NETLOGON_CONTROL_TC_VERIFY: + if (!r->in.data || !r->in.data->domain) { + return WERR_NOT_SUPPORTED; + } + + if (!wb_check_trust_creds(r->in.data->domain, &tc_status)) { + return WERR_NOT_SUPPORTED; + } + break; + case NETLOGON_CONTROL_TC_QUERY: + if (!r->in.data || !r->in.data->domain) { + return WERR_NOT_SUPPORTED; + } + + domain = r->in.data->domain; + + if (!is_trusted_domain(domain)) { + break; + } + + if (!get_dc_name(domain, NULL, dc_name2, &dc_ss)) { + tc_status = WERR_NO_LOGON_SERVERS; + break; + } + + dc_name = talloc_asprintf(p->mem_ctx, "\\\\%s", dc_name2); + if (!dc_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + tc_status = WERR_OK; + + break; + + case NETLOGON_CONTROL_REDISCOVER: + if (!r->in.data || !r->in.data->domain) { + return WERR_NOT_SUPPORTED; + } + + domain = r->in.data->domain; + + if (!is_trusted_domain(domain)) { + break; + } + + status = dsgetdcname(p->mem_ctx, p->msg_ctx, domain, NULL, NULL, + DS_FORCE_REDISCOVERY | DS_RETURN_FLAT_NAME, + &dc_info); + if (!NT_STATUS_IS_OK(status)) { + tc_status = WERR_NO_LOGON_SERVERS; + break; + } + + dc_name = talloc_asprintf(p->mem_ctx, "\\\\%s", dc_info->dc_unc); + if (!dc_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + tc_status = WERR_OK; + + break; + + case NETLOGON_CONTROL_CHANGE_PASSWORD: + if (!r->in.data || !r->in.data->domain) { + return WERR_NOT_SUPPORTED; + } + + if (!wb_change_trust_creds(r->in.data->domain, &tc_status)) { + return WERR_NOT_SUPPORTED; + } + break; + + default: + /* no idea what this should be */ + DEBUG(0,("%s: unimplemented function level [%d]\n", + fn, r->in.function_code)); + return WERR_NOT_SUPPORTED; + } + + /* prepare the response */ + + switch (r->in.level) { + case 1: + info1 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_1); + W_ERROR_HAVE_NO_MEMORY(info1); + + info1->flags = flags; + info1->pdc_connection_status = pdc_connection_status; + + r->out.query->info1 = info1; + break; + case 2: + info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2); + W_ERROR_HAVE_NO_MEMORY(info2); + + info2->flags = flags; + info2->pdc_connection_status = pdc_connection_status; + info2->trusted_dc_name = dc_name; + info2->tc_connection_status = tc_status; + + r->out.query->info2 = info2; + break; + case 3: + info3 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_3); + W_ERROR_HAVE_NO_MEMORY(info3); + + info3->flags = flags; + info3->logon_attempts = logon_attempts; + + r->out.query->info3 = info3; + break; + case 4: + info4 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_4); + W_ERROR_HAVE_NO_MEMORY(info4); + + info4->trusted_dc_name = dc_name; + info4->trusted_domain_name = r->in.data->domain; + + r->out.query->info4 = info4; + break; + default: + return WERR_INVALID_LEVEL; + } + + return WERR_OK; +} + +/************************************************************************* + _netr_NetrEnumerateTrustedDomains + *************************************************************************/ + +NTSTATUS _netr_NetrEnumerateTrustedDomains(struct pipes_struct *p, + struct netr_NetrEnumerateTrustedDomains *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + NTSTATUS result = NT_STATUS_OK; + DATA_BLOB blob; + size_t num_domains = 0; + const char **trusted_domains = NULL; + struct lsa_DomainList domain_list; + struct dcerpc_binding_handle *h = NULL; + struct policy_handle pol; + uint32_t enum_ctx = 0; + uint32_t max_size = (uint32_t)-1; + union lsa_revision_info out_revision_info = { + .info1 = { + .revision = 0, + }, + }; + uint32_t out_version = 0; + + ZERO_STRUCT(pol); + DEBUG(6,("_netr_NetrEnumerateTrustedDomains: %d\n", __LINE__)); + + status = rpcint_binding_handle(p->mem_ctx, + &ndr_table_lsarpc, + remote_address, + local_address, + session_info, + p->msg_ctx, + &h); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_lsa_open_policy_fallback( + h, + p->mem_ctx, + NULL, + true, + LSA_POLICY_VIEW_LOCAL_INFORMATION, + &out_version, + &out_revision_info, + &pol, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + do { + uint32_t i; + + /* Lookup list of trusted domains */ + status = dcerpc_lsa_EnumTrustDom(h, + p->mem_ctx, + &pol, + &enum_ctx, + &domain_list, + max_size, + &result); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, NT_STATUS_NO_MORE_ENTRIES) && + !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) { + status = result; + goto out; + } + + for (i = 0; i < domain_list.count; i++) { + if (!add_string_to_array(p->mem_ctx, domain_list.domains[i].name.string, + &trusted_domains, &num_domains)) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + if (num_domains > 0) { + /* multi sz terminate */ + trusted_domains = talloc_realloc(p->mem_ctx, trusted_domains, const char *, num_domains + 1); + if (trusted_domains == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + trusted_domains[num_domains] = NULL; + } + + if (!push_reg_multi_sz(trusted_domains, &blob, trusted_domains)) { + TALLOC_FREE(trusted_domains); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + r->out.trusted_domains_blob->data = blob.data; + r->out.trusted_domains_blob->length = blob.length; + + DEBUG(6,("_netr_NetrEnumerateTrustedDomains: %d\n", __LINE__)); + + status = NT_STATUS_OK; + + out: + if (is_valid_policy_hnd(&pol)) { + dcerpc_lsa_Close(h, p->mem_ctx, &pol, &result); + } + + return status; +} + +/************************************************************************* + *************************************************************************/ + +static NTSTATUS samr_find_machine_account(TALLOC_CTX *mem_ctx, + struct dcerpc_binding_handle *b, + const char *account_name, + uint32_t access_mask, + struct dom_sid2 **domain_sid_p, + uint32_t *user_rid_p, + struct policy_handle *user_handle) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_OK; + struct policy_handle connect_handle; + struct policy_handle domain_handle = { 0, }; + struct lsa_String domain_name; + struct dom_sid2 *domain_sid; + struct lsa_String names; + struct samr_Ids rids; + struct samr_Ids types; + uint32_t rid; + + status = dcerpc_samr_Connect2(b, mem_ctx, + lp_netbios_name(), + SAMR_ACCESS_CONNECT_TO_SERVER | + SAMR_ACCESS_ENUM_DOMAINS | + SAMR_ACCESS_LOOKUP_DOMAIN, + &connect_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + init_lsa_String(&domain_name, get_global_sam_name()); + + status = dcerpc_samr_LookupDomain(b, mem_ctx, + &connect_handle, + &domain_name, + &domain_sid, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + status = dcerpc_samr_OpenDomain(b, mem_ctx, + &connect_handle, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + domain_sid, + &domain_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + init_lsa_String(&names, account_name); + + status = dcerpc_samr_LookupNames(b, mem_ctx, + &domain_handle, + 1, + &names, + &rids, + &types, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + if (rids.count != 1) { + status = NT_STATUS_NO_SUCH_USER; + goto out; + } + if (types.count != 1) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + if (types.ids[0] != SID_NAME_USER) { + status = NT_STATUS_NO_SUCH_USER; + goto out; + } + + rid = rids.ids[0]; + + status = dcerpc_samr_OpenUser(b, mem_ctx, + &domain_handle, + access_mask, + rid, + user_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + if (user_rid_p) { + *user_rid_p = rid; + } + + if (domain_sid_p) { + *domain_sid_p = domain_sid; + } + + out: + if (is_valid_policy_hnd(&domain_handle)) { + dcerpc_samr_Close(b, mem_ctx, &domain_handle, &result); + } + if (is_valid_policy_hnd(&connect_handle)) { + dcerpc_samr_Close(b, mem_ctx, &connect_handle, &result); + } + + return status; +} + +/****************************************************************** + gets a machine password entry. checks access rights of the host. + ******************************************************************/ + +static NTSTATUS get_md4pw(struct samr_Password *md4pw, const char *mach_acct, + enum netr_SchannelType sec_chan_type, + struct dom_sid *sid, + struct messaging_context *msg_ctx) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_OK; + TALLOC_CTX *mem_ctx = NULL; + struct dcerpc_binding_handle *h = NULL; + struct tsocket_address *local = NULL; + struct policy_handle user_handle = { .handle_type = 0 }; + uint32_t user_rid = UINT32_MAX; + struct dom_sid *domain_sid = NULL; + uint32_t acct_ctrl = 0; + union samr_UserInfo *info = NULL; + struct auth_session_info *session_info = NULL; + int rc; + +#if 0 + + /* + * Currently this code is redundant as we already have a filter + * by hostname list. What this code really needs to do is to + * get a hosts allowed/hosts denied list from the SAM database + * on a per user basis, and make the access decision there. + * I will leave this code here for now as a reminder to implement + * this at a later date. JRA. + */ + + if (!allow_access(lp_domain_hostsdeny(), lp_domain_hostsallow(), + p->client_id.name, + p->client_id.addr)) { + DEBUG(0,("get_md4pw: Workstation %s denied access to domain\n", mach_acct)); + return False; + } +#endif /* 0 */ + + mem_ctx = talloc_stackframe(); + + status = make_session_info_system(mem_ctx, &session_info); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + ZERO_STRUCT(user_handle); + + rc = tsocket_address_inet_from_strings(mem_ctx, + "ip", + "127.0.0.1", + 0, + &local); + if (rc < 0) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + status = rpcint_binding_handle(mem_ctx, + &ndr_table_samr, + local, + NULL, + session_info, + msg_ctx, + &h); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = samr_find_machine_account(mem_ctx, h, mach_acct, + SEC_FLAG_MAXIMUM_ALLOWED, + &domain_sid, &user_rid, + &user_handle); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcerpc_samr_QueryUserInfo2(h, + mem_ctx, + &user_handle, + UserControlInformation, + &info, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + acct_ctrl = info->info16.acct_flags; + + if (acct_ctrl & ACB_DISABLED) { + DEBUG(0,("get_md4pw: Workstation %s: account is disabled\n", mach_acct)); + status = NT_STATUS_ACCOUNT_DISABLED; + goto out; + } + + if (!(acct_ctrl & ACB_SVRTRUST) && + !(acct_ctrl & ACB_WSTRUST) && + !(acct_ctrl & ACB_DOMTRUST)) + { + DEBUG(0,("get_md4pw: Workstation %s: account is not a trust account\n", mach_acct)); + status = NT_STATUS_NO_TRUST_SAM_ACCOUNT; + goto out; + } + + switch (sec_chan_type) { + case SEC_CHAN_BDC: + if (!(acct_ctrl & ACB_SVRTRUST)) { + DEBUG(0,("get_md4pw: Workstation %s: BDC secure channel requested " + "but not a server trust account\n", mach_acct)); + status = NT_STATUS_NO_TRUST_SAM_ACCOUNT; + goto out; + } + break; + case SEC_CHAN_WKSTA: + if (!(acct_ctrl & ACB_WSTRUST)) { + DEBUG(0,("get_md4pw: Workstation %s: WORKSTATION secure channel requested " + "but not a workstation trust account\n", mach_acct)); + status = NT_STATUS_NO_TRUST_SAM_ACCOUNT; + goto out; + } + break; + case SEC_CHAN_DOMAIN: + if (!(acct_ctrl & ACB_DOMTRUST)) { + DEBUG(0,("get_md4pw: Workstation %s: DOMAIN secure channel requested " + "but not a interdomain trust account\n", mach_acct)); + status = NT_STATUS_NO_TRUST_SAM_ACCOUNT; + goto out; + } + break; + default: + break; + } + + become_root(); + status = dcerpc_samr_QueryUserInfo2(h, + mem_ctx, + &user_handle, + UserInternal1Information, + &info, + &result); + unbecome_root(); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + if (info->info18.nt_pwd_active == 0) { + DEBUG(0,("get_md4pw: Workstation %s: account does not have a password\n", mach_acct)); + status = NT_STATUS_LOGON_FAILURE; + goto out; + } + + /* samr gives out nthash unencrypted (!) */ + memcpy(md4pw->hash, info->info18.nt_pwd.hash, 16); + + sid_compose(sid, domain_sid, user_rid); + + out: + if (h && is_valid_policy_hnd(&user_handle)) { + dcerpc_samr_Close(h, mem_ctx, &user_handle, &result); + } + + talloc_free(mem_ctx); + + return status; +} + +/************************************************************************* + _netr_ServerReqChallenge + *************************************************************************/ + +NTSTATUS _netr_ServerReqChallenge(struct pipes_struct *p, + struct netr_ServerReqChallenge *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct netlogon_server_pipe_state *pipe_state = NULL; + NTSTATUS status; + + pipe_state = dcesrv_iface_state_find_conn( + dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + + if (pipe_state) { + DEBUG(10,("_netr_ServerReqChallenge: new challenge requested. Clearing old state.\n")); + talloc_free(pipe_state); + } + + pipe_state = talloc(p->mem_ctx, struct netlogon_server_pipe_state); + NT_STATUS_HAVE_NO_MEMORY(pipe_state); + + pipe_state->client_challenge = *r->in.credentials; + + netlogon_creds_random_challenge(&pipe_state->server_challenge); + + *r->out.return_credentials = pipe_state->server_challenge; + + status = dcesrv_iface_state_store_conn( + dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + pipe_state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/************************************************************************* + _netr_ServerAuthenticate + Create the initial credentials. + *************************************************************************/ + +NTSTATUS _netr_ServerAuthenticate(struct pipes_struct *p, + struct netr_ServerAuthenticate *r) +{ + struct netr_ServerAuthenticate3 a; + uint32_t negotiate_flags = 0; + uint32_t rid; + + a.in.server_name = r->in.server_name; + a.in.account_name = r->in.account_name; + a.in.secure_channel_type = r->in.secure_channel_type; + a.in.computer_name = r->in.computer_name; + a.in.credentials = r->in.credentials; + a.in.negotiate_flags = &negotiate_flags; + + a.out.return_credentials = r->out.return_credentials; + a.out.rid = &rid; + a.out.negotiate_flags = &negotiate_flags; + + return _netr_ServerAuthenticate3(p, &a); + +} + +/************************************************************************* + _netr_ServerAuthenticate3 + *************************************************************************/ + +NTSTATUS _netr_ServerAuthenticate3(struct pipes_struct *p, + struct netr_ServerAuthenticate3 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + NTSTATUS status; + uint32_t srv_flgs; + /* r->in.negotiate_flags is an aliased pointer to r->out.negotiate_flags, + * so use a copy to avoid destroying the client values. */ + uint32_t in_neg_flags = *r->in.negotiate_flags; + const char *fn; + struct loadparm_context *lp_ctx = p->dce_call->conn->dce_ctx->lp_ctx; + struct dom_sid sid; + struct samr_Password mach_pwd; + struct netlogon_creds_CredentialState *creds; + struct netlogon_server_pipe_state *pipe_state = NULL; + + /* According to Microsoft (see bugid #6099) + * Windows 7 looks at the negotiate_flags + * returned in this structure *even if the + * call fails with access denied* ! So in order + * to allow Win7 to connect to a Samba NT style + * PDC we set the flags before we know if it's + * an error or not. + */ + + /* 0x000001ff */ + srv_flgs = NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_PASSWORD_SET2; + + /* Ensure we support strong (128-bit) keys. */ + if (in_neg_flags & NETLOGON_NEG_STRONG_KEYS) { + srv_flgs |= NETLOGON_NEG_STRONG_KEYS; + } + + if (in_neg_flags & NETLOGON_NEG_SUPPORTS_AES) { + srv_flgs |= NETLOGON_NEG_SUPPORTS_AES; + } + + if (in_neg_flags & NETLOGON_NEG_SCHANNEL) { + srv_flgs |= NETLOGON_NEG_SCHANNEL; + } + + /* + * Support authentication of trusted domains. + * + * These flags are the minimum required set which works with win2k3 + * and win2k8. + */ + if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) { + srv_flgs |= NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION; + } + + /* + * If weak crypto is disabled, do not announce that we support RC4. + */ + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) { + srv_flgs &= ~NETLOGON_NEG_ARCFOUR; + } + + switch (dce_call->pkt.u.request.opnum) { + case NDR_NETR_SERVERAUTHENTICATE: + fn = "_netr_ServerAuthenticate"; + break; + case NDR_NETR_SERVERAUTHENTICATE2: + fn = "_netr_ServerAuthenticate2"; + break; + case NDR_NETR_SERVERAUTHENTICATE3: + fn = "_netr_ServerAuthenticate3"; + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /* We use this as the key to store the creds: */ + /* r->in.computer_name */ + + pipe_state = dcesrv_iface_state_find_conn( + dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + + if (!pipe_state) { + DEBUG(0,("%s: no challenge sent to client %s\n", fn, + r->in.computer_name)); + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + + status = get_md4pw(&mach_pwd, + r->in.account_name, + r->in.secure_channel_type, + &sid, p->msg_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: failed to get machine password for " + "account %s: %s\n", + fn, r->in.account_name, nt_errstr(status) )); + /* always return NT_STATUS_ACCESS_DENIED */ + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + + /* From the client / server challenges and md4 password, generate sess key */ + /* Check client credentials are valid. */ + creds = netlogon_creds_server_init(p->mem_ctx, + r->in.account_name, + r->in.computer_name, + r->in.secure_channel_type, + &pipe_state->client_challenge, + &pipe_state->server_challenge, + &mach_pwd, + r->in.credentials, + r->out.return_credentials, + srv_flgs); + if (!creds) { + DEBUG(0,("%s: netlogon_creds_server_check failed. Rejecting auth " + "request from client %s machine account %s\n", + fn, r->in.computer_name, + r->in.account_name)); + status = NT_STATUS_ACCESS_DENIED; + goto out; + } + + creds->sid = dom_sid_dup(creds, &sid); + if (!creds->sid) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + /* Store off the state so we can continue after client disconnect. */ + become_root(); + status = schannel_save_creds_state(p->mem_ctx, lp_ctx, creds); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(r->out.return_credentials); + goto out; + } + + sid_peek_rid(&sid, r->out.rid); + + status = NT_STATUS_OK; + + out: + + *r->out.negotiate_flags = srv_flgs; + return status; +} + +/************************************************************************* + _netr_ServerAuthenticate2 + *************************************************************************/ + +NTSTATUS _netr_ServerAuthenticate2(struct pipes_struct *p, + struct netr_ServerAuthenticate2 *r) +{ + struct netr_ServerAuthenticate3 a; + uint32_t rid; + + a.in.server_name = r->in.server_name; + a.in.account_name = r->in.account_name; + a.in.secure_channel_type = r->in.secure_channel_type; + a.in.computer_name = r->in.computer_name; + a.in.credentials = r->in.credentials; + a.in.negotiate_flags = r->in.negotiate_flags; + + a.out.return_credentials = r->out.return_credentials; + a.out.rid = &rid; + a.out.negotiate_flags = r->out.negotiate_flags; + + return _netr_ServerAuthenticate3(p, &a); +} + +/************************************************************************* + *************************************************************************/ + +static NTSTATUS samr_open_machine_account( + struct dcerpc_binding_handle *b, + const struct dom_sid *machine_sid, + uint32_t access_mask, + struct policy_handle *machine_handle) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct policy_handle connect_handle = { .handle_type = 0 }; + struct policy_handle domain_handle = { .handle_type = 0 }; + struct dom_sid domain_sid = *machine_sid; + uint32_t machine_rid; + NTSTATUS result = NT_STATUS_OK; + NTSTATUS status = NT_STATUS_INVALID_PARAMETER; + bool ok; + + ok = sid_split_rid(&domain_sid, &machine_rid); + if (!ok) { + goto out; + } + + status = dcerpc_samr_Connect2( + b, + frame, + lp_netbios_name(), + SAMR_ACCESS_CONNECT_TO_SERVER | + SAMR_ACCESS_ENUM_DOMAINS | + SAMR_ACCESS_LOOKUP_DOMAIN, + &connect_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + status = dcerpc_samr_OpenDomain( + b, + frame, + &connect_handle, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + &domain_sid, + &domain_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + status = dcerpc_samr_OpenUser( + b, + frame, + &domain_handle, + SEC_FLAG_MAXIMUM_ALLOWED, + machine_rid, + machine_handle, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + +out: + if ((b != NULL) && is_valid_policy_hnd(&domain_handle)) { + dcerpc_samr_Close(b, frame, &domain_handle, &result); + } + if ((b != NULL) && is_valid_policy_hnd(&connect_handle)) { + dcerpc_samr_Close(b, frame, &connect_handle, &result); + } + TALLOC_FREE(frame); + return status; +} + +struct _samr_Credentials_t { + enum { + CRED_TYPE_NT_HASH, + CRED_TYPE_PLAIN_TEXT, + } cred_type; + union { + struct samr_Password *nt_hash; + const char *password; + } creds; +}; + + +static NTSTATUS netr_set_machine_account_password( + TALLOC_CTX *mem_ctx, + struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const struct dom_sid *machine_sid, + struct _samr_Credentials_t *cr) +{ + NTSTATUS status; + NTSTATUS result = NT_STATUS_OK; + struct dcerpc_binding_handle *h = NULL; + struct tsocket_address *local; + struct policy_handle user_handle = { .handle_type = 0 }; + uint32_t acct_ctrl; + union samr_UserInfo *info; + struct samr_UserInfo18 info18; + struct samr_UserInfo26 info26; + DATA_BLOB in,out; + int rc; + DATA_BLOB session_key; + enum samr_UserInfoLevel infolevel; + TALLOC_CTX *frame = talloc_stackframe(); + + status = session_extract_session_key(session_info, + &session_key, + KEY_USE_16BYTES); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + rc = tsocket_address_inet_from_strings(frame, + "ip", + "127.0.0.1", + 0, + &local); + if (rc < 0) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + status = rpcint_binding_handle(frame, + &ndr_table_samr, + local, + NULL, + get_session_info_system(), + msg_ctx, + &h); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = samr_open_machine_account( + h, machine_sid, SEC_FLAG_MAXIMUM_ALLOWED, &user_handle); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcerpc_samr_QueryUserInfo2(h, + frame, + &user_handle, + UserControlInformation, + &info, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + acct_ctrl = info->info16.acct_flags; + + if (!(acct_ctrl & ACB_WSTRUST || + acct_ctrl & ACB_SVRTRUST || + acct_ctrl & ACB_DOMTRUST)) { + status = NT_STATUS_NO_SUCH_USER; + goto out; + } + + if (acct_ctrl & ACB_DISABLED) { + status = NT_STATUS_ACCOUNT_DISABLED; + goto out; + } + + switch(cr->cred_type) { + case CRED_TYPE_NT_HASH: + ZERO_STRUCT(info18); + + infolevel = UserInternal1Information; + + in = data_blob_const(cr->creds.nt_hash, 16); + out = data_blob_talloc_zero(frame, 16); + if (out.data == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + goto out; + } + memcpy(info18.nt_pwd.hash, out.data, out.length); + + info18.nt_pwd_active = true; + + info->info18 = info18; + break; + case CRED_TYPE_PLAIN_TEXT: + ZERO_STRUCT(info26); + + infolevel = UserInternal5InformationNew; + + status = init_samr_CryptPasswordEx(cr->creds.password, + &session_key, + &info26.password); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + info26.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON; + info->info26 = info26; + break; + default: + status = NT_STATUS_INTERNAL_ERROR; + goto out; + break; + } + + status = dcerpc_samr_SetUserInfo2(h, + frame, + &user_handle, + infolevel, + info, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + goto out; + } + + out: + if (h && is_valid_policy_hnd(&user_handle)) { + dcerpc_samr_Close(h, frame, &user_handle, &result); + } + TALLOC_FREE(frame); + + return status; +} + +/************************************************************************* + _netr_ServerPasswordSet + *************************************************************************/ + +NTSTATUS _netr_ServerPasswordSet(struct pipes_struct *p, + struct netr_ServerPasswordSet *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status = NT_STATUS_OK; + size_t i; + struct netlogon_creds_CredentialState *creds = NULL; + struct _samr_Credentials_t cr = { CRED_TYPE_NT_HASH, {0}}; + + DEBUG(5,("_netr_ServerPasswordSet: %d\n", __LINE__)); + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + const char *computer_name = "<unknown>"; + + if (creds != NULL && creds->computer_name != NULL) { + computer_name = creds->computer_name; + } + DEBUG(2,("_netr_ServerPasswordSet: netlogon_creds_server_step failed. Rejecting auth " + "request from client %s machine account %s\n", + r->in.computer_name, computer_name)); + TALLOC_FREE(creds); + return status; + } + + DEBUG(3,("_netr_ServerPasswordSet: Server Password Set by remote machine:[%s] on account [%s]\n", + r->in.computer_name, creds->computer_name)); + + status = netlogon_creds_des_decrypt(creds, r->in.new_password); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(100,("_netr_ServerPasswordSet: new given value was :\n")); + for(i = 0; i < sizeof(r->in.new_password->hash); i++) + DEBUG(100,("%02X ", r->in.new_password->hash[i])); + DEBUG(100,("\n")); + + cr.creds.nt_hash = r->in.new_password; + status = netr_set_machine_account_password(p->mem_ctx, + session_info, + p->msg_ctx, + creds->sid, + &cr); + return status; +} + +/**************************************************************** + _netr_ServerPasswordSet2 +****************************************************************/ + +NTSTATUS _netr_ServerPasswordSet2(struct pipes_struct *p, + struct netr_ServerPasswordSet2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + struct netlogon_creds_CredentialState *creds = NULL; + DATA_BLOB plaintext = data_blob_null; + DATA_BLOB new_password = data_blob_null; + size_t confounder_len; + DATA_BLOB dec_blob = data_blob_null; + DATA_BLOB enc_blob = data_blob_null; + struct samr_CryptPassword password_buf; + struct _samr_Credentials_t cr = { CRED_TYPE_PLAIN_TEXT, {0}}; + bool ok; + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("netlogon_creds_server_step failed. " + "Rejecting auth request from client %s\n", + r->in.computer_name); + TALLOC_FREE(creds); + return status; + } + + DBG_NOTICE("Server Password Set2 by remote " + "machine:[%s] on account [%s]\n", + r->in.computer_name, + creds->computer_name != NULL ? + creds->computer_name : "<unknown>"); + + memcpy(password_buf.data, r->in.new_password->data, 512); + SIVAL(password_buf.data, 512, r->in.new_password->length); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_aes_decrypt(creds, + password_buf.data, + 516); + } else { + status = netlogon_creds_arcfour_crypt(creds, + password_buf.data, + 516); + } + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(creds); + return status; + } + + if (!extract_pw_from_buffer(p->mem_ctx, password_buf.data, &new_password)) { + DEBUG(2,("_netr_ServerPasswordSet2: unable to extract password " + "from a buffer. Rejecting auth request as a wrong password\n")); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the length field was encrypted, + * otherwise we are under attack. + */ + if (new_password.length == r->in.new_password->length) { + DBG_WARNING("Length[%zu] field not encrypted\n", + new_password.length); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * We don't allow empty passwords for machine accounts. + */ + if (new_password.length < 2) { + DBG_WARNING("Empty password Length[%zu]\n", + new_password.length); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the confounder part of CryptPassword + * buffer was encrypted, otherwise we are under attack. + */ + confounder_len = 512 - new_password.length; + enc_blob = data_blob_const(r->in.new_password->data, confounder_len); + dec_blob = data_blob_const(password_buf.data, confounder_len); + if (confounder_len > 0 && data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Confounder buffer not encrypted Length[%zu]\n", + confounder_len); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Check that the password part was actually encrypted, + * otherwise we are under attack. + */ + enc_blob = data_blob_const(r->in.new_password->data + confounder_len, + new_password.length); + dec_blob = data_blob_const(password_buf.data + confounder_len, + new_password.length); + if (data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Password buffer not encrypted Length[%zu]\n", + new_password.length); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * don't allow zero buffers + */ + if (all_zero(new_password.data, new_password.length)) { + DBG_WARNING("Password zero buffer Length[%zu]\n", + new_password.length); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* Convert from UTF16 -> plaintext. */ + ok = convert_string_talloc(p->mem_ctx, + CH_UTF16, + CH_UNIX, + new_password.data, + new_password.length, + &plaintext.data, + &plaintext.length); + if (!ok) { + DBG_WARNING("unable to extract password from a buffer. " + "Rejecting auth request as a wrong password\n"); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * We don't allow empty passwords for machine accounts. + */ + + cr.creds.password = (const char*) plaintext.data; + if (strlen(cr.creds.password) == 0) { + DBG_WARNING("Empty plaintext password\n"); + TALLOC_FREE(creds); + return NT_STATUS_WRONG_PASSWORD; + } + + status = netr_set_machine_account_password(p->mem_ctx, + session_info, + p->msg_ctx, + creds->sid, + &cr); + TALLOC_FREE(creds); + return status; +} + +/************************************************************************* + _netr_LogonSamLogoff + *************************************************************************/ + +NTSTATUS _netr_LogonSamLogoff(struct pipes_struct *p, + struct netr_LogonSamLogoff *r) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds; + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + + return status; +} + +static NTSTATUS _netr_LogonSamLogon_check(const struct netr_LogonSamLogonEx *r) +{ + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + if (r->in.logon->password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + break; + case NetlogonValidationSamInfo4: /* 6 */ + if ((pdb_capabilities() & PDB_CAP_ADS) == 0) { + DEBUG(10,("Not adding validation info level 6 " + "without ADS passdb backend\n")); + return NT_STATUS_INVALID_INFO_CLASS; + } + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + if (r->in.logon->network == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + break; + case NetlogonValidationSamInfo4: /* 6 */ + if ((pdb_capabilities() & PDB_CAP_ADS) == 0) { + DEBUG(10,("Not adding validation info level 6 " + "without ADS passdb backend\n")); + return NT_STATUS_INVALID_INFO_CLASS; + } + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + + case NetlogonGenericInformation: + if (r->in.logon->generic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* we don't support this here */ + return NT_STATUS_INVALID_PARAMETER; +#if 0 + switch (r->in.validation_level) { + /* TODO: case NetlogonValidationGenericInfo: 4 */ + case NetlogonValidationGenericInfo2: /* 5 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; +#endif + default: + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +/************************************************************************* + _netr_LogonSamLogon_base + *************************************************************************/ + +static NTSTATUS _netr_LogonSamLogon_base(struct pipes_struct *p, + struct netr_LogonSamLogonEx *r, + struct netlogon_creds_CredentialState *creds) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + NTSTATUS status = NT_STATUS_OK; + union netr_LogonLevel *logon = r->in.logon; + const char *nt_username, *nt_domain, *nt_workstation; + char *sanitized_username = NULL; + struct auth_usersupplied_info *user_info = NULL; + struct auth_serversupplied_info *server_info = NULL; + struct auth_context *auth_context = NULL; + const char *fn; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + +#ifdef DEBUG_PASSWORD + logon = netlogon_creds_shallow_copy_logon(p->mem_ctx, + r->in.logon_level, + r->in.logon); + if (logon == NULL) { + logon = r->in.logon; + } +#endif + + switch (opnum) { + case NDR_NETR_LOGONSAMLOGON: + fn = "_netr_LogonSamLogon"; + /* + * Already called netr_check_schannel() via + * netr_creds_server_step_check() + */ + break; + case NDR_NETR_LOGONSAMLOGONWITHFLAGS: + fn = "_netr_LogonSamLogonWithFlags"; + /* + * Already called netr_check_schannel() via + * netr_creds_server_step_check() + */ + break; + case NDR_NETR_LOGONSAMLOGONEX: + fn = "_netr_LogonSamLogonEx"; + + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + return NT_STATUS_ACCESS_DENIED; + } + + status = dcesrv_netr_check_schannel(p->dce_call, + creds, + auth_type, + auth_level, + opnum); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + *r->out.authoritative = 1; /* authoritative response */ + + switch (r->in.validation_level) { + case 2: + r->out.validation->sam2 = talloc_zero(p->mem_ctx, struct netr_SamInfo2); + if (!r->out.validation->sam2) { + return NT_STATUS_NO_MEMORY; + } + break; + case 3: + r->out.validation->sam3 = talloc_zero(p->mem_ctx, struct netr_SamInfo3); + if (!r->out.validation->sam3) { + return NT_STATUS_NO_MEMORY; + } + break; + case 6: + r->out.validation->sam6 = talloc_zero(p->mem_ctx, struct netr_SamInfo6); + if (!r->out.validation->sam6) { + return NT_STATUS_NO_MEMORY; + } + break; + default: + DEBUG(0,("%s: bad validation_level value %d.\n", + fn, (int)r->in.validation_level)); + return NT_STATUS_INVALID_INFO_CLASS; + } + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + nt_username = logon->password->identity_info.account_name.string ? + logon->password->identity_info.account_name.string : ""; + nt_domain = logon->password->identity_info.domain_name.string ? + logon->password->identity_info.domain_name.string : ""; + nt_workstation = logon->password->identity_info.workstation.string ? + logon->password->identity_info.workstation.string : ""; + + DEBUG(3,("SAM Logon (Interactive). Domain:[%s]. ", lp_workgroup())); + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + nt_username = logon->network->identity_info.account_name.string ? + logon->network->identity_info.account_name.string : ""; + nt_domain = logon->network->identity_info.domain_name.string ? + logon->network->identity_info.domain_name.string : ""; + nt_workstation = logon->network->identity_info.workstation.string ? + logon->network->identity_info.workstation.string : ""; + + DEBUG(3,("SAM Logon (Network). Domain:[%s]. ", lp_workgroup())); + break; + default: + DEBUG(2,("SAM Logon: unsupported switch value\n")); + return NT_STATUS_INVALID_INFO_CLASS; + } /* end switch */ + + DEBUG(3,("User:[%s@%s] Requested Domain:[%s]\n", nt_username, nt_workstation, nt_domain)); + + DEBUG(5,("Attempting validation level %d for unmapped username %s.\n", + r->in.validation_level, nt_username)); + + status = netlogon_creds_decrypt_samlogon_logon(creds, + r->in.logon_level, + logon); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = make_auth3_context_for_netlogon(talloc_tos(), &auth_context); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (r->in.logon_level) { + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + { + const char *wksname = nt_workstation; + const char *workgroup = lp_workgroup(); + bool ok; + + ok = auth3_context_set_challenge( + auth_context, logon->network->challenge, "fixed"); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + /* For a network logon, the workstation name comes in with two + * backslashes in the front. Strip them if they are there. */ + + if (*wksname == '\\') wksname++; + if (*wksname == '\\') wksname++; + + /* Standard challenge/response authentication */ + if (!make_user_info_netlogon_network(talloc_tos(), + &user_info, + nt_username, nt_domain, + wksname, + remote_address, + local_address, + logon->network->identity_info.parameter_control, + logon->network->lm.data, + logon->network->lm.length, + logon->network->nt.data, + logon->network->nt.length)) { + status = NT_STATUS_NO_MEMORY; + } + + if (NT_STATUS_IS_OK(status)) { + status = NTLMv2_RESPONSE_verify_netlogon_creds( + user_info->client.account_name, + user_info->client.domain_name, + user_info->password.response.nt, + creds, workgroup); + } + break; + } + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + + /* 'Interactive' authentication, supplies the password in its + MD4 form, encrypted with the session key. We will convert + this to challenge/response for the auth subsystem to chew + on */ + { + uint8_t chal[8]; + +#ifdef DEBUG_PASSWORD + if (logon != r->in.logon) { + DEBUG(100,("lm owf password:")); + dump_data(100, + r->in.logon->password->lmpassword.hash, 16); + + DEBUG(100,("nt owf password:")); + dump_data(100, + r->in.logon->password->ntpassword.hash, 16); + } + + DEBUG(100,("decrypt of lm owf password:")); + dump_data(100, logon->password->lmpassword.hash, 16); + + DEBUG(100,("decrypt of nt owf password:")); + dump_data(100, logon->password->ntpassword.hash, 16); +#endif + + auth_get_ntlm_challenge(auth_context, chal); + + if (!make_user_info_netlogon_interactive(talloc_tos(), + &user_info, + nt_username, nt_domain, + nt_workstation, + remote_address, + local_address, + logon->password->identity_info.parameter_control, + chal, + logon->password->lmpassword.hash, + logon->password->ntpassword.hash)) { + status = NT_STATUS_NO_MEMORY; + } + break; + } + default: + DEBUG(2,("SAM Logon: unsupported switch value\n")); + return NT_STATUS_INVALID_INFO_CLASS; + } /* end switch */ + + if ( NT_STATUS_IS_OK(status) ) { + status = auth_check_ntlm_password(p->mem_ctx, + auth_context, + user_info, + &server_info, + r->out.authoritative); + } + + TALLOC_FREE(auth_context); + TALLOC_FREE(user_info); + + DEBUG(5,("%s: check_password returned status %s\n", + fn, nt_errstr(status))); + + /* Check account and password */ + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(server_info); + return status; + } + + if (server_info->guest) { + /* We don't like guest domain logons... */ + DEBUG(5,("%s: Attempted domain logon as GUEST " + "denied.\n", fn)); + TALLOC_FREE(server_info); + return NT_STATUS_LOGON_FAILURE; + } + + sanitized_username = talloc_alpha_strcpy(talloc_tos(), + nt_username, + SAFE_NETBIOS_CHARS "$"); + if (sanitized_username == NULL) { + TALLOC_FREE(server_info); + return NT_STATUS_NO_MEMORY; + } + + set_current_user_info(sanitized_username, + server_info->unix_name, + server_info->info3->base.logon_domain.string); + TALLOC_FREE(sanitized_username); + + /* This is the point at which, if the login was successful, that + the SAM Local Security Authority should record that the user is + logged in to the domain. */ + + switch (r->in.validation_level) { + case 2: + status = serverinfo_to_SamInfo2(server_info, + r->out.validation->sam2); + break; + case 3: + status = serverinfo_to_SamInfo3(server_info, + r->out.validation->sam3); + break; + case 6: { + /* Only allow this if the pipe is protected. */ + if (auth_level < DCERPC_AUTH_LEVEL_PRIVACY) { + DEBUG(0,("netr_Validation6: client %s not using privacy for netlogon\n", + get_remote_machine_name())); + status = NT_STATUS_INVALID_PARAMETER; + break; + } + + status = serverinfo_to_SamInfo6(server_info, + r->out.validation->sam6); + break; + } + } + + TALLOC_FREE(server_info); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = netlogon_creds_encrypt_samlogon_validation(creds, + r->in.validation_level, + r->out.validation); + + return status; +} + +/**************************************************************** + _netr_LogonSamLogonWithFlags +****************************************************************/ + +NTSTATUS _netr_LogonSamLogonWithFlags(struct pipes_struct *p, + struct netr_LogonSamLogonWithFlags *r) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds; + struct netr_LogonSamLogonEx r2; + struct netr_Authenticator return_authenticator; + + *r->out.authoritative = true; + + r2.in.server_name = r->in.server_name; + r2.in.computer_name = r->in.computer_name; + r2.in.logon_level = r->in.logon_level; + r2.in.logon = r->in.logon; + r2.in.validation_level = r->in.validation_level; + r2.in.flags = r->in.flags; + r2.out.validation = r->out.validation; + r2.out.authoritative = r->out.authoritative; + r2.out.flags = r->out.flags; + + status = _netr_LogonSamLogon_check(&r2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + &return_authenticator, + &creds); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = _netr_LogonSamLogon_base(p, &r2, creds); + + *r->out.return_authenticator = return_authenticator; + + return status; +} + +/************************************************************************* + _netr_LogonSamLogon + *************************************************************************/ + +NTSTATUS _netr_LogonSamLogon(struct pipes_struct *p, + struct netr_LogonSamLogon *r) +{ + NTSTATUS status; + struct netr_LogonSamLogonWithFlags r2; + uint32_t flags = 0; + + r2.in.server_name = r->in.server_name; + r2.in.computer_name = r->in.computer_name; + r2.in.credential = r->in.credential; + r2.in.logon_level = r->in.logon_level; + r2.in.logon = r->in.logon; + r2.in.validation_level = r->in.validation_level; + r2.in.return_authenticator = r->in.return_authenticator; + r2.in.flags = &flags; + r2.out.validation = r->out.validation; + r2.out.authoritative = r->out.authoritative; + r2.out.flags = &flags; + r2.out.return_authenticator = r->out.return_authenticator; + + status = _netr_LogonSamLogonWithFlags(p, &r2); + + return status; +} + +/************************************************************************* + _netr_LogonSamLogonEx + - no credential chaining. Map into net sam logon. + *************************************************************************/ + +NTSTATUS _netr_LogonSamLogonEx(struct pipes_struct *p, + struct netr_LogonSamLogonEx *r) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds = NULL; + struct loadparm_context *lp_ctx = p->dce_call->conn->dce_ctx->lp_ctx; + + *r->out.authoritative = true; + + status = _netr_LogonSamLogon_check(r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + become_root(); + status = schannel_get_creds_state(p->mem_ctx, lp_ctx, + r->in.computer_name, &creds); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = _netr_LogonSamLogon_base(p, r, creds); + TALLOC_FREE(creds); + + return status; +} + +/************************************************************************* + _ds_enum_dom_trusts + *************************************************************************/ +#if 0 /* JERRY -- not correct */ + NTSTATUS _ds_enum_dom_trusts(struct pipes_struct *p, DS_Q_ENUM_DOM_TRUSTS *q_u, + DS_R_ENUM_DOM_TRUSTS *r_u) +{ + NTSTATUS status = NT_STATUS_OK; + + /* TODO: According to MSDN, the can only be executed against a + DC or domain member running Windows 2000 or later. Need + to test against a standalone 2k server and see what it + does. A windows 2000 DC includes its own domain in the + list. --jerry */ + + return status; +} +#endif /* JERRY */ + + +/**************************************************************** +****************************************************************/ + +WERROR _netr_LogonUasLogon(struct pipes_struct *p, + struct netr_LogonUasLogon *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_LogonUasLogoff(struct pipes_struct *p, + struct netr_LogonUasLogoff *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_DatabaseDeltas(struct pipes_struct *p, + struct netr_DatabaseDeltas *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_DatabaseSync(struct pipes_struct *p, + struct netr_DatabaseSync *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_AccountDeltas(struct pipes_struct *p, + struct netr_AccountDeltas *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_AccountSync(struct pipes_struct *p, + struct netr_AccountSync *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +static bool wb_getdcname(TALLOC_CTX *mem_ctx, + const char *domain, + const char **dcname, + uint32_t flags, + WERROR *werr) +{ + wbcErr result; + struct wbcDomainControllerInfo *dc_info = NULL; + + result = wbcLookupDomainController(domain, + flags, + &dc_info); + switch (result) { + case WBC_ERR_SUCCESS: + break; + case WBC_ERR_WINBIND_NOT_AVAILABLE: + return false; + case WBC_ERR_DOMAIN_NOT_FOUND: + *werr = WERR_NO_SUCH_DOMAIN; + return true; + default: + *werr = WERR_DOMAIN_CONTROLLER_NOT_FOUND; + return true; + } + + *dcname = talloc_strdup(mem_ctx, dc_info->dc_name); + wbcFreeMemory(dc_info); + if (!*dcname) { + *werr = WERR_NOT_ENOUGH_MEMORY; + return false; + } + + *werr = WERR_OK; + + return true; +} + +/**************************************************************** + _netr_GetDcName +****************************************************************/ + +WERROR _netr_GetDcName(struct pipes_struct *p, + struct netr_GetDcName *r) +{ + NTSTATUS status; + WERROR werr; + uint32_t flags; + struct netr_DsRGetDCNameInfo *info; + bool ret; + + ret = wb_getdcname(p->mem_ctx, + r->in.domainname, + r->out.dcname, + WBC_LOOKUP_DC_IS_FLAT_NAME | + WBC_LOOKUP_DC_RETURN_FLAT_NAME | + WBC_LOOKUP_DC_PDC_REQUIRED, + &werr); + if (ret == true) { + return werr; + } + + flags = DS_PDC_REQUIRED | DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME; + + status = dsgetdcname(p->mem_ctx, + p->msg_ctx, + r->in.domainname, + NULL, + NULL, + flags, + &info); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + *r->out.dcname = talloc_strdup(p->mem_ctx, info->dc_unc); + talloc_free(info); + if (!*r->out.dcname) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/**************************************************************** + _netr_GetAnyDCName +****************************************************************/ + +WERROR _netr_GetAnyDCName(struct pipes_struct *p, + struct netr_GetAnyDCName *r) +{ + NTSTATUS status; + WERROR werr; + uint32_t flags; + struct netr_DsRGetDCNameInfo *info; + bool ret; + + ret = wb_getdcname(p->mem_ctx, + r->in.domainname, + r->out.dcname, + WBC_LOOKUP_DC_IS_FLAT_NAME | + WBC_LOOKUP_DC_RETURN_FLAT_NAME, + &werr); + if (ret == true) { + return werr; + } + + flags = DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME; + + status = dsgetdcname(p->mem_ctx, + p->msg_ctx, + r->in.domainname, + NULL, + NULL, + flags, + &info); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + *r->out.dcname = talloc_strdup(p->mem_ctx, info->dc_unc); + talloc_free(info); + if (!*r->out.dcname) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_DatabaseSync2(struct pipes_struct *p, + struct netr_DatabaseSync2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_DatabaseRedo(struct pipes_struct *p, + struct netr_DatabaseRedo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRGetDCName(struct pipes_struct *p, + struct netr_DsRGetDCName *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_LogonGetCapabilities(struct pipes_struct *p, + struct netr_LogonGetCapabilities *r) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS status; + + switch (r->in.query_level) { + case 1: + break; + case 2: + /* + * Until we know the details behind KB5028166 + * just return DCERPC_NCA_S_FAULT_INVALID_TAG + * like an unpatched Windows Server. + */ + FALL_THROUGH; + default: + /* + * There would not be a way to marshall the + * the response. Which would mean our final + * ndr_push would fail an we would return + * an RPC-level fault with DCERPC_FAULT_BAD_STUB_DATA. + * + * But it's important to match a Windows server + * especially before KB5028166, see also our bug #15418 + * Otherwise Windows client would stop talking to us. + */ + p->fault_state = DCERPC_NCA_S_FAULT_INVALID_TAG; + return NT_STATUS_NOT_SUPPORTED; + } + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.capabilities->server_capabilities = creds->negotiate_flags; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_NETRLOGONSETSERVICEBITS(struct pipes_struct *p, + struct netr_NETRLOGONSETSERVICEBITS *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_LogonGetTrustRid(struct pipes_struct *p, + struct netr_LogonGetTrustRid *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_NETRLOGONCOMPUTESERVERDIGEST(struct pipes_struct *p, + struct netr_NETRLOGONCOMPUTESERVERDIGEST *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_NETRLOGONCOMPUTECLIENTDIGEST(struct pipes_struct *p, + struct netr_NETRLOGONCOMPUTECLIENTDIGEST *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRGetDCNameEx(struct pipes_struct *p, + struct netr_DsRGetDCNameEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRGetSiteName(struct pipes_struct *p, + struct netr_DsRGetSiteName *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_LogonGetDomainInfo(struct pipes_struct *p, + struct netr_LogonGetDomainInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_ServerPasswordGet(struct pipes_struct *p, + struct netr_ServerPasswordGet *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_NetrLogonSendToSam(struct pipes_struct *p, + struct netr_NetrLogonSendToSam *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRAddressToSitenamesW(struct pipes_struct *p, + struct netr_DsRAddressToSitenamesW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRGetDCNameEx2(struct pipes_struct *p, + struct netr_DsRGetDCNameEx2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN(struct pipes_struct *p, + struct netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_NetrEnumerateTrustedDomainsEx(struct pipes_struct *p, + struct netr_NetrEnumerateTrustedDomainsEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRAddressToSitenamesExW(struct pipes_struct *p, + struct netr_DsRAddressToSitenamesExW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsrGetDcSiteCoverageW(struct pipes_struct *p, + struct netr_DsrGetDcSiteCoverageW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsrEnumerateDomainTrusts(struct pipes_struct *p, + struct netr_DsrEnumerateDomainTrusts *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsrDeregisterDNSHostRecords(struct pipes_struct *p, + struct netr_DsrDeregisterDNSHostRecords *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_ServerTrustPasswordsGet(struct pipes_struct *p, + struct netr_ServerTrustPasswordsGet *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS fill_forest_trust_array(TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation *info) +{ + struct lsa_ForestTrustRecord *e; + struct pdb_domain_info *dom_info; + struct lsa_ForestTrustDomainInfo *domain_info; + char **upn_suffixes = NULL; + uint32_t num_suffixes = 0; + uint32_t i = 0; + NTSTATUS status; + + dom_info = pdb_get_domain_info(mem_ctx); + if (dom_info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + info->count = 2; + + become_root(); + status = pdb_enum_upn_suffixes(info, &num_suffixes, &upn_suffixes); + unbecome_root(); + if (NT_STATUS_IS_OK(status) && (num_suffixes > 0)) { + info->count += num_suffixes; + } + + info->entries = talloc_array(info, struct lsa_ForestTrustRecord *, info->count); + if (info->entries == NULL) { + return NT_STATUS_NO_MEMORY; + } + + e = talloc(info, struct lsa_ForestTrustRecord); + if (e == NULL) { + return NT_STATUS_NO_MEMORY; + } + + e->flags = 0; + e->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME; + e->time = 0; /* so far always 0 in trces. */ + e->forest_trust_data.top_level_name.string = talloc_steal(info, + dom_info->dns_forest); + + info->entries[0] = e; + + if (num_suffixes > 0) { + for (i = 0; i < num_suffixes ; i++) { + e = talloc(info, struct lsa_ForestTrustRecord); + if (e == NULL) { + return NT_STATUS_NO_MEMORY; + } + + e->flags = 0; + e->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME; + e->time = 0; /* so far always 0 in traces. */ + e->forest_trust_data.top_level_name.string = upn_suffixes[i]; + info->entries[1 + i] = e; + } + } + + e = talloc(info, struct lsa_ForestTrustRecord); + if (e == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* TODO: check if disabled and set flags accordingly */ + e->flags = 0; + e->type = LSA_FOREST_TRUST_DOMAIN_INFO; + e->time = 0; /* so far always 0 in traces. */ + + domain_info = &e->forest_trust_data.domain_info; + domain_info->domain_sid = dom_sid_dup(info, &dom_info->sid); + + domain_info->dns_domain_name.string = talloc_steal(info, + dom_info->dns_domain); + domain_info->netbios_domain_name.string = talloc_steal(info, + dom_info->name); + + info->entries[info->count - 1] = e; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +WERROR _netr_DsRGetForestTrustInformation(struct pipes_struct *p, + struct netr_DsRGetForestTrustInformation *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + struct lsa_ForestTrustInformation *info, **info_ptr; + enum security_user_level security_level; + + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_USER) { + return WERR_ACCESS_DENIED; + } + + if (r->in.flags & (~DS_GFTI_UPDATE_TDO)) { + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_INVALID_FLAGS; + } + + if ((r->in.flags & DS_GFTI_UPDATE_TDO) && (lp_server_role() != ROLE_DOMAIN_PDC)) { + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NERR_NOTPRIMARY; + } + + if ((r->in.trusted_domain_name == NULL) && (r->in.flags & DS_GFTI_UPDATE_TDO)) { + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_INVALID_PARAMETER; + } + + /* retrieve forest trust information and stop further processing */ + if (r->in.trusted_domain_name == NULL) { + info_ptr = talloc(p->mem_ctx, struct lsa_ForestTrustInformation *); + if (info_ptr == NULL) { + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return WERR_NOT_ENOUGH_MEMORY; + } + info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation); + if (info == NULL) { + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Fill forest trust information and expand UPN suffixes list */ + status = fill_forest_trust_array(p->mem_ctx, info); + if (!NT_STATUS_IS_OK(status)) { + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + return WERR_NOT_ENOUGH_MEMORY; + } + + *info_ptr = info; + r->out.forest_trust_info = info_ptr; + + return WERR_OK; + + } + + /* TODO: implement remaining parts of DsrGetForestTrustInformation (opnum 43) + * when trusted_domain_name is not NULL */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _netr_GetForestTrustInformation +****************************************************************/ + +NTSTATUS _netr_GetForestTrustInformation(struct pipes_struct *p, + struct netr_GetForestTrustInformation *r) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds; + struct lsa_ForestTrustInformation *info, **info_ptr; + + /* TODO: check server name */ + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) && + (creds->secure_channel_type != SEC_CHAN_DOMAIN)) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + info_ptr = talloc(p->mem_ctx, struct lsa_ForestTrustInformation *); + if (!info_ptr) { + return NT_STATUS_NO_MEMORY; + } + info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + /* Fill forest trust information, do expand UPN suffixes list */ + status = fill_forest_trust_array(p->mem_ctx, info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *info_ptr = info; + r->out.forest_trust_info = info_ptr; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static NTSTATUS get_password_from_trustAuth(TALLOC_CTX *mem_ctx, + const DATA_BLOB *trustAuth_blob, + struct netlogon_creds_CredentialState *creds, + struct samr_Password *current_pw_enc, + struct samr_Password *previous_pw_enc) +{ + enum ndr_err_code ndr_err; + struct trustAuthInOutBlob trustAuth; + NTSTATUS status; + + ndr_err = ndr_pull_struct_blob_all(trustAuth_blob, mem_ctx, &trustAuth, + (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (trustAuth.count != 0 && trustAuth.current.count != 0 && + trustAuth.current.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) { + mdfour(current_pw_enc->hash, + trustAuth.current.array[0].AuthInfo.clear.password, + trustAuth.current.array[0].AuthInfo.clear.size); + status = netlogon_creds_des_encrypt(creds, current_pw_enc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + return NT_STATUS_UNSUCCESSFUL; + } + + + if (trustAuth.previous.count != 0 && + trustAuth.previous.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) { + mdfour(previous_pw_enc->hash, + trustAuth.previous.array[0].AuthInfo.clear.password, + trustAuth.previous.array[0].AuthInfo.clear.size); + status = netlogon_creds_des_encrypt(creds, previous_pw_enc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + ZERO_STRUCTP(previous_pw_enc); + } + + return NT_STATUS_OK; +} + +/**************************************************************** + _netr_ServerGetTrustInfo +****************************************************************/ + +NTSTATUS _netr_ServerGetTrustInfo(struct pipes_struct *p, + struct netr_ServerGetTrustInfo *r) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds; + char *account_name; + size_t account_name_last; + bool trusted; + struct netr_TrustInfo *trust_info; + struct pdb_trusted_domain *td; + + /* TODO: check server name */ + + become_root(); + status = dcesrv_netr_creds_server_step_check(p->dce_call, + p->mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + account_name = talloc_strdup(p->mem_ctx, r->in.account_name); + if (account_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + account_name_last = strlen(account_name); + if (account_name_last == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + account_name_last--; + if (account_name[account_name_last] == '.') { + account_name[account_name_last] = '\0'; + } + + if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) && + (creds->secure_channel_type != SEC_CHAN_DOMAIN)) { + trusted = false; + } else { + trusted = true; + } + + + if (trusted) { + account_name_last = strlen(account_name); + if (account_name_last == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + account_name_last--; + if (account_name[account_name_last] == '$') { + account_name[account_name_last] = '\0'; + } + + status = pdb_get_trusted_domain(p->mem_ctx, account_name, &td); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (r->out.trust_info != NULL) { + trust_info = talloc_zero(p->mem_ctx, struct netr_TrustInfo); + if (trust_info == NULL) { + return NT_STATUS_NO_MEMORY; + } + trust_info->count = 1; + + trust_info->data = talloc_array(trust_info, uint32_t, 1); + if (trust_info->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + trust_info->data[0] = td->trust_attributes; + + *r->out.trust_info = trust_info; + } + + if (td->trust_auth_incoming.data == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = get_password_from_trustAuth(p->mem_ctx, + &td->trust_auth_incoming, + creds, + r->out.new_owf_password, + r->out.old_owf_password); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + } else { +/* TODO: look for machine password */ + ZERO_STRUCTP(r->out.new_owf_password); + ZERO_STRUCTP(r->out.old_owf_password); + + return NT_STATUS_NOT_IMPLEMENTED; + } + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_Unused47(struct pipes_struct *p, + struct netr_Unused47 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _netr_DsrUpdateReadOnlyServerDnsRecords(struct pipes_struct *p, + struct netr_DsrUpdateReadOnlyServerDnsRecords *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + * Define the bind function that will be used by ndr_netlogon_scompat.c, + * included at the bottom of this file. + */ +#define DCESRV_INTERFACE_NETLOGON_BIND(context, iface) \ + dcesrv_interface_netlogon_bind(context, iface) + +static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx; + int schannel = lpcfg_server_schannel(lp_ctx); + bool schannel_global_required = (schannel == true); + bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx); + static bool warned_global_schannel_once = false; + static bool warned_global_seal_once = false; + + if (!schannel_global_required && !warned_global_schannel_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2020-1472(ZeroLogon): " + "Please configure 'server schannel = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=14497\n"); + warned_global_schannel_once = true; + } + + if (!global_require_seal && !warned_global_seal_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023 (and others): " + "Please configure 'server schannel require seal = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_seal_once = true; + } + + return NT_STATUS_OK; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_netlogon_scompat.c" diff --git a/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c b/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c new file mode 100644 index 0000000..cfa2336 --- /dev/null +++ b/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c @@ -0,0 +1,810 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * + * Copyright (C) Gerald (Jerry) Carter 2005. + * Copyright (C) Guenther Deschner 2008,2009. + * + * 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 "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_ntsvcs.h" +#include "librpc/gen_ndr/ndr_ntsvcs_scompat.h" +#include "services/svc_winreg_glue.h" +#include "../libcli/registry/util_reg.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/******************************************************************** +********************************************************************/ + +static char* get_device_path(TALLOC_CTX *mem_ctx, const char *device ) +{ + return talloc_asprintf(mem_ctx, "ROOT\\Legacy_%s\\0000", device); +} + +/******************************************************************** +********************************************************************/ + +WERROR _PNP_GetVersion(struct pipes_struct *p, + struct PNP_GetVersion *r) +{ + *r->out.version = 0x0400; /* no idea what this means */ + + return WERR_OK; +} + +/******************************************************************** +********************************************************************/ + +WERROR _PNP_GetDeviceListSize(struct pipes_struct *p, + struct PNP_GetDeviceListSize *r) +{ + char *devicepath; + + if ((r->in.flags & CM_GETIDLIST_FILTER_SERVICE) && + (!r->in.devicename)) { + return WERR_CM_INVALID_POINTER; + } + + if (!(devicepath = get_device_path(p->mem_ctx, r->in.devicename))) { + return WERR_NOT_ENOUGH_MEMORY; + } + + *r->out.size = strlen(devicepath) + 2; + + TALLOC_FREE(devicepath); + + return WERR_OK; +} + +/**************************************************************** + _PNP_GetDeviceList +****************************************************************/ + +WERROR _PNP_GetDeviceList(struct pipes_struct *p, + struct PNP_GetDeviceList *r) +{ + char *devicepath; + uint32_t size = 0; + const char **multi_sz = NULL; + DATA_BLOB blob; + + if ((r->in.flags & CM_GETIDLIST_FILTER_SERVICE) && + (!r->in.filter)) { + return WERR_CM_INVALID_POINTER; + } + + if (!(devicepath = get_device_path(p->mem_ctx, r->in.filter))) { + return WERR_NOT_ENOUGH_MEMORY; + } + + size = strlen(devicepath) + 2; + + if (*r->in.length < size) { + return WERR_CM_BUFFER_SMALL; + } + + multi_sz = talloc_zero_array(p->mem_ctx, const char *, 2); + if (!multi_sz) { + return WERR_NOT_ENOUGH_MEMORY; + } + + multi_sz[0] = devicepath; + + if (!push_reg_multi_sz(multi_sz, &blob, multi_sz)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (*r->in.length < blob.length/2) { + return WERR_CM_BUFFER_SMALL; + } + + memcpy(r->out.buffer, blob.data, blob.length); + + return WERR_OK; +} + +/******************************************************************** +_PNP_GetDeviceRegProp +********************************************************************/ + +WERROR _PNP_GetDeviceRegProp(struct pipes_struct *p, + struct PNP_GetDeviceRegProp *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + char *ptr; + const char *result; + DATA_BLOB blob; + TALLOC_CTX *mem_ctx = NULL; + + switch( r->in.property ) { + case DEV_REGPROP_DESC: + + /* just parse the service name from the device path and then + lookup the display name */ + if ( !(ptr = strrchr_m( r->in.devicepath, '\\' )) ) + return WERR_GEN_FAILURE; + *ptr = '\0'; + + if ( !(ptr = strrchr_m( r->in.devicepath, '_' )) ) + return WERR_GEN_FAILURE; + ptr++; + + mem_ctx = talloc_stackframe(); + + result = svcctl_lookup_dispname(mem_ctx, + p->msg_ctx, + session_info, + ptr); + if (result == NULL) { + return WERR_GEN_FAILURE; + } + + if (!push_reg_sz(mem_ctx, &blob, result)) { + talloc_free(mem_ctx); + return WERR_GEN_FAILURE; + } + + if (*r->in.buffer_size < blob.length) { + *r->out.needed = blob.length; + *r->out.buffer_size = 0; + talloc_free(mem_ctx); + return WERR_CM_BUFFER_SMALL; + } + + r->out.buffer = (uint8_t *)talloc_memdup(p->mem_ctx, blob.data, blob.length); + talloc_free(mem_ctx); + if (!r->out.buffer) { + return WERR_NOT_ENOUGH_MEMORY; + } + + *r->out.reg_data_type = REG_SZ; /* always 1...tested using a remove device manager connection */ + *r->out.buffer_size = blob.length; + *r->out.needed = blob.length; + + break; + + default: + *r->out.reg_data_type = 0x00437c98; /* ??? */ + return WERR_CM_NO_SUCH_VALUE; + } + + return WERR_OK; +} + +/******************************************************************** +********************************************************************/ + +WERROR _PNP_ValidateDeviceInstance(struct pipes_struct *p, + struct PNP_ValidateDeviceInstance *r) +{ + /* whatever dude */ + return WERR_OK; +} + +/******************************************************************** +********************************************************************/ + +WERROR _PNP_GetHwProfInfo(struct pipes_struct *p, + struct PNP_GetHwProfInfo *r) +{ + /* steal the incoming buffer */ + + r->out.info = r->in.info; + + /* Take the 5th Ammentment */ + + return WERR_CM_NO_MORE_HW_PROFILES; +} + +/******************************************************************** +********************************************************************/ + +WERROR _PNP_HwProfFlags(struct pipes_struct *p, + struct PNP_HwProfFlags *r) +{ + /* just nod your head */ + + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_Disconnect(struct pipes_struct *p, + struct PNP_Disconnect *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_Connect(struct pipes_struct *p, + struct PNP_Connect *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetGlobalState(struct pipes_struct *p, + struct PNP_GetGlobalState *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_InitDetection(struct pipes_struct *p, + struct PNP_InitDetection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_ReportLogOn(struct pipes_struct *p, + struct PNP_ReportLogOn *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetRootDeviceInstance(struct pipes_struct *p, + struct PNP_GetRootDeviceInstance *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetRelatedDeviceInstance(struct pipes_struct *p, + struct PNP_GetRelatedDeviceInstance *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_EnumerateSubKeys(struct pipes_struct *p, + struct PNP_EnumerateSubKeys *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetDepth(struct pipes_struct *p, + struct PNP_GetDepth *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_SetDeviceRegProp(struct pipes_struct *p, + struct PNP_SetDeviceRegProp *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetClassInstance(struct pipes_struct *p, + struct PNP_GetClassInstance *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_CreateKey(struct pipes_struct *p, + struct PNP_CreateKey *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_DeleteRegistryKey(struct pipes_struct *p, + struct PNP_DeleteRegistryKey *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetClassCount(struct pipes_struct *p, + struct PNP_GetClassCount *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetClassName(struct pipes_struct *p, + struct PNP_GetClassName *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_DeleteClassKey(struct pipes_struct *p, + struct PNP_DeleteClassKey *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetInterfaceDeviceAlias(struct pipes_struct *p, + struct PNP_GetInterfaceDeviceAlias *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetInterfaceDeviceList(struct pipes_struct *p, + struct PNP_GetInterfaceDeviceList *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetInterfaceDeviceListSize(struct pipes_struct *p, + struct PNP_GetInterfaceDeviceListSize *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RegisterDeviceClassAssociation(struct pipes_struct *p, + struct PNP_RegisterDeviceClassAssociation *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_UnregisterDeviceClassAssociation(struct pipes_struct *p, + struct PNP_UnregisterDeviceClassAssociation *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetClassRegProp(struct pipes_struct *p, + struct PNP_GetClassRegProp *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_SetClassRegProp(struct pipes_struct *p, + struct PNP_SetClassRegProp *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_CreateDevInst(struct pipes_struct *p, + struct PNP_CreateDevInst *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_DeviceInstanceAction(struct pipes_struct *p, + struct PNP_DeviceInstanceAction *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetDeviceStatus(struct pipes_struct *p, + struct PNP_GetDeviceStatus *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_SetDeviceProblem(struct pipes_struct *p, + struct PNP_SetDeviceProblem *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_DisableDevInst(struct pipes_struct *p, + struct PNP_DisableDevInst *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_UninstallDevInst(struct pipes_struct *p, + struct PNP_UninstallDevInst *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_AddID(struct pipes_struct *p, + struct PNP_AddID *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RegisterDriver(struct pipes_struct *p, + struct PNP_RegisterDriver *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_QueryRemove(struct pipes_struct *p, + struct PNP_QueryRemove *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RequestDeviceEject(struct pipes_struct *p, + struct PNP_RequestDeviceEject *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_IsDockStationPresent(struct pipes_struct *p, + struct PNP_IsDockStationPresent *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RequestEjectPC(struct pipes_struct *p, + struct PNP_RequestEjectPC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_AddEmptyLogConf(struct pipes_struct *p, + struct PNP_AddEmptyLogConf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_FreeLogConf(struct pipes_struct *p, + struct PNP_FreeLogConf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetFirstLogConf(struct pipes_struct *p, + struct PNP_GetFirstLogConf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetNextLogConf(struct pipes_struct *p, + struct PNP_GetNextLogConf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetLogConfPriority(struct pipes_struct *p, + struct PNP_GetLogConfPriority *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_AddResDes(struct pipes_struct *p, + struct PNP_AddResDes *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_FreeResDes(struct pipes_struct *p, + struct PNP_FreeResDes *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetNextResDes(struct pipes_struct *p, + struct PNP_GetNextResDes *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetResDesData(struct pipes_struct *p, + struct PNP_GetResDesData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetResDesDataSize(struct pipes_struct *p, + struct PNP_GetResDesDataSize *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_ModifyResDes(struct pipes_struct *p, + struct PNP_ModifyResDes *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_DetectResourceLimit(struct pipes_struct *p, + struct PNP_DetectResourceLimit *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_QueryResConfList(struct pipes_struct *p, + struct PNP_QueryResConfList *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_SetHwProf(struct pipes_struct *p, + struct PNP_SetHwProf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_QueryArbitratorFreeData(struct pipes_struct *p, + struct PNP_QueryArbitratorFreeData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_QueryArbitratorFreeSize(struct pipes_struct *p, + struct PNP_QueryArbitratorFreeSize *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RunDetection(struct pipes_struct *p, + struct PNP_RunDetection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_RegisterNotification(struct pipes_struct *p, + struct PNP_RegisterNotification *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_UnregisterNotification(struct pipes_struct *p, + struct PNP_UnregisterNotification *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetCustomDevProp(struct pipes_struct *p, + struct PNP_GetCustomDevProp *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetVersionInternal(struct pipes_struct *p, + struct PNP_GetVersionInternal *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetBlockedDriverInfo(struct pipes_struct *p, + struct PNP_GetBlockedDriverInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** +****************************************************************/ + +WERROR _PNP_GetServerSideDeviceInstallFlags(struct pipes_struct *p, + struct PNP_GetServerSideDeviceInstallFlags *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_ntsvcs_scompat.c" diff --git a/source3/rpc_server/rpc_config.c b/source3/rpc_server/rpc_config.c new file mode 100644 index 0000000..af167d8 --- /dev/null +++ b/source3/rpc_server/rpc_config.c @@ -0,0 +1,77 @@ +/* + Unix SMB/Netbios implementation. + Generic infrastructure for RPC Daemons + Copyright (C) Simo Sorce 2011 + Copyright (C) Andreas Schneider 2011 + + 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 "rpc_server/rpc_config.h" +#include "rpc_server/rpc_server.h" +#include "lib/param/param.h" +#include "librpc/rpc/dcesrv_core.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static struct dcesrv_context_callbacks srv_callbacks = { + .log.successful_authz = dcesrv_log_successful_authz, + .auth.gensec_prepare = dcesrv_auth_gensec_prepare, + .auth.become_root = become_root, + .auth.unbecome_root = unbecome_root, + .assoc_group.find = dcesrv_assoc_group_find, +}; + +static struct dcesrv_context *global_dcesrv_ctx = NULL; + +struct dcesrv_context *global_dcesrv_context(void) +{ + NTSTATUS status; + + if (global_dcesrv_ctx == NULL) { + struct loadparm_context *lp_ctx = NULL; + + DBG_INFO("Initializing DCE/RPC server context\n"); + + lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + smb_panic("No memory"); + } + + /* + * Note we MUST use the NULL context here, not the + * autofree context, to avoid side effects in forked + * children exiting. + */ + status = dcesrv_init_context(global_event_context(), + lp_ctx, + &srv_callbacks, + &global_dcesrv_ctx); + if (!NT_STATUS_IS_OK(status)) { + smb_panic("Failed to init DCE/RPC context"); + } + + talloc_steal(global_dcesrv_ctx, lp_ctx); + } + + return global_dcesrv_ctx; +} + +void global_dcesrv_context_free(void) +{ + TALLOC_FREE(global_dcesrv_ctx); +} diff --git a/source3/rpc_server/rpc_config.h b/source3/rpc_server/rpc_config.h new file mode 100644 index 0000000..40d5855 --- /dev/null +++ b/source3/rpc_server/rpc_config.h @@ -0,0 +1,30 @@ +/* + * Unix SMB/CIFS implementation. + * + * SMBD RPC service config + * + * Copyright (c) 2011 Andreas Schneider <asn@samba.org> + * Copyright (C) 2011 Simo Sorce <idra@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/>. + */ + +#ifndef _RPC_CONFIG_H +#define _RPC_CONFIG_H + +struct dcesrv_context; +struct dcesrv_context *global_dcesrv_context(void); +void global_dcesrv_context_free(void); + +#endif /* _RPC_CONFIG_H */ diff --git a/source3/rpc_server/rpc_handles.c b/source3/rpc_server/rpc_handles.c new file mode 100644 index 0000000..928b10e --- /dev/null +++ b/source3/rpc_server/rpc_handles.c @@ -0,0 +1,233 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Jeremy Allison 2001. + * + * 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 "system/passwd.h" /* uid_wrapper */ +#include "../librpc/gen_ndr/ndr_lsa.h" +#include "../librpc/gen_ndr/ndr_samr.h" +#include "auth.h" +#include "rpc_server/rpc_pipes.h" +#include "../libcli/security/security.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/ndr/ndr_table.h" +#include "librpc/rpc/dcesrv_core.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static size_t num_handles = 0; + +bool check_open_pipes(void) +{ + if (num_handles > 0) { + return true; + } + + return false; +} + +size_t num_pipe_handles(void) +{ + return num_handles; +} + +/**************************************************************************** + find first available policy slot. creates a policy handle for you. + + If "data_ptr" is given, this must be a talloc'ed object, create_policy_hnd + talloc_moves this into the handle. If the policy_hnd is closed, + data_ptr is TALLOC_FREE()'ed +****************************************************************************/ + +struct hnd_cnt { + bool _dummy; +}; + +static int hnd_cnt_destructor(struct hnd_cnt *cnt) +{ + num_handles--; + return 0; +} + +void *create_policy_hnd(struct pipes_struct *p, + struct policy_handle *hnd, + uint8_t handle_type, + void *data_ptr) +{ + struct dcesrv_handle *rpc_hnd = NULL; + struct hnd_cnt *cnt = NULL; + + rpc_hnd = dcesrv_handle_create(p->dce_call, handle_type); + if (rpc_hnd == NULL) { + return NULL; + } + + cnt = talloc_zero(rpc_hnd, struct hnd_cnt); + if (cnt == NULL) { + TALLOC_FREE(rpc_hnd); + return NULL; + } + talloc_set_destructor(cnt, hnd_cnt_destructor); + + if (data_ptr != NULL) { + rpc_hnd->data = talloc_move(rpc_hnd, &data_ptr); + } + + *hnd = rpc_hnd->wire_handle; + + num_handles++; + + return rpc_hnd; +} + +/**************************************************************************** + find policy by handle - internal version. +****************************************************************************/ + +static struct dcesrv_handle *find_policy_by_hnd_internal( + struct pipes_struct *p, + const struct policy_handle *hnd, + uint8_t handle_type, + void **data_p) +{ + struct dcesrv_handle *h = NULL; + + if (data_p) { + *data_p = NULL; + } + + /* + * Do not pass an empty policy_handle to dcesrv_handle_lookup() or + * it will create a new empty handle + */ + if (ndr_policy_handle_empty(hnd)) { + p->fault_state = DCERPC_FAULT_CONTEXT_MISMATCH; + return NULL; + } + + /* + * Do not pass handle_type to avoid setting the fault_state in the + * pipes_struct if the handle type does not match + */ + h = dcesrv_handle_lookup(p->dce_call, hnd, DCESRV_HANDLE_ANY); + if (h == NULL) { + p->fault_state = DCERPC_FAULT_CONTEXT_MISMATCH; + return NULL; + } + + if (handle_type != DCESRV_HANDLE_ANY && + h->wire_handle.handle_type != handle_type) { + /* Just return NULL, do not set a fault + * state in pipes_struct */ + return NULL; + } + + if (data_p) { + *data_p = h->data; + } + + return h; +} + +/**************************************************************************** + find policy by handle +****************************************************************************/ + +void *_find_policy_by_hnd(struct pipes_struct *p, + const struct policy_handle *hnd, + uint8_t handle_type, + NTSTATUS *pstatus) +{ + struct dcesrv_handle *rpc_hnd = NULL; + void *data = NULL; + + rpc_hnd = find_policy_by_hnd_internal(p, hnd, handle_type, &data); + if (rpc_hnd == NULL) { + *pstatus = NT_STATUS_INVALID_HANDLE; + return NULL; + } + + *pstatus = NT_STATUS_OK; + return data; +} + +/**************************************************************************** + Close a policy. +****************************************************************************/ + +bool close_policy_hnd(struct pipes_struct *p, + struct policy_handle *hnd) +{ + struct dcesrv_handle *rpc_hnd = NULL; + + rpc_hnd = find_policy_by_hnd_internal(p, hnd, DCESRV_HANDLE_ANY, NULL); + if (rpc_hnd == NULL) { + DEBUG(3, ("Error closing policy (policy not found)\n")); + return false; + } + + TALLOC_FREE(rpc_hnd); + + return true; +} + +/******************************************************************* +Shall we allow access to this rpc? Currently this function +implements the 'restrict anonymous' setting by denying access to +anonymous users if the restrict anonymous level is > 0. Further work +will be checking a security descriptor to determine whether a user +token has enough access to access the pipe. +********************************************************************/ + +bool pipe_access_check(struct pipes_struct *p) +{ + /* Don't let anonymous users access this RPC if restrict + anonymous > 0 */ + + if (lp_restrict_anonymous() > 0) { + + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_auth *auth_state = dce_call->auth_state; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + struct auth_session_info *session_info = NULL; + enum security_user_level user_level; + + if (!auth_state->auth_finished) { + return false; + } + + dcesrv_call_auth_info(dce_call, &auth_type, NULL); + + /* schannel, so we must be ok */ + if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + return True; + } + + session_info = dcesrv_call_session_info(dce_call); + user_level = security_session_user_level(session_info, NULL); + + if (user_level < SECURITY_USER) { + return False; + } + } + + return True; +} diff --git a/source3/rpc_server/rpc_host.c b/source3/rpc_server/rpc_host.c new file mode 100644 index 0000000..1e891b4 --- /dev/null +++ b/source3/rpc_server/rpc_host.c @@ -0,0 +1,2984 @@ +/* + * RPC host + * + * Implements samba-dcerpcd service. + * + * 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/>. + */ + +/* + * This binary has two usage modes: + * + * In the normal case when invoked from smbd or winbind it is given a + * directory to scan via --libexec-rpcds and will invoke on demand any + * binaries it finds there starting with rpcd_ when a named pipe + * connection is requested. + * + * In the second mode it can be started explicitly from system startup + * scripts. + * + * When Samba is set up as an Active Directory Domain Controller the + * normal samba binary overrides and provides DCERPC services, whilst + * allowing samba-dcerpcd to provide the services that smbd used to + * provide in that set-up, such as SRVSVC. + * + * The second mode can also be useful for use outside of the Samba framework, + * for example, use with the Linux kernel SMB2 server ksmbd. In this mode + * it behaves like inetd and listens on sockets on behalf of RPC server + * implementations. + */ + +#include "replace.h" +#include <fnmatch.h> +#include "lib/cmdline/cmdline.h" +#include "lib/cmdline/closefrom_except.h" +#include "source3/include/includes.h" +#include "source3/include/auth.h" +#include "rpc_sock_helper.h" +#include "messages.h" +#include "lib/util_file.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/smb_strtox.h" +#include "lib/util/debug.h" +#include "lib/util/server_id.h" +#include "lib/util/util_tdb.h" +#include "lib/tdb_wrap/tdb_wrap.h" +#include "lib/async_req/async_sock.h" +#include "librpc/rpc/dcerpc_util.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/named_pipe_auth/npa_tstream.h" +#include "librpc/gen_ndr/ndr_rpc_host.h" +#include "source3/param/loadparm.h" +#include "source3/lib/global_contexts.h" +#include "lib/util/strv.h" +#include "lib/util/pidfile.h" +#include "source3/rpc_client/cli_pipe.h" +#include "librpc/gen_ndr/ndr_epmapper.h" +#include "librpc/gen_ndr/ndr_epmapper_c.h" +#include "nsswitch/winbind_client.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security_token.h" + +extern bool override_logfile; + +struct rpc_server; +struct rpc_work_process; + +/* + * samba-dcerpcd state to keep track of rpcd_* servers. + */ +struct rpc_host { + struct messaging_context *msg_ctx; + struct rpc_server **servers; + struct tdb_wrap *epmdb; + + int worker_stdin[2]; + + bool np_helper; + + /* + * If we're started with --np-helper but nobody contacts us, + * we need to exit after a while. This will be deleted once + * the first real client connects and our self-exit mechanism + * when we don't have any worker processes left kicks in. + */ + struct tevent_timer *np_helper_shutdown; +}; + +/* + * Map a RPC interface to a name. Used when filling the endpoint + * mapper database + */ +struct rpc_host_iface_name { + struct ndr_syntax_id iface; + char *name; +}; + +/* + * rpc_host representation for listening sockets. ncacn_ip_tcp might + * listen on multiple explicit IPs, all with the same port. + */ +struct rpc_host_endpoint { + struct rpc_server *server; + struct dcerpc_binding *binding; + struct ndr_syntax_id *interfaces; + int *fds; + size_t num_fds; +}; + +/* + * Staging area until we sent the socket plus bind to the helper + */ +struct rpc_host_pending_client { + struct rpc_host_pending_client *prev, *next; + + /* + * Pointer for the destructor to remove us from the list of + * pending clients + */ + struct rpc_server *server; + + /* + * Waiter for client exit before a helper accepted the request + */ + struct tevent_req *hangup_wait; + + /* + * Info to pick the worker + */ + struct ncacn_packet *bind_pkt; + + /* + * This is what we send down to the worker + */ + int sock; + struct rpc_host_client *client; +}; + +/* + * Representation of one worker process. For each rpcd_* executable + * there will be more of than one of these. + */ +struct rpc_work_process { + pid_t pid; + + /* + * !available means: + * + * Worker forked but did not send its initial status yet (not + * yet initialized) + * + * Worker died, but we did not receive SIGCHLD yet. We noticed + * it because we couldn't send it a message. + */ + bool available; + + /* + * Incremented by us when sending a client, decremented by + * MSG_RPC_HOST_WORKER_STATUS sent by workers whenever a + * client exits. + */ + uint32_t num_associations; + uint32_t num_connections; + + /* + * Send SHUTDOWN to an idle child after a while + */ + struct tevent_timer *exit_timer; +}; + +/* + * State for a set of running instances of an rpcd_* server executable + */ +struct rpc_server { + struct rpc_host *host; + /* + * Index into the rpc_host_state->servers array + */ + uint32_t server_index; + + const char *rpc_server_exe; + + struct rpc_host_endpoint **endpoints; + struct rpc_host_iface_name *iface_names; + + size_t max_workers; + size_t idle_seconds; + + /* + * "workers" can be larger than "max_workers": Internal + * connections require an idle worker to avoid deadlocks + * between RPC servers: netlogon requires samr, everybody + * requires winreg. And if a deep call in netlogon asks for a + * samr connection, this must never end up in the same + * process. named_pipe_auth_req_info8->need_idle_server is set + * in those cases. + */ + struct rpc_work_process *workers; + + struct rpc_host_pending_client *pending_clients; +}; + +struct rpc_server_get_endpoints_state { + char **argl; + char *ncalrpc_endpoint; + enum dcerpc_transport_t only_transport; + + struct rpc_host_iface_name *iface_names; + struct rpc_host_endpoint **endpoints; + + unsigned long num_workers; + unsigned long idle_seconds; +}; + +static void rpc_server_get_endpoints_done(struct tevent_req *subreq); + +/** + * @brief Query interfaces from an rpcd helper + * + * Spawn a rpcd helper, ask it for the interfaces it serves via + * --list-interfaces, parse the output + * + * @param[in] mem_ctx Memory context for the tevent_req + * @param[in] ev Event context to run this on + * @param[in] rpc_server_exe Binary to ask with --list-interfaces + * @param[in] only_transport Filter out anything but this + * @return The tevent_req representing this process + */ + +static struct tevent_req *rpc_server_get_endpoints_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *rpc_server_exe, + enum dcerpc_transport_t only_transport) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct rpc_server_get_endpoints_state *state = NULL; + const char *progname = NULL; + + req = tevent_req_create( + mem_ctx, &state, struct rpc_server_get_endpoints_state); + if (req == NULL) { + return NULL; + } + state->only_transport = only_transport; + + progname = strrchr(rpc_server_exe, '/'); + if (progname != NULL) { + progname += 1; + } else { + progname = rpc_server_exe; + } + + state->ncalrpc_endpoint = talloc_strdup(state, progname); + if (tevent_req_nomem(state->ncalrpc_endpoint, req)) { + return tevent_req_post(req, ev); + } + + state->argl = talloc_array(state, char *, 4); + if (tevent_req_nomem(state->argl, req)) { + return tevent_req_post(req, ev); + } + + state->argl = str_list_make_empty(state); + str_list_add_printf(&state->argl, "%s", rpc_server_exe); + str_list_add_printf(&state->argl, "--list-interfaces"); + str_list_add_printf( + &state->argl, "--configfile=%s", get_dyn_CONFIGFILE()); + + if (tevent_req_nomem(state->argl, req)) { + return tevent_req_post(req, ev); + } + + subreq = file_ploadv_send(state, ev, state->argl, 65536); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, rpc_server_get_endpoints_done, req); + return req; +} + +/* + * Parse a line of format + * + * 338cd001-2244-31f1-aaaa-900038001003/0x00000001 winreg + * + * and add it to the "piface_names" array. + */ + +static struct rpc_host_iface_name *rpc_exe_parse_iface_line( + TALLOC_CTX *mem_ctx, + struct rpc_host_iface_name **piface_names, + const char *line) +{ + struct rpc_host_iface_name *iface_names = *piface_names; + struct rpc_host_iface_name *tmp = NULL, *result = NULL; + size_t i, num_ifaces = talloc_array_length(iface_names); + struct ndr_syntax_id iface; + char *name = NULL; + bool ok; + + ok = ndr_syntax_id_from_string(line, &iface); + if (!ok) { + DBG_WARNING("ndr_syntax_id_from_string() failed for: [%s]\n", + line); + return NULL; + } + + name = strchr(line, ' '); + if (name == NULL) { + return NULL; + } + name += 1; + + for (i=0; i<num_ifaces; i++) { + result = &iface_names[i]; + + if (ndr_syntax_id_equal(&result->iface, &iface)) { + return result; + } + } + + if (num_ifaces + 1 < num_ifaces) { + return NULL; + } + + name = talloc_strdup(mem_ctx, name); + if (name == NULL) { + return NULL; + } + + tmp = talloc_realloc( + mem_ctx, + iface_names, + struct rpc_host_iface_name, + num_ifaces + 1); + if (tmp == NULL) { + TALLOC_FREE(name); + return NULL; + } + iface_names = tmp; + + result = &iface_names[num_ifaces]; + + *result = (struct rpc_host_iface_name) { + .iface = iface, + .name = talloc_move(iface_names, &name), + }; + + *piface_names = iface_names; + + return result; +} + +static struct rpc_host_iface_name *rpc_host_iface_names_find( + struct rpc_host_iface_name *iface_names, + const struct ndr_syntax_id *iface) +{ + size_t i, num_iface_names = talloc_array_length(iface_names); + + for (i=0; i<num_iface_names; i++) { + struct rpc_host_iface_name *iface_name = &iface_names[i]; + + if (ndr_syntax_id_equal(iface, &iface_name->iface)) { + return iface_name; + } + } + + return NULL; +} + +static bool dcerpc_binding_same_endpoint( + const struct dcerpc_binding *b1, const struct dcerpc_binding *b2) +{ + enum dcerpc_transport_t t1 = dcerpc_binding_get_transport(b1); + enum dcerpc_transport_t t2 = dcerpc_binding_get_transport(b2); + const char *e1 = NULL, *e2 = NULL; + int cmp; + + if (t1 != t2) { + return false; + } + + e1 = dcerpc_binding_get_string_option(b1, "endpoint"); + e2 = dcerpc_binding_get_string_option(b2, "endpoint"); + + if ((e1 == NULL) && (e2 == NULL)) { + return true; + } + if ((e1 == NULL) || (e2 == NULL)) { + return false; + } + cmp = strcmp(e1, e2); + return (cmp == 0); +} + +/** + * @brief Filter whether we want to serve an endpoint + * + * samba-dcerpcd might want to serve all endpoints a rpcd reported to + * us via --list-interfaces. + * + * In member mode, we only serve named pipes. Indicated by NCACN_NP + * passed in via "only_transport". + * + * @param[in] binding Which binding is in question? + * @param[in] only_transport Exclusive transport to serve + * @return Do we want to serve "binding" from samba-dcerpcd? + */ + +static bool rpc_host_serve_endpoint( + struct dcerpc_binding *binding, + enum dcerpc_transport_t only_transport) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(binding); + + if (only_transport == NCA_UNKNOWN) { + /* no filter around */ + return true; + } + + if (transport != only_transport) { + /* filter out */ + return false; + } + + return true; +} + +static struct rpc_host_endpoint *rpc_host_endpoint_find( + struct rpc_server_get_endpoints_state *state, + const char *binding_string) +{ + size_t i, num_endpoints = talloc_array_length(state->endpoints); + struct rpc_host_endpoint **tmp = NULL, *ep = NULL; + enum dcerpc_transport_t transport; + NTSTATUS status; + bool serve_this; + + ep = talloc_zero(state, struct rpc_host_endpoint); + if (ep == NULL) { + goto fail; + } + + status = dcerpc_parse_binding(ep, binding_string, &ep->binding); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n", + binding_string, + nt_errstr(status)); + goto fail; + } + + serve_this = rpc_host_serve_endpoint( + ep->binding, state->only_transport); + if (!serve_this) { + goto fail; + } + + transport = dcerpc_binding_get_transport(ep->binding); + + if (transport == NCALRPC) { + const char *ncalrpc_sock = dcerpc_binding_get_string_option( + ep->binding, "endpoint"); + + if (ncalrpc_sock == NULL) { + /* + * generic ncalrpc:, set program-specific + * socket name. epmapper will redirect clients + * properly. + */ + status = dcerpc_binding_set_string_option( + ep->binding, + "endpoint", + state->ncalrpc_endpoint); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_binding_set_string_option " + "failed: %s\n", + nt_errstr(status)); + goto fail; + } + } + } + + for (i=0; i<num_endpoints; i++) { + + bool ok = dcerpc_binding_same_endpoint( + ep->binding, state->endpoints[i]->binding); + + if (ok) { + TALLOC_FREE(ep); + return state->endpoints[i]; + } + } + + if (num_endpoints + 1 < num_endpoints) { + goto fail; + } + + tmp = talloc_realloc( + state, + state->endpoints, + struct rpc_host_endpoint *, + num_endpoints + 1); + if (tmp == NULL) { + goto fail; + } + state->endpoints = tmp; + state->endpoints[num_endpoints] = talloc_move(state->endpoints, &ep); + + return state->endpoints[num_endpoints]; +fail: + TALLOC_FREE(ep); + return NULL; +} + +static bool ndr_interfaces_add_unique( + TALLOC_CTX *mem_ctx, + struct ndr_syntax_id **pifaces, + const struct ndr_syntax_id *iface) +{ + struct ndr_syntax_id *ifaces = *pifaces; + size_t i, num_ifaces = talloc_array_length(ifaces); + + for (i=0; i<num_ifaces; i++) { + if (ndr_syntax_id_equal(iface, &ifaces[i])) { + return true; + } + } + + if (num_ifaces + 1 < num_ifaces) { + return false; + } + ifaces = talloc_realloc( + mem_ctx, + ifaces, + struct ndr_syntax_id, + num_ifaces + 1); + if (ifaces == NULL) { + return false; + } + ifaces[num_ifaces] = *iface; + + *pifaces = ifaces; + return true; +} + +/* + * Read the text reply from the rpcd_* process telling us what + * endpoints it will serve when asked with --list-interfaces. + */ +static void rpc_server_get_endpoints_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_server_get_endpoints_state *state = tevent_req_data( + req, struct rpc_server_get_endpoints_state); + struct rpc_host_iface_name *iface = NULL; + uint8_t *buf = NULL; + size_t buflen; + char **lines = NULL; + int ret, i, num_lines; + + ret = file_ploadv_recv(subreq, state, &buf); + TALLOC_FREE(subreq); + if (tevent_req_error(req, ret)) { + return; + } + + buflen = talloc_get_size(buf); + if (buflen == 0) { + tevent_req_done(req); + return; + } + + lines = file_lines_parse((char *)buf, buflen, &num_lines, state); + if (tevent_req_nomem(lines, req)) { + return; + } + + if (num_lines < 2) { + DBG_DEBUG("Got %d lines, expected at least 2\n", num_lines); + tevent_req_error(req, EINVAL); + return; + } + + state->num_workers = smb_strtoul( + lines[0], NULL, 10, &ret, SMB_STR_FULL_STR_CONV); + if (ret != 0) { + DBG_DEBUG("Could not parse num_workers(%s): %s\n", + lines[0], + strerror(ret)); + tevent_req_error(req, ret); + return; + } + /* + * We need to limit the number of workers in order + * to put the worker index into a 16-bit space, + * in order to use a 16-bit association group space + * per worker. + */ + if (state->num_workers > 65536) { + state->num_workers = 65536; + } + + state->idle_seconds = smb_strtoul( + lines[1], NULL, 10, &ret, SMB_STR_FULL_STR_CONV); + if (ret != 0) { + DBG_DEBUG("Could not parse idle_seconds (%s): %s\n", + lines[1], + strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DBG_DEBUG("num_workers=%lu, idle_seconds=%lu for %s\n", + state->num_workers, + state->idle_seconds, + state->argl[0]); + + for (i=2; i<num_lines; i++) { + char *line = lines[i]; + struct rpc_host_endpoint *endpoint = NULL; + bool ok; + + if (line[0] != ' ') { + iface = rpc_exe_parse_iface_line( + state, &state->iface_names, line); + if (iface == NULL) { + DBG_WARNING( + "rpc_exe_parse_iface_line failed " + "for: [%s] from %s\n", + line, + state->argl[0]); + tevent_req_oom(req); + return; + } + continue; + } + + if (iface == NULL) { + DBG_DEBUG("Interface GUID line missing\n"); + tevent_req_error(req, EINVAL); + return; + } + + endpoint = rpc_host_endpoint_find(state, line+1); + if (endpoint == NULL) { + DBG_DEBUG("rpc_host_endpoint_find for %s failed\n", + line+1); + continue; + } + + ok = ndr_interfaces_add_unique( + endpoint, + &endpoint->interfaces, + &iface->iface); + if (!ok) { + DBG_DEBUG("ndr_interfaces_add_unique failed\n"); + tevent_req_oom(req); + return; + } + } + + tevent_req_done(req); +} + +/** + * @brief Receive output from --list-interfaces + * + * @param[in] req The async req that just finished + * @param[in] mem_ctx Where to put the output on + * @param[out] endpoints The endpoints to be listened on + * @param[out] iface_names Annotation for epm_Lookup's epm_entry_t + * @return 0/errno + */ +static int rpc_server_get_endpoints_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct rpc_host_endpoint ***endpoints, + struct rpc_host_iface_name **iface_names, + size_t *num_workers, + size_t *idle_seconds) +{ + struct rpc_server_get_endpoints_state *state = tevent_req_data( + req, struct rpc_server_get_endpoints_state); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + tevent_req_received(req); + return err; + } + + *endpoints = talloc_move(mem_ctx, &state->endpoints); + *iface_names = talloc_move(mem_ctx, &state->iface_names); + *num_workers = state->num_workers; + *idle_seconds = state->idle_seconds; + tevent_req_received(req); + return 0; +} + +/* + * For NCACN_NP we get the named pipe auth info from smbd, if a client + * comes in via TCP or NCALPRC we need to invent it ourselves with + * anonymous session info. + */ + +static NTSTATUS rpc_host_generate_npa_info8_from_sock( + TALLOC_CTX *mem_ctx, + enum dcerpc_transport_t transport, + int sock, + const struct samba_sockaddr *peer_addr, + struct named_pipe_auth_req_info8 **pinfo8) +{ + struct named_pipe_auth_req_info8 *info8 = NULL; + struct samba_sockaddr local_addr = { + .sa_socklen = sizeof(struct sockaddr_storage), + }; + struct tsocket_address *taddr = NULL; + char *remote_client_name = NULL; + char *remote_client_addr = NULL; + char *local_server_name = NULL; + char *local_server_addr = NULL; + char *(*tsocket_address_to_name_fn)( + const struct tsocket_address *addr, + TALLOC_CTX *mem_ctx) = NULL; + NTSTATUS status = NT_STATUS_NO_MEMORY; + int ret; + + /* + * For NCACN_NP we get the npa info from smbd + */ + SMB_ASSERT((transport == NCACN_IP_TCP) || (transport == NCALRPC)); + + tsocket_address_to_name_fn = (transport == NCACN_IP_TCP) ? + tsocket_address_inet_addr_string : tsocket_address_unix_path; + + info8 = talloc_zero(mem_ctx, struct named_pipe_auth_req_info8); + if (info8 == NULL) { + goto fail; + } + info8->session_info = + talloc_zero(info8, struct auth_session_info_transport); + if (info8->session_info == NULL) { + goto fail; + } + + status = make_session_info_anonymous( + info8->session_info, + &info8->session_info->session_info); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("make_session_info_anonymous failed: %s\n", + nt_errstr(status)); + goto fail; + } + + ret = tsocket_address_bsd_from_samba_sockaddr(info8, + peer_addr, + &taddr); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: " + "%s\n", + strerror(errno)); + goto fail; + } + remote_client_addr = tsocket_address_to_name_fn(taddr, info8); + if (remote_client_addr == NULL) { + DBG_DEBUG("tsocket_address_to_name_fn failed\n"); + goto nomem; + } + TALLOC_FREE(taddr); + + remote_client_name = talloc_strdup(info8, remote_client_addr); + if (remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup failed\n"); + goto nomem; + } + + if (transport == NCACN_IP_TCP) { + bool ok = samba_sockaddr_get_port(peer_addr, + &info8->remote_client_port); + if (!ok) { + DBG_DEBUG("samba_sockaddr_get_port failed\n"); + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + } + + ret = getsockname(sock, &local_addr.u.sa, &local_addr.sa_socklen); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + DBG_DEBUG("getsockname failed: %s\n", strerror(errno)); + goto fail; + } + + ret = tsocket_address_bsd_from_samba_sockaddr(info8, + &local_addr, + &taddr); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: " + "%s\n", + strerror(errno)); + goto fail; + } + local_server_addr = tsocket_address_to_name_fn(taddr, info8); + if (local_server_addr == NULL) { + DBG_DEBUG("tsocket_address_to_name_fn failed\n"); + goto nomem; + } + TALLOC_FREE(taddr); + + local_server_name = talloc_strdup(info8, local_server_addr); + if (local_server_name == NULL) { + DBG_DEBUG("talloc_strdup failed\n"); + goto nomem; + } + + if (transport == NCACN_IP_TCP) { + bool ok = samba_sockaddr_get_port(&local_addr, + &info8->local_server_port); + if (!ok) { + DBG_DEBUG("samba_sockaddr_get_port failed\n"); + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + } + + if (transport == NCALRPC) { + uid_t uid; + gid_t gid; + + ret = getpeereid(sock, &uid, &gid); + if (ret < 0) { + status = map_nt_error_from_unix(errno); + DBG_DEBUG("getpeereid failed: %s\n", strerror(errno)); + goto fail; + } + + if (uid == sec_initial_uid()) { + + /* + * Indicate "root" to gensec + */ + + TALLOC_FREE(remote_client_addr); + TALLOC_FREE(remote_client_name); + + ret = tsocket_address_unix_from_path( + info8, + AS_SYSTEM_MAGIC_PATH_TOKEN, + &taddr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_unix_from_path " + "failed\n"); + goto nomem; + } + + remote_client_addr = + tsocket_address_unix_path(taddr, info8); + if (remote_client_addr == NULL) { + DBG_DEBUG("tsocket_address_unix_path " + "failed\n"); + goto nomem; + } + remote_client_name = + talloc_strdup(info8, remote_client_addr); + if (remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup failed\n"); + goto nomem; + } + } + } + + info8->remote_client_addr = remote_client_addr; + info8->remote_client_name = remote_client_name; + info8->local_server_addr = local_server_addr; + info8->local_server_name = local_server_name; + + *pinfo8 = info8; + return NT_STATUS_OK; + +nomem: + status = NT_STATUS_NO_MEMORY; +fail: + TALLOC_FREE(info8); + return status; +} + +struct rpc_host_bind_read_state { + struct tevent_context *ev; + + int sock; + struct tstream_context *plain; + struct tstream_context *npa_stream; + + struct ncacn_packet *pkt; + struct rpc_host_client *client; +}; + +static void rpc_host_bind_read_cleanup( + struct tevent_req *req, enum tevent_req_state req_state); +static void rpc_host_bind_read_got_npa(struct tevent_req *subreq); +static void rpc_host_bind_read_got_bind(struct tevent_req *subreq); + +/* + * Wait for a bind packet from a client. + */ +static struct tevent_req *rpc_host_bind_read_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + enum dcerpc_transport_t transport, + int *psock, + const struct samba_sockaddr *peer_addr) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct rpc_host_bind_read_state *state = NULL; + int rc, sock_dup; + NTSTATUS status; + + req = tevent_req_create( + mem_ctx, &state, struct rpc_host_bind_read_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + state->sock = *psock; + *psock = -1; + + tevent_req_set_cleanup_fn(req, rpc_host_bind_read_cleanup); + + state->client = talloc_zero(state, struct rpc_host_client); + if (tevent_req_nomem(state->client, req)) { + return tevent_req_post(req, ev); + } + + /* + * Dup the socket to read the first RPC packet: + * tstream_bsd_existing_socket() takes ownership with + * autoclose, but we need to send "sock" down to our worker + * process later. + */ + sock_dup = dup(state->sock); + if (sock_dup == -1) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + rc = tstream_bsd_existing_socket(state, sock_dup, &state->plain); + if (rc == -1) { + DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n", + strerror(errno)); + tevent_req_error(req, errno); + close(sock_dup); + return tevent_req_post(req, ev); + } + /* as server we want to fail early */ + tstream_bsd_fail_readv_first_error(state->plain, true); + + if (transport == NCACN_NP) { + subreq = tstream_npa_accept_existing_send( + state, + ev, + state->plain, + FILE_TYPE_MESSAGE_MODE_PIPE, + 0xff | 0x0400 | 0x0100, + 4096); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, rpc_host_bind_read_got_npa, req); + return req; + } + + status = rpc_host_generate_npa_info8_from_sock( + state->client, + transport, + state->sock, + peer_addr, + &state->client->npa_info8); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_read_ncacn_packet_send(state, ev, state->plain); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req); + return req; +} + +static void rpc_host_bind_read_cleanup( + struct tevent_req *req, enum tevent_req_state req_state) +{ + struct rpc_host_bind_read_state *state = tevent_req_data( + req, struct rpc_host_bind_read_state); + + if ((req_state == TEVENT_REQ_RECEIVED) && (state->sock != -1)) { + close(state->sock); + state->sock = -1; + } +} + +static void rpc_host_bind_read_got_npa(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_bind_read_state *state = tevent_req_data( + req, struct rpc_host_bind_read_state); + struct named_pipe_auth_req_info8 *info8 = NULL; + int ret, err; + + ret = tstream_npa_accept_existing_recv(subreq, + &err, + state, + &state->npa_stream, + &info8, + NULL, /* transport */ + NULL, /* remote_client_addr */ + NULL, /* remote_client_name */ + NULL, /* local_server_addr */ + NULL, /* local_server_name */ + NULL); /* session_info */ + if (ret == -1) { + tevent_req_error(req, err); + return; + } + + state->client->npa_info8 = talloc_move(state->client, &info8); + + subreq = dcerpc_read_ncacn_packet_send( + state, state->ev, state->npa_stream); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req); +} + +static void rpc_host_bind_read_got_bind(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_bind_read_state *state = tevent_req_data( + req, struct rpc_host_bind_read_state); + struct ncacn_packet *pkt = NULL; + NTSTATUS status; + + status = dcerpc_read_ncacn_packet_recv( + subreq, + state->client, + &pkt, + &state->client->bind_packet); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_read_ncacn_packet_recv failed: %s\n", + nt_errstr(status)); + tevent_req_error(req, EINVAL); /* TODO */ + return; + } + state->pkt = talloc_move(state, &pkt); + + tevent_req_done(req); +} + +static int rpc_host_bind_read_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + int *sock, + struct rpc_host_client **client, + struct ncacn_packet **bind_pkt) +{ + struct rpc_host_bind_read_state *state = tevent_req_data( + req, struct rpc_host_bind_read_state); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + tevent_req_received(req); + return err; + } + + *sock = state->sock; + state->sock = -1; + + *client = talloc_move(mem_ctx, &state->client); + *bind_pkt = talloc_move(mem_ctx, &state->pkt); + tevent_req_received(req); + return 0; +} + +/* + * Start the given rpcd_* binary. + */ +static int rpc_host_exec_worker(struct rpc_server *server, size_t idx) +{ + struct rpc_work_process *worker = &server->workers[idx]; + char **argv = NULL; + int ret = ENOMEM; + + argv = str_list_make_empty(server); + str_list_add_printf( + &argv, "%s", server->rpc_server_exe); + str_list_add_printf( + &argv, "--configfile=%s", get_dyn_CONFIGFILE()); + str_list_add_printf( + &argv, "--worker-group=%"PRIu32, server->server_index); + str_list_add_printf( + &argv, "--worker-index=%zu", idx); + str_list_add_printf( + &argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV)); + if (!is_default_dyn_LOGFILEBASE()) { + str_list_add_printf( + &argv, "--log-basename=%s", get_dyn_LOGFILEBASE()); + } + if (argv == NULL) { + ret = ENOMEM; + goto fail; + } + + worker->pid = fork(); + if (worker->pid == -1) { + ret = errno; + goto fail; + } + if (worker->pid == 0) { + /* Child. */ + close(server->host->worker_stdin[1]); + ret = dup2(server->host->worker_stdin[0], 0); + if (ret != 0) { + exit(1); + } + execv(argv[0], argv); + _exit(1); + } + + DBG_DEBUG("Creating worker %s for index %zu: pid=%d\n", + server->rpc_server_exe, + idx, + (int)worker->pid); + + ret = 0; +fail: + TALLOC_FREE(argv); + return ret; +} + +/* + * Find an rpcd_* worker for an external client, respect server->max_workers + */ +static struct rpc_work_process *rpc_host_find_worker(struct rpc_server *server) +{ + struct rpc_work_process *worker = NULL; + struct rpc_work_process *perfect_worker = NULL; + struct rpc_work_process *best_worker = NULL; + size_t empty_slot = SIZE_MAX; + size_t i; + + for (i=0; i<server->max_workers; i++) { + worker = &server->workers[i]; + + if (worker->pid == -1) { + empty_slot = MIN(empty_slot, i); + continue; + } + if (!worker->available) { + continue; + } + if (worker->num_associations == 0) { + /* + * We have an idle worker... + */ + perfect_worker = worker; + break; + } + if (best_worker == NULL) { + /* + * It's busy, but the best so far... + */ + best_worker = worker; + continue; + } + if (worker->num_associations < best_worker->num_associations) { + /* + * It's also busy, but has less association groups + * (logical clients) + */ + best_worker = worker; + continue; + } + if (worker->num_associations > best_worker->num_associations) { + /* + * It's not better + */ + continue; + } + /* + * Ok, with the same number of association groups + * we pick the one with the lowest number of connections + */ + if (worker->num_connections < best_worker->num_connections) { + best_worker = worker; + continue; + } + } + + if (perfect_worker != NULL) { + return perfect_worker; + } + + if (empty_slot < SIZE_MAX) { + int ret = rpc_host_exec_worker(server, empty_slot); + if (ret != 0) { + DBG_WARNING("Could not fork worker: %s\n", + strerror(ret)); + } + return NULL; + } + + if (best_worker != NULL) { + return best_worker; + } + + return NULL; +} + +/* + * Find an rpcd_* worker for an internal connection, possibly go beyond + * server->max_workers + */ +static struct rpc_work_process *rpc_host_find_idle_worker( + struct rpc_server *server) +{ + struct rpc_work_process *worker = NULL, *tmp = NULL; + size_t i, num_workers = talloc_array_length(server->workers); + size_t empty_slot = SIZE_MAX; + int ret; + + for (i=server->max_workers; i<num_workers; i++) { + worker = &server->workers[i]; + + if (worker->pid == -1) { + empty_slot = MIN(empty_slot, i); + continue; + } + if (!worker->available) { + continue; + } + if (worker->num_associations == 0) { + return &server->workers[i]; + } + } + + if (empty_slot < SIZE_MAX) { + ret = rpc_host_exec_worker(server, empty_slot); + if (ret != 0) { + DBG_WARNING("Could not fork worker: %s\n", + strerror(ret)); + } + return NULL; + } + + /* + * All workers are busy. We need to expand the number of + * workers because we were asked for an idle worker. + */ + if (num_workers >= UINT16_MAX) { + /* + * The worker index would not fit into 16-bits + */ + return NULL; + } + tmp = talloc_realloc( + server, + server->workers, + struct rpc_work_process, + num_workers+1); + if (tmp == NULL) { + return NULL; + } + server->workers = tmp; + + server->workers[num_workers] = (struct rpc_work_process) { .pid=-1, }; + + ret = rpc_host_exec_worker(server, num_workers); + if (ret != 0) { + DBG_WARNING("Could not exec worker: %s\n", strerror(ret)); + } + + return NULL; +} + +/* + * Find an rpcd_* process to talk to. Start a new one if necessary. + */ +static void rpc_host_distribute_clients(struct rpc_server *server) +{ + struct rpc_work_process *worker = NULL; + struct rpc_host_pending_client *pending_client = NULL; + uint32_t assoc_group_id; + DATA_BLOB blob; + struct iovec iov; + enum ndr_err_code ndr_err; + NTSTATUS status; + const char *client_type = NULL; + +again: + pending_client = server->pending_clients; + if (pending_client == NULL) { + DBG_DEBUG("No pending clients\n"); + return; + } + + assoc_group_id = pending_client->bind_pkt->u.bind.assoc_group_id; + + if (assoc_group_id != 0) { + size_t num_workers = talloc_array_length(server->workers); + uint16_t worker_index = assoc_group_id >> 16; + + client_type = "associated"; + + if (worker_index >= num_workers) { + DBG_DEBUG("Invalid assoc group id %"PRIu32"\n", + assoc_group_id); + goto done; + } + worker = &server->workers[worker_index]; + + if ((worker->pid == -1) || !worker->available) { + DBG_DEBUG("Requested worker index %"PRIu16": " + "pid=%d, available=%d\n", + worker_index, + (int)worker->pid, + (int)worker->available); + /* + * Pick a random one for a proper bind nack + */ + client_type = "associated+lost"; + worker = rpc_host_find_worker(server); + } + } else { + struct auth_session_info_transport *session_info = + pending_client->client->npa_info8->session_info; + uint32_t flags = 0; + bool found; + + client_type = "new"; + + found = security_token_find_npa_flags( + session_info->session_info->security_token, + &flags); + + /* fresh assoc group requested */ + if (found & (flags & SAMBA_NPA_FLAGS_NEED_IDLE)) { + client_type = "new+exclusive"; + worker = rpc_host_find_idle_worker(server); + } else { + client_type = "new"; + worker = rpc_host_find_worker(server); + } + } + + if (worker == NULL) { + DBG_DEBUG("No worker found for %s client\n", client_type); + return; + } + + DLIST_REMOVE(server->pending_clients, pending_client); + + ndr_err = ndr_push_struct_blob( + &blob, + pending_client, + pending_client->client, + (ndr_push_flags_fn_t)ndr_push_rpc_host_client); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_WARNING("ndr_push_rpc_host_client failed: %s\n", + ndr_errstr(ndr_err)); + goto done; + } + + DBG_INFO("Sending %s client %s to %d with " + "%"PRIu32" associations and %"PRIu32" connections\n", + client_type, + server->rpc_server_exe, + worker->pid, + worker->num_associations, + worker->num_connections); + + iov = (struct iovec) { + .iov_base = blob.data, .iov_len = blob.length, + }; + + status = messaging_send_iov( + server->host->msg_ctx, + pid_to_procid(worker->pid), + MSG_RPC_HOST_NEW_CLIENT, + &iov, + 1, + &pending_client->sock, + 1); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + DBG_DEBUG("worker %d died, sigchld not yet received?\n", + worker->pid); + DLIST_ADD(server->pending_clients, pending_client); + worker->available = false; + goto again; + } + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("messaging_send_iov failed: %s\n", + nt_errstr(status)); + goto done; + } + if (assoc_group_id == 0) { + worker->num_associations += 1; + } + worker->num_connections += 1; + TALLOC_FREE(worker->exit_timer); + + TALLOC_FREE(server->host->np_helper_shutdown); + +done: + TALLOC_FREE(pending_client); +} + +static int rpc_host_pending_client_destructor( + struct rpc_host_pending_client *p) +{ + TALLOC_FREE(p->hangup_wait); + if (p->sock != -1) { + close(p->sock); + p->sock = -1; + } + DLIST_REMOVE(p->server->pending_clients, p); + return 0; +} + +/* + * Exception condition handler before rpcd_* worker + * is handling the socket. Either the client exited or + * sent unexpected data after the initial bind. + */ +static void rpc_host_client_exited(struct tevent_req *subreq) +{ + struct rpc_host_pending_client *pending = tevent_req_callback_data( + subreq, struct rpc_host_pending_client); + bool ok; + int err; + + ok = wait_for_read_recv(subreq, &err); + + TALLOC_FREE(subreq); + pending->hangup_wait = NULL; + + if (ok) { + DBG_DEBUG("client on sock %d sent data\n", pending->sock); + } else { + DBG_DEBUG("client exited with %s\n", strerror(err)); + } + TALLOC_FREE(pending); +} + +struct rpc_iface_binding_map { + struct ndr_syntax_id iface; + char *bindings; +}; + +static bool rpc_iface_binding_map_add_endpoint( + TALLOC_CTX *mem_ctx, + const struct rpc_host_endpoint *ep, + struct rpc_host_iface_name *iface_names, + struct rpc_iface_binding_map **pmaps) +{ + const struct ndr_syntax_id mgmt_iface = { + {0xafa8bd80, + 0x7d8a, + 0x11c9, + {0xbe,0xf4}, + {0x08,0x00,0x2b,0x10,0x29,0x89} + }, + 1.0}; + + struct rpc_iface_binding_map *maps = *pmaps; + size_t i, num_ifaces = talloc_array_length(ep->interfaces); + char *binding_string = NULL; + bool ok = false; + + binding_string = dcerpc_binding_string(mem_ctx, ep->binding); + if (binding_string == NULL) { + return false; + } + + for (i=0; i<num_ifaces; i++) { + const struct ndr_syntax_id *iface = &ep->interfaces[i]; + size_t j, num_maps = talloc_array_length(maps); + struct rpc_iface_binding_map *map = NULL; + char *p = NULL; + + if (ndr_syntax_id_equal(iface, &mgmt_iface)) { + /* + * mgmt is offered everywhere, don't put it + * into epmdb.tdb. + */ + continue; + } + + for (j=0; j<num_maps; j++) { + map = &maps[j]; + if (ndr_syntax_id_equal(&map->iface, iface)) { + break; + } + } + + if (j == num_maps) { + struct rpc_iface_binding_map *tmp = NULL; + struct rpc_host_iface_name *iface_name = NULL; + + iface_name = rpc_host_iface_names_find( + iface_names, iface); + if (iface_name == NULL) { + goto fail; + } + + tmp = talloc_realloc( + mem_ctx, + maps, + struct rpc_iface_binding_map, + num_maps+1); + if (tmp == NULL) { + goto fail; + } + maps = tmp; + + map = &maps[num_maps]; + *map = (struct rpc_iface_binding_map) { + .iface = *iface, + .bindings = talloc_move( + maps, &iface_name->name), + }; + } + + p = strv_find(map->bindings, binding_string); + if (p == NULL) { + int ret = strv_add( + maps, &map->bindings, binding_string); + if (ret != 0) { + goto fail; + } + } + } + + ok = true; +fail: + *pmaps = maps; + return ok; +} + +static bool rpc_iface_binding_map_add_endpoints( + TALLOC_CTX *mem_ctx, + struct rpc_host_endpoint **endpoints, + struct rpc_host_iface_name *iface_names, + struct rpc_iface_binding_map **pbinding_maps) +{ + size_t i, num_endpoints = talloc_array_length(endpoints); + + for (i=0; i<num_endpoints; i++) { + bool ok = rpc_iface_binding_map_add_endpoint( + mem_ctx, endpoints[i], iface_names, pbinding_maps); + if (!ok) { + return false; + } + } + return true; +} + +static bool rpc_host_fill_epm_db( + struct tdb_wrap *db, + struct rpc_host_endpoint **endpoints, + struct rpc_host_iface_name *iface_names) +{ + struct rpc_iface_binding_map *maps = NULL; + size_t i, num_maps; + bool ret = false; + bool ok; + + ok = rpc_iface_binding_map_add_endpoints( + talloc_tos(), endpoints, iface_names, &maps); + if (!ok) { + goto fail; + } + + num_maps = talloc_array_length(maps); + + for (i=0; i<num_maps; i++) { + struct rpc_iface_binding_map *map = &maps[i]; + struct ndr_syntax_id_buf buf; + char *keystr = ndr_syntax_id_buf_string(&map->iface, &buf); + TDB_DATA value = { + .dptr = (uint8_t *)map->bindings, + .dsize = talloc_array_length(map->bindings), + }; + int rc; + + rc = tdb_store( + db->tdb, string_term_tdb_data(keystr), value, 0); + if (rc == -1) { + DBG_DEBUG("tdb_store() failed: %s\n", + tdb_errorstr(db->tdb)); + goto fail; + } + } + + ret = true; +fail: + TALLOC_FREE(maps); + return ret; +} + +struct rpc_server_setup_state { + struct rpc_server *server; +}; + +static void rpc_server_setup_got_endpoints(struct tevent_req *subreq); + +/* + * Async initialize state for all possible rpcd_* servers. + * Note this does not start them. + */ +static struct tevent_req *rpc_server_setup_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct rpc_host *host, + const char *rpc_server_exe) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct rpc_server_setup_state *state = NULL; + struct rpc_server *server = NULL; + + req = tevent_req_create( + mem_ctx, &state, struct rpc_server_setup_state); + if (req == NULL) { + return NULL; + } + state->server = talloc_zero(state, struct rpc_server); + if (tevent_req_nomem(state->server, req)) { + return tevent_req_post(req, ev); + } + + server = state->server; + + *server = (struct rpc_server) { + .host = host, + .server_index = UINT32_MAX, + .rpc_server_exe = talloc_strdup(server, rpc_server_exe), + }; + if (tevent_req_nomem(server->rpc_server_exe, req)) { + return tevent_req_post(req, ev); + } + + subreq = rpc_server_get_endpoints_send( + state, + ev, + rpc_server_exe, + host->np_helper ? NCACN_NP : NCA_UNKNOWN); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, rpc_server_setup_got_endpoints, req); + return req; +} + +static void rpc_server_setup_got_endpoints(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_server_setup_state *state = tevent_req_data( + req, struct rpc_server_setup_state); + struct rpc_server *server = state->server; + int ret; + size_t i, num_endpoints; + bool ok; + + ret = rpc_server_get_endpoints_recv( + subreq, + server, + &server->endpoints, + &server->iface_names, + &server->max_workers, + &server->idle_seconds); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_nterror(req, map_nt_error_from_unix(ret)); + return; + } + + server->workers = talloc_array( + server, struct rpc_work_process, server->max_workers); + if (tevent_req_nomem(server->workers, req)) { + return; + } + + for (i=0; i<server->max_workers; i++) { + /* mark as not yet created */ + server->workers[i] = (struct rpc_work_process) { .pid=-1, }; + } + + num_endpoints = talloc_array_length(server->endpoints); + + for (i=0; i<num_endpoints; i++) { + struct rpc_host_endpoint *e = server->endpoints[i]; + NTSTATUS status; + size_t j; + + e->server = server; + + status = dcesrv_create_binding_sockets( + e->binding, e, &e->num_fds, &e->fds); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + continue; + } + if (tevent_req_nterror(req, status)) { + DBG_DEBUG("dcesrv_create_binding_sockets failed: %s\n", + nt_errstr(status)); + return; + } + + for (j=0; j<e->num_fds; j++) { + ret = listen(e->fds[j], 256); + if (ret == -1) { + tevent_req_nterror( + req, map_nt_error_from_unix(errno)); + return; + } + } + } + + ok = rpc_host_fill_epm_db( + server->host->epmdb, server->endpoints, server->iface_names); + if (!ok) { + DBG_DEBUG("rpc_host_fill_epm_db failed\n"); + } + + tevent_req_done(req); +} + +static NTSTATUS rpc_server_setup_recv( + struct tevent_req *req, TALLOC_CTX *mem_ctx, struct rpc_server **server) +{ + struct rpc_server_setup_state *state = tevent_req_data( + req, struct rpc_server_setup_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *server = talloc_move(mem_ctx, &state->server); + tevent_req_received(req); + return NT_STATUS_OK; +} + +/* + * rpcd_* died. Called from SIGCHLD handler. + */ +static void rpc_worker_exited(struct rpc_host *host, pid_t pid) +{ + size_t i, num_servers = talloc_array_length(host->servers); + struct rpc_work_process *worker = NULL; + bool found_pid = false; + bool have_active_worker = false; + + for (i=0; i<num_servers; i++) { + struct rpc_server *server = host->servers[i]; + size_t j, num_workers; + + if (server == NULL) { + /* SIGCHLD for --list-interfaces run */ + continue; + } + + num_workers = talloc_array_length(server->workers); + + for (j=0; j<num_workers; j++) { + worker = &server->workers[j]; + if (worker->pid == pid) { + found_pid = true; + worker->pid = -1; + worker->available = false; + } + + if (worker->pid != -1) { + have_active_worker = true; + } + } + } + + if (!found_pid) { + DBG_WARNING("No worker with PID %d\n", (int)pid); + return; + } + + if (!have_active_worker && host->np_helper) { + /* + * We have nothing left to do as an np_helper. + * Terminate ourselves (samba-dcerpcd). We will + * be restarted on demand anyway. + */ + DBG_DEBUG("Exiting idle np helper\n"); + exit(0); + } +} + +/* + * rpcd_* died. + */ +static void rpc_host_sigchld( + struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct rpc_host *state = talloc_get_type_abort( + private_data, struct rpc_host); + pid_t pid; + int wstatus; + + while ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) { + DBG_DEBUG("pid=%d, wstatus=%d\n", (int)pid, wstatus); + rpc_worker_exited(state, pid); + } +} + +/* + * Idle timer fired for a rcpd_* worker. Ask it to terminate. + */ +static void rpc_host_exit_worker( + struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct rpc_server *server = talloc_get_type_abort( + private_data, struct rpc_server); + size_t i, num_workers = talloc_array_length(server->workers); + + /* + * Scan for the right worker. We don't have too many of those, + * and maintaining an index would be more data structure effort. + */ + + for (i=0; i<num_workers; i++) { + struct rpc_work_process *w = &server->workers[i]; + NTSTATUS status; + + if (w->exit_timer != te) { + continue; + } + w->exit_timer = NULL; + + SMB_ASSERT(w->num_associations == 0); + + status = messaging_send( + server->host->msg_ctx, + pid_to_procid(w->pid), + MSG_SHUTDOWN, + NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not send SHUTDOWN msg: %s\n", + nt_errstr(status)); + } + + w->available = false; + break; + } +} + +/* + * rcpd_* worker replied with its status. + */ +static void rpc_host_child_status_recv( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct rpc_host *host = talloc_get_type_abort( + private_data, struct rpc_host); + size_t num_servers = talloc_array_length(host->servers); + struct rpc_server *server = NULL; + size_t num_workers; + pid_t src_pid = procid_to_pid(&server_id); + struct rpc_work_process *worker = NULL; + struct rpc_worker_status status_message; + enum ndr_err_code ndr_err; + + ndr_err = ndr_pull_struct_blob_all_noalloc( + data, + &status_message, + (ndr_pull_flags_fn_t)ndr_pull_rpc_worker_status); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + struct server_id_buf buf; + DBG_WARNING("Got invalid message from pid %s\n", + server_id_str_buf(server_id, &buf)); + return; + } + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(rpc_worker_status, &status_message); + } + + if (status_message.server_index >= num_servers) { + DBG_WARNING("Got invalid server_index=%"PRIu32", " + "num_servers=%zu\n", + status_message.server_index, + num_servers); + return; + } + + server = host->servers[status_message.server_index]; + + num_workers = talloc_array_length(server->workers); + if (status_message.worker_index >= num_workers) { + DBG_WARNING("Got invalid worker_index=%"PRIu32", " + "num_workers=%zu\n", + status_message.worker_index, + num_workers); + return; + } + worker = &server->workers[status_message.worker_index]; + + if (src_pid != worker->pid) { + DBG_WARNING("Got idx=%"PRIu32" from %d, expected %d\n", + status_message.worker_index, + (int)src_pid, + worker->pid); + return; + } + + worker->available = true; + worker->num_associations = status_message.num_association_groups; + worker->num_connections = status_message.num_connections; + + if (worker->num_associations != 0) { + TALLOC_FREE(worker->exit_timer); + } else { + worker->exit_timer = tevent_add_timer( + messaging_tevent_context(msg), + server->workers, + tevent_timeval_current_ofs(server->idle_seconds, 0), + rpc_host_exit_worker, + server); + /* No NULL check, it's not fatal if this does not work */ + } + + rpc_host_distribute_clients(server); +} + +/* + * samba-dcerpcd has been asked to shutdown. + * Mark the initial tevent_req as done so we + * exit the event loop. + */ +static void rpc_host_msg_shutdown( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + tevent_req_done(req); +} + +/* + * Only match directory entries starting in rpcd_ + */ +static int rpcd_filter(const struct dirent *d) +{ + int match = fnmatch("rpcd_*", d->d_name, 0); + return (match == 0) ? 1 : 0; +} + +/* + * Scan the given libexecdir for rpcd_* services + * and return them as a strv list. + */ +static int rpc_host_list_servers( + const char *libexecdir, TALLOC_CTX *mem_ctx, char **pservers) +{ + char *servers = NULL; + struct dirent **namelist = NULL; + int i, num_servers; + int ret = ENOMEM; + + num_servers = scandir(libexecdir, &namelist, rpcd_filter, alphasort); + if (num_servers == -1) { + DBG_DEBUG("scandir failed: %s\n", strerror(errno)); + return errno; + } + + for (i=0; i<num_servers; i++) { + char *exe = talloc_asprintf( + mem_ctx, "%s/%s", libexecdir, namelist[i]->d_name); + if (exe == NULL) { + goto fail; + } + + ret = strv_add(mem_ctx, &servers, exe); + TALLOC_FREE(exe); + if (ret != 0) { + goto fail; + } + } +fail: + for (i=0; i<num_servers; i++) { + SAFE_FREE(namelist[i]); + } + SAFE_FREE(namelist); + + if (ret != 0) { + TALLOC_FREE(servers); + return ret; + } + *pservers = servers; + return 0; +} + +struct rpc_host_endpoint_accept_state { + struct tevent_context *ev; + struct rpc_host_endpoint *endpoint; +}; + +static void rpc_host_endpoint_accept_accepted(struct tevent_req *subreq); +static void rpc_host_endpoint_accept_got_bind(struct tevent_req *subreq); + +/* + * Asynchronously wait for a DCERPC connection from a client. + */ +static struct tevent_req *rpc_host_endpoint_accept_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct rpc_host_endpoint *endpoint) +{ + struct tevent_req *req = NULL; + struct rpc_host_endpoint_accept_state *state = NULL; + size_t i; + + req = tevent_req_create( + mem_ctx, &state, struct rpc_host_endpoint_accept_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->endpoint = endpoint; + + for (i=0; i<endpoint->num_fds; i++) { + struct tevent_req *subreq = NULL; + + subreq = accept_send(state, ev, endpoint->fds[i]); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, rpc_host_endpoint_accept_accepted, req); + } + + return req; +} + +/* + * Accept a DCERPC connection from a client. + */ +static void rpc_host_endpoint_accept_accepted(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_endpoint_accept_state *state = tevent_req_data( + req, struct rpc_host_endpoint_accept_state); + struct rpc_host_endpoint *endpoint = state->endpoint; + int sock, listen_sock, err; + struct samba_sockaddr peer_addr; + + sock = accept_recv(subreq, &listen_sock, &peer_addr, &err); + TALLOC_FREE(subreq); + if (sock == -1) { + /* What to do here? Just ignore the error and retry? */ + DBG_DEBUG("accept_recv failed: %s\n", strerror(err)); + tevent_req_error(req, err); + return; + } + + subreq = accept_send(state, state->ev, listen_sock); + if (tevent_req_nomem(subreq, req)) { + close(sock); + sock = -1; + return; + } + tevent_req_set_callback( + subreq, rpc_host_endpoint_accept_accepted, req); + + subreq = rpc_host_bind_read_send( + state, + state->ev, + dcerpc_binding_get_transport(endpoint->binding), + &sock, + &peer_addr); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback( + subreq, rpc_host_endpoint_accept_got_bind, req); +} + +/* + * Client sent us a DCERPC bind packet. + */ +static void rpc_host_endpoint_accept_got_bind(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_endpoint_accept_state *state = tevent_req_data( + req, struct rpc_host_endpoint_accept_state); + struct rpc_host_endpoint *endpoint = state->endpoint; + struct rpc_server *server = endpoint->server; + struct rpc_host_pending_client *pending = NULL; + struct rpc_host_client *client = NULL; + struct ncacn_packet *bind_pkt = NULL; + int ret; + int sock=-1; + + ret = rpc_host_bind_read_recv( + subreq, state, &sock, &client, &bind_pkt); + TALLOC_FREE(subreq); + if (ret != 0) { + DBG_DEBUG("rpc_host_bind_read_recv returned %s\n", + strerror(ret)); + goto fail; + } + + client->binding = dcerpc_binding_string(client, endpoint->binding); + if (client->binding == NULL) { + DBG_WARNING("dcerpc_binding_string failed, dropping client\n"); + goto fail; + } + + pending = talloc_zero(server, struct rpc_host_pending_client); + if (pending == NULL) { + DBG_WARNING("talloc failed, dropping client\n"); + goto fail; + } + pending->server = server; + pending->sock = sock; + pending->bind_pkt = talloc_move(pending, &bind_pkt); + pending->client = talloc_move(pending, &client); + talloc_set_destructor(pending, rpc_host_pending_client_destructor); + sock = -1; + + pending->hangup_wait = wait_for_read_send( + pending, state->ev, pending->sock, true); + if (pending->hangup_wait == NULL) { + DBG_WARNING("wait_for_read_send failed, dropping client\n"); + TALLOC_FREE(pending); + return; + } + tevent_req_set_callback( + pending->hangup_wait, rpc_host_client_exited, pending); + + DLIST_ADD_END(server->pending_clients, pending); + rpc_host_distribute_clients(server); + return; + +fail: + TALLOC_FREE(client); + if (sock != -1) { + close(sock); + } +} + +static int rpc_host_endpoint_accept_recv( + struct tevent_req *req, struct rpc_host_endpoint **ep) +{ + struct rpc_host_endpoint_accept_state *state = tevent_req_data( + req, struct rpc_host_endpoint_accept_state); + + *ep = state->endpoint; + + return tevent_req_simple_recv_unix(req); +} + +/* + * Full state for samba-dcerpcd. Everything else + * is hung off this. + */ +struct rpc_host_state { + struct tevent_context *ev; + struct rpc_host *host; + + bool is_ready; + const char *daemon_ready_progname; + struct tevent_immediate *ready_signal_immediate; + int *ready_signal_fds; + + size_t num_servers; + size_t num_prepared; +}; + +/* + * Tell whoever invoked samba-dcerpcd we're ready to + * serve. + */ +static void rpc_host_report_readiness( + struct tevent_context *ev, + struct tevent_immediate *im, + void *private_data) +{ + struct rpc_host_state *state = talloc_get_type_abort( + private_data, struct rpc_host_state); + size_t i, num_fds = talloc_array_length(state->ready_signal_fds); + + if (!state->is_ready) { + DBG_DEBUG("Not yet ready\n"); + return; + } + + for (i=0; i<num_fds; i++) { + uint8_t byte = 0; + ssize_t nwritten; + + do { + nwritten = write( + state->ready_signal_fds[i], + (void *)&byte, + sizeof(byte)); + } while ((nwritten == -1) && (errno == EINTR)); + + close(state->ready_signal_fds[i]); + } + + TALLOC_FREE(state->ready_signal_fds); +} + +/* + * Respond to a "are you ready" message. + */ +static bool rpc_host_ready_signal_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_host_state *state = talloc_get_type_abort( + private_data, struct rpc_host_state); + size_t num_fds = talloc_array_length(state->ready_signal_fds); + int *tmp = NULL; + + if (rec->msg_type != MSG_DAEMON_READY_FD) { + return false; + } + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + if (num_fds + 1 < num_fds) { + return false; + } + tmp = talloc_realloc(state, state->ready_signal_fds, int, num_fds+1); + if (tmp == NULL) { + return false; + } + state->ready_signal_fds = tmp; + + state->ready_signal_fds[num_fds] = rec->fds[0]; + rec->fds[0] = -1; + + tevent_schedule_immediate( + state->ready_signal_immediate, + state->ev, + rpc_host_report_readiness, + state); + + return false; +} + +/* + * Respond to a "what is your status" message. + */ +static bool rpc_host_dump_status_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_host_state *state = talloc_get_type_abort( + private_data, struct rpc_host_state); + struct rpc_host *host = state->host; + struct rpc_server **servers = host->servers; + size_t i, num_servers = talloc_array_length(servers); + FILE *f = NULL; + int fd; + + if (rec->msg_type != MSG_RPC_DUMP_STATUS) { + return false; + } + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + fd = dup(rec->fds[0]); + if (fd == -1) { + DBG_DEBUG("dup(%"PRIi64") failed: %s\n", + rec->fds[0], + strerror(errno)); + return false; + } + + f = fdopen(fd, "w"); + if (f == NULL) { + DBG_DEBUG("fdopen failed: %s\n", strerror(errno)); + close(fd); + return false; + } + + for (i=0; i<num_servers; i++) { + struct rpc_server *server = servers[i]; + size_t j, num_workers = talloc_array_length(server->workers); + size_t active_workers = 0; + + for (j=0; j<num_workers; j++) { + if (server->workers[j].pid != -1) { + active_workers += 1; + } + } + + fprintf(f, + "%s: active_workers=%zu\n", + server->rpc_server_exe, + active_workers); + + for (j=0; j<num_workers; j++) { + struct rpc_work_process *w = &server->workers[j]; + + if (w->pid == (pid_t)-1) { + continue; + } + + fprintf(f, + " worker[%zu]: pid=%d, num_associations=%"PRIu32", num_connections=%"PRIu32"\n", + j, + (int)w->pid, + w->num_associations, + w->num_connections); + } + } + + fclose(f); + + return false; +} + +static void rpc_host_server_setup_done(struct tevent_req *subreq); +static void rpc_host_endpoint_failed(struct tevent_req *subreq); + +/* + * Async startup for samba-dcerpcd. + */ +static struct tevent_req *rpc_host_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx, + char *servers, + int ready_signal_fd, + const char *daemon_ready_progname, + bool is_np_helper) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct rpc_host_state *state = NULL; + struct rpc_host *host = NULL; + struct tevent_signal *se = NULL; + char *epmdb_path = NULL; + char *exe = NULL; + size_t i, num_servers = strv_count(servers); + NTSTATUS status; + int ret; + + req = tevent_req_create(req, &state, struct rpc_host_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->daemon_ready_progname = daemon_ready_progname; + + state->ready_signal_immediate = tevent_create_immediate(state); + if (tevent_req_nomem(state->ready_signal_immediate, req)) { + return tevent_req_post(req, ev); + } + + if (ready_signal_fd != -1) { + state->ready_signal_fds = talloc_array(state, int, 1); + if (tevent_req_nomem(state->ready_signal_fds, req)) { + return tevent_req_post(req, ev); + } + state->ready_signal_fds[0] = ready_signal_fd; + } + + state->host = talloc_zero(state, struct rpc_host); + if (tevent_req_nomem(state->host, req)) { + return tevent_req_post(req, ev); + } + host = state->host; + + host->msg_ctx = msg_ctx; + host->np_helper = is_np_helper; + + ret = pipe(host->worker_stdin); + if (ret == -1) { + tevent_req_nterror(req, map_nt_error_from_unix(errno)); + return tevent_req_post(req, ev); + } + + host->servers = talloc_zero_array( + host, struct rpc_server *, num_servers); + if (tevent_req_nomem(host->servers, req)) { + return tevent_req_post(req, ev); + } + + se = tevent_add_signal(ev, state, SIGCHLD, 0, rpc_host_sigchld, host); + if (tevent_req_nomem(se, req)) { + return tevent_req_post(req, ev); + } + BlockSignals(false, SIGCHLD); + + status = messaging_register( + msg_ctx, + host, + MSG_RPC_WORKER_STATUS, + rpc_host_child_status_recv); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + status = messaging_register( + msg_ctx, req, MSG_SHUTDOWN, rpc_host_msg_shutdown); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + subreq = messaging_filtered_read_send( + state, ev, msg_ctx, rpc_host_ready_signal_filter, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + subreq = messaging_filtered_read_send( + state, ev, msg_ctx, rpc_host_dump_status_filter, state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + epmdb_path = lock_path(state, "epmdb.tdb"); + if (tevent_req_nomem(epmdb_path, req)) { + return tevent_req_post(req, ev); + } + + host->epmdb = tdb_wrap_open( + host, + epmdb_path, + 0, + TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH, + O_RDWR|O_CREAT, + 0644); + if (host->epmdb == NULL) { + DBG_DEBUG("tdb_wrap_open(%s) failed: %s\n", + epmdb_path, + strerror(errno)); + tevent_req_nterror(req, map_nt_error_from_unix(errno)); + return tevent_req_post(req, ev); + } + TALLOC_FREE(epmdb_path); + + for (exe = strv_next(servers, exe), i = 0; + exe != NULL; + exe = strv_next(servers, exe), i++) { + + DBG_DEBUG("server_setup for %s index %zu\n", exe, i); + + subreq = rpc_server_setup_send( + state, + ev, + host, + exe); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, rpc_host_server_setup_done, req); + } + + return req; +} + +/* + * Timer function called after we were initialized but no one + * connected. Shutdown. + */ +static void rpc_host_shutdown( + struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + DBG_DEBUG("Nobody connected -- shutting down\n"); + tevent_req_done(req); +} + +static void rpc_host_server_setup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_state *state = tevent_req_data( + req, struct rpc_host_state); + struct rpc_server *server = NULL; + struct rpc_host *host = state->host; + size_t i, num_servers = talloc_array_length(host->servers); + NTSTATUS status; + + status = rpc_server_setup_recv(subreq, host, &server); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_server_setup_recv returned %s, ignoring\n", + nt_errstr(status)); + host->servers = talloc_realloc( + host, + host->servers, + struct rpc_server *, + num_servers-1); + return; + } + + server->server_index = state->num_prepared; + host->servers[state->num_prepared] = server; + + state->num_prepared += 1; + + if (state->num_prepared < num_servers) { + return; + } + + for (i=0; i<num_servers; i++) { + size_t j, num_endpoints; + + server = host->servers[i]; + num_endpoints = talloc_array_length(server->endpoints); + + for (j=0; j<num_endpoints; j++) { + subreq = rpc_host_endpoint_accept_send( + state, state->ev, server->endpoints[j]); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback( + subreq, rpc_host_endpoint_failed, req); + } + } + + state->is_ready = true; + + if (state->daemon_ready_progname != NULL) { + daemon_ready(state->daemon_ready_progname); + } + + if (host->np_helper) { + /* + * If we're started as an np helper, and no one talks to + * us within 10 seconds, just shut ourselves down. + */ + host->np_helper_shutdown = tevent_add_timer( + state->ev, + state, + timeval_current_ofs(10, 0), + rpc_host_shutdown, + req); + if (tevent_req_nomem(host->np_helper_shutdown, req)) { + return; + } + } + + tevent_schedule_immediate( + state->ready_signal_immediate, + state->ev, + rpc_host_report_readiness, + state); +} + +/* + * Log accept fail on an endpoint. + */ +static void rpc_host_endpoint_failed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct rpc_host_state *state = tevent_req_data( + req, struct rpc_host_state); + struct rpc_host_endpoint *endpoint = NULL; + char *binding_string = NULL; + int ret; + + ret = rpc_host_endpoint_accept_recv(subreq, &endpoint); + TALLOC_FREE(subreq); + + binding_string = dcerpc_binding_string(state, endpoint->binding); + DBG_DEBUG("rpc_host_endpoint_accept_recv for %s returned %s\n", + binding_string, + strerror(ret)); + TALLOC_FREE(binding_string); +} + +static NTSTATUS rpc_host_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static int rpc_host_pidfile_create( + struct messaging_context *msg_ctx, + const char *progname, + int ready_signal_fd) +{ + const char *piddir = lp_pid_directory(); + size_t len = strlen(piddir) + strlen(progname) + 6; + char pidFile[len]; + pid_t existing_pid; + int fd, ret; + + snprintf(pidFile, + sizeof(pidFile), + "%s/%s.pid", + piddir, progname); + + ret = pidfile_path_create(pidFile, &fd, &existing_pid); + if (ret == 0) { + /* leak fd */ + return 0; + } + + if (ret != EAGAIN) { + DBG_DEBUG("pidfile_path_create() failed: %s\n", + strerror(ret)); + return ret; + } + + DBG_DEBUG("%s pid %d exists\n", progname, (int)existing_pid); + + if (ready_signal_fd != -1) { + NTSTATUS status = messaging_send_iov( + msg_ctx, + pid_to_procid(existing_pid), + MSG_DAEMON_READY_FD, + NULL, + 0, + &ready_signal_fd, + 1); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not send ready_signal_fd: %s\n", + nt_errstr(status)); + } + } + + return EAGAIN; +} + +static void samba_dcerpcd_stdin_handler( + struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + char c; + + if (read(0, &c, 1) != 1) { + /* we have reached EOF on stdin, which means the + parent has exited. Shutdown the server */ + tevent_req_done(req); + } +} + +/* + * samba-dcerpcd microservice startup ! + */ +int main(int argc, const char *argv[]) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *progname = getprogname(); + TALLOC_CTX *frame = NULL; + struct tevent_context *ev_ctx = NULL; + struct messaging_context *msg_ctx = NULL; + struct tevent_req *req = NULL; + char *servers = NULL; + const char *arg = NULL; + size_t num_servers; + poptContext pc; + int ret, err; + NTSTATUS status; + bool log_stdout; + bool ok; + + int libexec_rpcds = 0; + int np_helper = 0; + int ready_signal_fd = -1; + + struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL; + struct poptOption long_options[] = { + POPT_AUTOHELP + { + .longName = "libexec-rpcds", + .argInfo = POPT_ARG_NONE, + .arg = &libexec_rpcds, + .descrip = "Use all rpcds in libexec", + }, + { + .longName = "ready-signal-fd", + .argInfo = POPT_ARG_INT, + .arg = &ready_signal_fd, + .descrip = "fd to close when initialized", + }, + { + .longName = "np-helper", + .argInfo = POPT_ARG_NONE, + .arg = &np_helper, + .descrip = "Internal named pipe server", + }, + POPT_COMMON_SAMBA + POPT_COMMON_DAEMON + POPT_COMMON_VERSION + POPT_TABLEEND + }; + + { + const char *fd_params[] = { "ready-signal-fd", }; + + closefrom_except_fd_params( + 3, ARRAY_SIZE(fd_params), fd_params, argc, argv); + } + + talloc_enable_null_tracking(); + frame = talloc_stackframe(); + umask(0); + sec_init(); + smb_init_locale(); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_SERVER, + true /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(frame); + exit(ENOMEM); + } + + pc = samba_popt_get_context(getprogname(), + argc, + argv, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(frame); + exit(1); + } + + poptSetOtherOptionHelp( + pc, "[OPTIONS] [SERVICE_1 SERVICE_2 .. SERVICE_N]"); + + ret = poptGetNextOpt(pc); + + if (ret != -1) { + if (ret >= 0) { + fprintf(stderr, + "\nGot unexpected option %d\n", + ret); + } else if (ret == POPT_ERROR_BADOPT) { + fprintf(stderr, + "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), + poptStrerror(ret)); + } else { + fprintf(stderr, + "\npoptGetNextOpt returned %s\n", + poptStrerror(ret)); + } + + poptFreeContext(pc); + TALLOC_FREE(frame); + exit(1); + } + + while ((arg = poptGetArg(pc)) != NULL) { + ret = strv_add(frame, &servers, arg); + if (ret != 0) { + DBG_ERR("strv_add() failed\n"); + poptFreeContext(pc); + TALLOC_FREE(frame); + exit(1); + } + } + + log_stdout = (debug_get_log_type() == DEBUG_STDOUT); + if (log_stdout) { + setup_logging(progname, DEBUG_STDOUT); + } else { + setup_logging(progname, DEBUG_FILE); + } + + /* + * If "rpc start on demand helpers = true" in smb.conf we must + * not start as standalone, only on demand from + * local_np_connect() functions. Log an error message telling + * the admin how to fix and then exit. + */ + if (lp_rpc_start_on_demand_helpers() && np_helper == 0) { + DBG_ERR("Cannot start in standalone mode if smb.conf " + "[global] setting " + "\"rpc start on demand helpers = true\" - " + "exiting\n"); + TALLOC_FREE(frame); + exit(1); + } + + if (libexec_rpcds != 0) { + ret = rpc_host_list_servers( + dyn_SAMBA_LIBEXECDIR, frame, &servers); + if (ret != 0) { + DBG_ERR("Could not list libexec: %s\n", + strerror(ret)); + poptFreeContext(pc); + TALLOC_FREE(frame); + exit(1); + } + } + + num_servers = strv_count(servers); + if (num_servers == 0) { + poptPrintUsage(pc, stderr, 0); + poptFreeContext(pc); + TALLOC_FREE(frame); + exit(1); + } + + poptFreeContext(pc); + + cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg(); + + if (log_stdout && cmdline_daemon_cfg->fork) { + DBG_ERR("Can't log to stdout unless in foreground\n"); + TALLOC_FREE(frame); + exit(1); + } + + msg_ctx = global_messaging_context(); + if (msg_ctx == NULL) { + DBG_ERR("messaging_init() failed\n"); + TALLOC_FREE(frame); + exit(1); + } + ev_ctx = messaging_tevent_context(msg_ctx); + + if (cmdline_daemon_cfg->fork) { + become_daemon( + true, + cmdline_daemon_cfg->no_process_group, + log_stdout); + + status = reinit_after_fork(msg_ctx, ev_ctx, false); + if (!NT_STATUS_IS_OK(status)) { + exit_daemon("reinit_after_fork() failed", + map_errno_from_nt_status(status)); + } + } else { + DBG_DEBUG("Calling daemon_status\n"); + daemon_status(progname, "Starting process ... "); + } + + BlockSignals(true, SIGPIPE); + + dump_core_setup(progname, lp_logfile(frame, lp_sub)); + + reopen_logs(); + + DBG_STARTUP_NOTICE("%s version %s started.\n%s\n", + progname, + samba_version_string(), + samba_copyright_string()); + + (void)winbind_off(); + ok = init_guest_session_info(frame); + (void)winbind_on(); + if (!ok) { + DBG_ERR("init_guest_session_info failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + ret = rpc_host_pidfile_create(msg_ctx, progname, ready_signal_fd); + if (ret != 0) { + DBG_DEBUG("rpc_host_pidfile_create failed: %s\n", + strerror(ret)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + req = rpc_host_send( + ev_ctx, + ev_ctx, + msg_ctx, + servers, + ready_signal_fd, + cmdline_daemon_cfg->fork ? NULL : progname, + np_helper != 0); + if (req == NULL) { + DBG_ERR("rpc_host_send failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + if (!cmdline_daemon_cfg->fork) { + struct stat st; + if (fstat(0, &st) != 0) { + DBG_DEBUG("fstat(0) failed: %s\n", + strerror(errno)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) { + tevent_add_fd( + ev_ctx, + ev_ctx, + 0, + TEVENT_FD_READ, + samba_dcerpcd_stdin_handler, + req); + } + } + + ok = tevent_req_poll_unix(req, ev_ctx, &err); + if (!ok) { + DBG_ERR("tevent_req_poll_unix failed: %s\n", + strerror(err)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + status = rpc_host_recv(req); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("rpc_host_recv returned %s\n", nt_errstr(status)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + TALLOC_FREE(frame); + + return 0; +} diff --git a/source3/rpc_server/rpc_ncacn_np.c b/source3/rpc_server/rpc_ncacn_np.c new file mode 100644 index 0000000..03618df --- /dev/null +++ b/source3/rpc_server/rpc_ncacn_np.c @@ -0,0 +1,217 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1998, + * Largely re-written : 2005 + * Copyright (C) Jeremy Allison 1998 - 2005 + * Copyright (C) Simo Sorce 2010 + * Copyright (C) Andrew Bartlett 2011 + * + * 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 "rpc_client/cli_pipe.h" +#include "rpc_dce.h" +#include "../libcli/named_pipe_auth/npa_tstream.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/auth.h" +#include "../auth/auth_sam_reply.h" +#include "../auth/auth_util.h" +#include "auth.h" +#include "rpc_server/rpc_pipes.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/util/tevent_ntstatus.h" +#include "rpc_server/rpc_config.h" +#include "librpc/ndr/ndr_table.h" +#include "rpc_server/rpc_server.h" +#include "librpc/rpc/dcerpc_util.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +struct np_proxy_state { + uint16_t file_type; + uint16_t device_state; + uint64_t allocation_size; + struct tstream_context *npipe; + struct tevent_queue *read_queue; + struct tevent_queue *write_queue; +}; + +struct npa_state *npa_state_init(TALLOC_CTX *mem_ctx) +{ + struct npa_state *npa; + + npa = talloc_zero(mem_ctx, struct npa_state); + if (npa == NULL) { + return NULL; + } + + npa->read_queue = tevent_queue_create(npa, "npa_cli_read"); + if (npa->read_queue == NULL) { + DEBUG(0, ("tevent_queue_create failed\n")); + goto fail; + } + + npa->write_queue = tevent_queue_create(npa, "npa_cli_write"); + if (npa->write_queue == NULL) { + DEBUG(0, ("tevent_queue_create failed\n")); + goto fail; + } + + return npa; +fail: + talloc_free(npa); + return NULL; +} + +/** + * @brief Create a new DCERPC Binding Handle which uses a local dispatch function. + * + * @param[in] mem_ctx The memory context to use. + * + * @param[in] ndr_table Normally the ndr_table_<name>. + * + * @param[in] remote_address The info about the connected client. + * + * @param[in] serversupplied_info The server supplied authentication function. + * + * @param[in] msg_ctx The messaging context that can be used by the server + * + * @param[out] binding_handle A pointer to store the connected + * dcerpc_binding_handle + * + * @return NT_STATUS_OK on success, a corresponding NT status if an + * error occurred. + * + * @code + * struct dcerpc_binding_handle *winreg_binding; + * NTSTATUS status; + * + * status = rpcint_binding_handle(tmp_ctx, + * &ndr_table_winreg, + * p->remote_address, + * p->session_info, + * p->msg_ctx + * &winreg_binding); + * @endcode + */ +NTSTATUS rpcint_binding_handle(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *ndr_table, + const struct tsocket_address *remote_address, + const struct tsocket_address *local_address, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct dcerpc_binding_handle **binding_handle) +{ + struct rpc_pipe_client *rpccli = NULL; + NTSTATUS status; + + status = rpc_pipe_open_local_np( + mem_ctx, + ndr_table, + NULL, + remote_address, + NULL, + local_address, + session_info, + &rpccli); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_pipe_open_local_np failed: %s\n", + nt_errstr(status)); + goto fail; + } + + *binding_handle = rpccli->binding_handle; + return NT_STATUS_OK; +fail: + TALLOC_FREE(rpccli); + return status; +} + +/** + * @brief Create a new RPC client context which uses a local dispatch function + * or a remote transport, depending on rpc_server configuration for the + * specific service. + * + * @param[in] mem_ctx The memory context to use. + * + * @param[in] abstract_syntax Normally the syntax_id of the autogenerated + * ndr_table_<name>. + * + * @param[in] serversupplied_info The server supplied authentication function. + * + * @param[in] remote_address The client address information. + * + * @param[in] msg_ctx The messaging context to use. + * + * @param[out] presult A pointer to store the connected rpc client pipe. + * + * @return NT_STATUS_OK on success, a corresponding NT status if an + * error occurred. + * + * @code + * struct rpc_pipe_client *winreg_pipe; + * NTSTATUS status; + * + * status = rpc_pipe_open_interface(tmp_ctx, + * &ndr_table_winreg.syntax_id, + * p->session_info, + * remote_address, + * &winreg_pipe); + * @endcode + */ + +NTSTATUS rpc_pipe_open_interface(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + const struct auth_session_info *session_info, + const struct tsocket_address *remote_address, + const struct tsocket_address *local_address, + struct messaging_context *msg_ctx, + struct rpc_pipe_client **cli_pipe) +{ + struct rpc_pipe_client *cli = NULL; + NTSTATUS status; + + if (cli_pipe != NULL) { + if (rpccli_is_connected(*cli_pipe)) { + return NT_STATUS_OK; + } else { + TALLOC_FREE(*cli_pipe); + } + } + + status = rpc_pipe_open_local_np( + mem_ctx, + table, + NULL, + remote_address, + NULL, + local_address, + session_info, + &cli); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Could not connect to %s pipe: %s\n", + table->name, + nt_errstr(status)); + return status; + } + + if (NT_STATUS_IS_OK(status) && cli_pipe != NULL) { + *cli_pipe = cli; + } + return status; +} diff --git a/source3/rpc_server/rpc_ncacn_np.h b/source3/rpc_server/rpc_ncacn_np.h new file mode 100644 index 0000000..acbc5f2 --- /dev/null +++ b/source3/rpc_server/rpc_ncacn_np.h @@ -0,0 +1,58 @@ +/* + Unix SMB/Netbios implementation. + RPC Server Headers + Copyright (C) Simo Sorce 2010 + + 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 _RPC_NCACN_NP_H_ +#define _RPC_NCACN_NP_H_ + +struct dcerpc_binding_handle; +struct ndr_interface_table; +struct tsocket_address; +struct dcesrv_context; +struct dcesrv_endpoint; + +struct npa_state { + struct tstream_context *stream; + + struct tevent_queue *read_queue; + struct tevent_queue *write_queue; + + uint64_t allocation_size; + uint16_t device_state; + uint16_t file_type; + + void *private_data; +}; + +struct npa_state *npa_state_init(TALLOC_CTX *mem_ctx); + +NTSTATUS rpcint_binding_handle(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *ndr_table, + const struct tsocket_address *remote_address, + const struct tsocket_address *local_address, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct dcerpc_binding_handle **binding_handle); +NTSTATUS rpc_pipe_open_interface(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + const struct auth_session_info *session_info, + const struct tsocket_address *remote_address, + const struct tsocket_address *local_address, + struct messaging_context *msg_ctx, + struct rpc_pipe_client **cli_pipe); +#endif /* _RPC_NCACN_NP_H_ */ diff --git a/source3/rpc_server/rpc_pipes.h b/source3/rpc_server/rpc_pipes.h new file mode 100644 index 0000000..17922b0 --- /dev/null +++ b/source3/rpc_server/rpc_pipes.h @@ -0,0 +1,73 @@ +/* + Unix SMB/Netbios implementation. + RPC Server Headers + Copyright (C) Andrew Tridgell 1992-1997 + Copyright (C) Luke Kenneth Casson Leighton 1996-1997 + Copyright (C) Paul Ashton 1997 + Copyright (C) Jeremy Allison 2000-2004 + Copyright (C) Simo Sorce 2010-2011 + + 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 _RPC_PIPES_H_ +#define _RPC_PIPES_H_ + +#include "source3/librpc/rpc/dcerpc.h" + +struct tsocket_address; +struct pipes_struct; +struct dcesrv_context; + +/* + * DCE/RPC-specific samba-internal-specific handling of data on + * NamedPipes. + */ +struct pipes_struct { + enum dcerpc_transport_t transport; + + struct messaging_context *msg_ctx; + + /* + * Set the DCERPC_FAULT to return. + */ + int fault_state; + + /* This context is used for PDU data and is freed between each pdu. + Don't use for pipe state storage. */ + TALLOC_CTX *mem_ctx; + + /* handle database to use on this pipe. */ + struct dcesrv_call_state *dce_call; +}; + +bool check_open_pipes(void); +size_t num_pipe_handles(void); + +void *create_policy_hnd(struct pipes_struct *p, + struct policy_handle *hnd, + uint8_t handle_type, + void *data_ptr); + +void *_find_policy_by_hnd(struct pipes_struct *p, + const struct policy_handle *hnd, + uint8_t handle_type, + NTSTATUS *pstatus); +#define find_policy_by_hnd(_p, _hnd, _hnd_type, _type, _pstatus) \ + (_type *)_find_policy_by_hnd((_p), (_hnd), (_hnd_type), (_pstatus)); + +bool close_policy_hnd(struct pipes_struct *p, struct policy_handle *hnd); +bool pipe_access_check(struct pipes_struct *p); + +#endif /* _RPC_PIPES_H_ */ diff --git a/source3/rpc_server/rpc_server.c b/source3/rpc_server/rpc_server.c new file mode 100644 index 0000000..a60f429 --- /dev/null +++ b/source3/rpc_server/rpc_server.c @@ -0,0 +1,309 @@ +/* + Unix SMB/Netbios implementation. + Generic infrstructure for RPC Daemons + Copyright (C) Simo Sorce 2010 + Copyright (C) Andrew Bartlett 2011 + Copyright (C) Andreas Schneider 2011 + + 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 "librpc/rpc/dcesrv_core.h" +#include "rpc_server/rpc_pipes.h" +#include "rpc_server/rpc_server.h" +#include "rpc_server/rpc_config.h" +#include "rpc_dce.h" +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/auth.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/named_pipe_auth/npa_tstream.h" +#include "../auth/auth_sam_reply.h" +#include "auth.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "rpc_server/srv_pipe_hnd.h" +#include "lib/util/idtree_random.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/* Start listening on the appropriate unix socket and setup all is needed to + * dispatch requests to the pipes rpc implementation */ + +struct dcerpc_ncacn_listen_state { + int fd; + + struct tevent_context *ev_ctx; + struct messaging_context *msg_ctx; + struct dcesrv_context *dce_ctx; + struct dcesrv_endpoint *endpoint; + dcerpc_ncacn_termination_fn termination_fn; + void *termination_data; +}; + +static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn, + const char *reason); + +NTSTATUS dcesrv_auth_gensec_prepare( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *call, + struct gensec_security **out, + void *private_data) +{ + struct gensec_security *gensec = NULL; + NTSTATUS status; + + if (out == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = auth_generic_prepare(mem_ctx, + call->conn->remote_address, + call->conn->local_address, + "DCE/RPC", + &gensec); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to prepare gensec: %s\n", nt_errstr(status)); + return status; + } + + *out = gensec; + + return NT_STATUS_OK; +} + +void dcesrv_log_successful_authz( + struct dcesrv_call_state *call, + void *private_data) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct auth4_context *auth4_context = NULL; + struct dcesrv_auth *auth = call->auth_state; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport( + call->conn->endpoint->ep_description); + const char *auth_type = derpc_transport_string_by_transport(transport); + const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE; + NTSTATUS status; + + if (frame == NULL) { + DBG_ERR("No memory\n"); + return; + } + + if (transport == NCACN_NP) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB; + } + + become_root(); + status = make_auth4_context(frame, &auth4_context); + unbecome_root(); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Unable to make auth context for authz log.\n"); + TALLOC_FREE(frame); + return; + } + + /* + * Log the authorization to this RPC interface. This + * covered ncacn_np pass-through auth, and anonymous + * DCE/RPC (eg epmapper, netlogon etc) + */ + log_successful_authz_event(auth4_context->msg_ctx, + auth4_context->lp_ctx, + call->conn->remote_address, + call->conn->local_address, + "DCE/RPC", + auth_type, + transport_protection, + auth->session_info, + NULL /* client_audit_info */, + NULL /* server_audit_info */); + + auth->auth_audited = true; + + TALLOC_FREE(frame); +} + +static int dcesrv_assoc_group_destructor(struct dcesrv_assoc_group *assoc_group) +{ + int ret; + ret = idr_remove(assoc_group->dce_ctx->assoc_groups_idr, + assoc_group->id); + if (ret != 0) { + DBG_ERR("Failed to remove assoc_group 0x%08x\n", + assoc_group->id); + } + return 0; +} + +static NTSTATUS dcesrv_assoc_group_new(struct dcesrv_call_state *call) +{ + struct dcesrv_connection *conn = call->conn; + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + int id; + + assoc_group = talloc_zero(conn, struct dcesrv_assoc_group); + if (assoc_group == NULL) { + return NT_STATUS_NO_MEMORY; + } + + id = idr_get_new_random(dce_ctx->assoc_groups_idr, + assoc_group, + 1, + UINT16_MAX); + if (id == -1) { + TALLOC_FREE(assoc_group); + DBG_ERR("Out of association groups!\n"); + return NT_STATUS_RPC_OUT_OF_RESOURCES; + } + + assoc_group->transport = transport; + assoc_group->id = id; + assoc_group->dce_ctx = dce_ctx; + + call->conn->assoc_group = assoc_group; + + talloc_set_destructor(assoc_group, dcesrv_assoc_group_destructor); + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_assoc_group_reference(struct dcesrv_call_state *call, + uint32_t assoc_group_id) +{ + struct dcesrv_connection *conn = call->conn; + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + void *id_ptr = NULL; + + /* find an association group given a assoc_group_id */ + id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, assoc_group_id); + if (id_ptr == NULL) { + /* + * FIXME If the association group is not found it has + * been created in other process (preforking daemons). + * Until this is properly fixed we just create a new + * association group in this process + */ + DBG_NOTICE("Failed to find assoc_group 0x%08x in this " + "server process, creating a new one\n", + assoc_group_id); + return dcesrv_assoc_group_new(call); + } + assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group); + + if (assoc_group->transport != transport) { + const char *at = + derpc_transport_string_by_transport( + assoc_group->transport); + const char *ct = + derpc_transport_string_by_transport( + transport); + + DBG_NOTICE("assoc_group 0x%08x (transport %s) " + "is not available on transport %s\n", + assoc_group_id, at, ct); + return NT_STATUS_UNSUCCESSFUL; + } + + conn->assoc_group = talloc_reference(conn, assoc_group); + return NT_STATUS_OK; +} + +NTSTATUS dcesrv_assoc_group_find( + struct dcesrv_call_state *call, + void *private_data) +{ + uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id; + + if (assoc_group_id != 0) { + return dcesrv_assoc_group_reference(call, assoc_group_id); + } + + /* If not requested by client create a new association group */ + return dcesrv_assoc_group_new(call); +} + +void dcesrv_transport_terminate_connection(struct dcesrv_connection *dce_conn, + const char *reason) +{ + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + dce_conn->transport.private_data, + struct dcerpc_ncacn_conn); + + ncacn_terminate_connection(ncacn_conn, reason); +} + +static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn, + const char *reason) +{ + if (reason == NULL) { + reason = "Unknown reason"; + } + + DBG_NOTICE("Terminating connection - '%s'\n", reason); + + talloc_free(conn); +} + +NTSTATUS dcesrv_endpoint_by_ncacn_np_name(struct dcesrv_context *dce_ctx, + const char *pipe_name, + struct dcesrv_endpoint **out) +{ + struct dcesrv_endpoint *e = NULL; + + for (e = dce_ctx->endpoint_list; e; e = e->next) { + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(e->ep_description); + const char *endpoint = NULL; + + if (transport != NCACN_NP) { + continue; + } + + endpoint = dcerpc_binding_get_string_option(e->ep_description, + "endpoint"); + if (endpoint == NULL) { + continue; + } + + if (strncmp(endpoint, "\\pipe\\", 6) == 0) { + endpoint += 6; + } + + if (strequal(endpoint, pipe_name)) { + *out = e; + return NT_STATUS_OK; + } + } + + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +struct pipes_struct *dcesrv_get_pipes_struct(struct dcesrv_connection *conn) +{ + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + conn->transport.private_data, + struct dcerpc_ncacn_conn); + + return &ncacn_conn->p; +} + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/rpc_server.h b/source3/rpc_server/rpc_server.h new file mode 100644 index 0000000..73cd78a --- /dev/null +++ b/source3/rpc_server/rpc_server.h @@ -0,0 +1,72 @@ +/* + * RPC Server helper headers + * Almost completely rewritten by (C) Jeremy Allison 2005 - 2010 + * Copyright (C) Simo Sorce <idra@samba.org> - 2010 + * + * 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 _RPC_SERVER_H_ +#define _RPC_SERVER_H_ + +#include "librpc/rpc/rpc_common.h" /* For enum dcerpc_transport_t */ + +#include "librpc/rpc/dcesrv_core.h" +#include "rpc_pipes.h" + +struct auth_session_info; +struct cli_credentials; + +typedef void (*dcerpc_ncacn_termination_fn)(struct dcesrv_connection *, + void *); + +struct dcerpc_ncacn_conn { + struct dcerpc_ncacn_conn *prev, *next; + int sock; + + struct pipes_struct p; + dcerpc_ncacn_termination_fn termination_fn; + void *termination_data; + + struct dcesrv_endpoint *endpoint; + + char *remote_client_name; + char *local_server_name; +}; + +void set_incoming_fault(struct pipes_struct *p); +void process_complete_pdu(struct pipes_struct *p, struct ncacn_packet *pkt); + +NTSTATUS dcesrv_auth_gensec_prepare( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *call, + struct gensec_security **out, + void *private_data); +void dcesrv_log_successful_authz( + struct dcesrv_call_state *call, + void *private_data); +NTSTATUS dcesrv_assoc_group_find( + struct dcesrv_call_state *call, + void *private_data); + +NTSTATUS dcesrv_endpoint_by_ncacn_np_name(struct dcesrv_context *dce_ctx, + const char *endpoint, + struct dcesrv_endpoint **out); + +struct pipes_struct *dcesrv_get_pipes_struct(struct dcesrv_connection *conn); + +void dcesrv_transport_terminate_connection(struct dcesrv_connection *dce_conn, + const char *reason); + +#endif /* _PRC_SERVER_H_ */ diff --git a/source3/rpc_server/rpc_sock_helper.c b/source3/rpc_server/rpc_sock_helper.c new file mode 100644 index 0000000..364b889 --- /dev/null +++ b/source3/rpc_server/rpc_sock_helper.c @@ -0,0 +1,399 @@ +/* + * Unix SMB/CIFS implementation. + * + * RPC Socket Helper + * + * 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 "ntdomain.h" + +#include "../lib/tsocket/tsocket.h" +#include "librpc/rpc/dcesrv_core.h" +#include "rpc_server/rpc_sock_helper.h" +#include "librpc/ndr/ndr_table.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static NTSTATUS dcesrv_create_ncacn_np_socket( + struct dcerpc_binding *b, int *out_fd) +{ + char *np_dir = NULL; + int fd = -1; + NTSTATUS status; + const char *endpoint; + char *endpoint_normalized = NULL; + char *p = NULL; + + endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + if (endpoint == NULL) { + DBG_ERR("Endpoint mandatory for named pipes\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* The endpoint string from IDL can be mixed uppercase and case is + * normalized by smbd on connection */ + endpoint_normalized = strlower_talloc(talloc_tos(), endpoint); + if (endpoint_normalized == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* The endpoint string from IDL can be prefixed by \pipe\ */ + p = endpoint_normalized; + if (strncmp(p, "\\pipe\\", 6) == 0) { + p += 6; + } + + /* + * As lp_ncalrpc_dir() should have 0755, but + * lp_ncalrpc_dir()/np should have 0700, we need to + * create lp_ncalrpc_dir() first. + */ + if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) { + status = map_nt_error_from_unix_common(errno); + DBG_ERR("Failed to create pipe directory %s - %s\n", + lp_ncalrpc_dir(), strerror(errno)); + goto out; + } + + np_dir = talloc_asprintf(talloc_tos(), "%s/np", lp_ncalrpc_dir()); + if (!np_dir) { + status = NT_STATUS_NO_MEMORY; + DBG_ERR("Out of memory\n"); + goto out; + } + + if (!directory_create_or_exist_strict(np_dir, geteuid(), 0700)) { + status = map_nt_error_from_unix_common(errno); + DBG_ERR("Failed to create pipe directory %s - %s\n", + np_dir, strerror(errno)); + goto out; + } + + fd = create_pipe_sock(np_dir, p, 0700); + if (fd == -1) { + status = map_nt_error_from_unix_common(errno); + DBG_ERR("Failed to create ncacn_np socket! '%s/%s': %s\n", + np_dir, p, strerror(errno)); + goto out; + } + + DBG_DEBUG("Opened pipe socket fd %d for %s\n", fd, p); + + *out_fd = fd; + + status = NT_STATUS_OK; + +out: + TALLOC_FREE(endpoint_normalized); + TALLOC_FREE(np_dir); + return status; +} + +/******************************************************************** + * Start listening on the tcp/ip socket + ********************************************************************/ + +static NTSTATUS dcesrv_create_ncacn_ip_tcp_socket( + const struct sockaddr_storage *ifss, uint16_t *port, int *out_fd) +{ + int fd = -1; + + if (*port == 0) { + static uint16_t low = 0; + uint16_t i; + + if (low == 0) { + low = lp_rpc_low_port(); + } + + for (i = low; i <= lp_rpc_high_port(); i++) { + fd = open_socket_in(SOCK_STREAM, ifss, i, false); + if (fd >= 0) { + *port = i; + low = i+1; + break; + } + } + } else { + fd = open_socket_in(SOCK_STREAM, ifss, *port, true); + } + + if (fd < 0) { + DBG_ERR("Failed to create socket on port %u!\n", *port); + return map_nt_error_from_unix(-fd); + } + + /* ready to listen */ + set_socket_options(fd, "SO_KEEPALIVE"); + set_socket_options(fd, lp_socket_options()); + + DBG_DEBUG("Opened ncacn_ip_tcp socket fd %d for port %u\n", fd, *port); + + *out_fd = fd; + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_create_ncacn_ip_tcp_sockets( + struct dcerpc_binding *b, + TALLOC_CTX *mem_ctx, + size_t *pnum_fds, + int **pfds) +{ + uint16_t port = 0; + char port_str[11]; + const char *endpoint = NULL; + size_t i = 0, num_fds; + int *fds = NULL; + struct samba_sockaddr *addrs = NULL; + NTSTATUS status = NT_STATUS_INVALID_PARAMETER; + bool ok; + + endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + if (endpoint != NULL) { + port = atoi(endpoint); + } + + if (lp_interfaces() && lp_bind_interfaces_only()) { + num_fds = iface_count(); + } else { + num_fds = 1; +#ifdef HAVE_IPV6 + num_fds += 1; +#endif + } + + addrs = talloc_array(mem_ctx, struct samba_sockaddr, num_fds); + if (addrs == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + fds = talloc_array(mem_ctx, int, num_fds); + if (fds == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* + * Fill "addrs" + */ + + if (lp_interfaces() && lp_bind_interfaces_only()) { + for (i=0; i<num_fds; i++) { + const struct sockaddr_storage *ifss = + iface_n_sockaddr_storage(i); + + ok = sockaddr_storage_to_samba_sockaddr( + &addrs[i], ifss); + if (!ok) { + i = 0; /* nothing to close */ + goto fail; + } + } + } else { + struct sockaddr_storage ss = { .ss_family = 0 }; + +#ifdef HAVE_IPV6 + ok = interpret_string_addr( + &ss, "::", AI_NUMERICHOST|AI_PASSIVE); + if (!ok) { + goto fail; + } + ok = sockaddr_storage_to_samba_sockaddr(&addrs[0], &ss); + if (!ok) { + goto fail; + } +#endif + ok = interpret_string_addr( + &ss, "0.0.0.0", AI_NUMERICHOST|AI_PASSIVE); + if (!ok) { + goto fail; + } + + /* num_fds set above depending on HAVE_IPV6 */ + ok = sockaddr_storage_to_samba_sockaddr( + &addrs[num_fds-1], &ss); + if (!ok) { + goto fail; + } + } + + for (i=0; i<num_fds; i++) { + status = dcesrv_create_ncacn_ip_tcp_socket( + &addrs[i].u.ss, &port, &fds[i]); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + samba_sockaddr_set_port(&addrs[i], port); + } + + /* Set the port in the endpoint */ + snprintf(port_str, sizeof(port_str), "%"PRIu16, port); + + status = dcerpc_binding_set_string_option(b, "endpoint", port_str); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set binding endpoint '%s': %s\n", + port_str, nt_errstr(status)); + goto fail; + } + + TALLOC_FREE(addrs); + + *pfds = fds; + *pnum_fds = num_fds; + + return NT_STATUS_OK; + +fail: + while (i > 0) { + close(fds[i-1]); + i -= 1; + } + TALLOC_FREE(fds); + TALLOC_FREE(addrs); + return status; +} + +/******************************************************************** + * Start listening on the ncalrpc socket + ********************************************************************/ + +static NTSTATUS dcesrv_create_ncalrpc_socket( + struct dcerpc_binding *b, int *out_fd) +{ + int fd = -1; + const char *endpoint = NULL; + NTSTATUS status; + + endpoint = dcerpc_binding_get_string_option(b, "endpoint"); + if (endpoint == NULL) { + /* + * No identifier specified: use DEFAULT or SMBD. + * + * When role is AD DC we run two rpc server instances, the one + * started by 'samba' and the one embedded in 'smbd'. + * Avoid listening in DEFAULT socket for NCALRPC as both + * servers will race to accept connections. In this case smbd + * will listen in SMBD socket and rpcint binding handle + * implementation will pick the right socket to use. + * + * TODO: DO NOT hardcode this value anywhere else. Rather, + * specify no endpoint and let the epmapper worry about it. + */ + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + endpoint = "SMBD"; + } else { + endpoint = "DEFAULT"; + } + status = dcerpc_binding_set_string_option( + b, "endpoint", endpoint); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to set ncalrpc 'endpoint' binding " + "string option to '%s': %s\n", + endpoint, nt_errstr(status)); + return status; + } + } + + if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) { + status = map_nt_error_from_unix_common(errno); + DBG_ERR("Failed to create ncalrpc directory '%s': %s\n", + lp_ncalrpc_dir(), strerror(errno)); + goto out; + } + + fd = create_pipe_sock(lp_ncalrpc_dir(), endpoint, 0755); + if (fd == -1) { + status = map_nt_error_from_unix_common(errno); + DBG_ERR("Failed to create ncalrpc socket '%s/%s': %s\n", + lp_ncalrpc_dir(), endpoint, strerror(errno)); + goto out; + } + + DBG_DEBUG("Opened ncalrpc socket fd '%d' for '%s/%s'\n", + fd, lp_ncalrpc_dir(), endpoint); + + *out_fd = fd; + + return NT_STATUS_OK; + +out: + return status; +} + +NTSTATUS dcesrv_create_binding_sockets( + struct dcerpc_binding *b, + TALLOC_CTX *mem_ctx, + size_t *pnum_fds, + int **pfds) +{ + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(b); + size_t i, num_fds = 1; + int *fds = NULL; + NTSTATUS status; + + if ((transport == NCALRPC) || (transport == NCACN_NP)) { + fds = talloc(mem_ctx, int); + if (fds == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + switch(transport) { + case NCALRPC: + status = dcesrv_create_ncalrpc_socket(b, fds); + break; + case NCACN_NP: + status = dcesrv_create_ncacn_np_socket(b, fds); + break; + case NCACN_IP_TCP: + status = dcesrv_create_ncacn_ip_tcp_sockets( + b, talloc_tos(), &num_fds, &fds); + break; + default: + status = NT_STATUS_NOT_SUPPORTED; + break; + } + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(fds); + return status; + } + + for (i=0; i<num_fds; i++) { + bool ok = smb_set_close_on_exec(fds[i]); + if (!ok) { + status = map_nt_error_from_unix(errno); + break; + } + } + if (i < num_fds) { + for (i=0; i<num_fds; i++) { + close(fds[i]); + } + TALLOC_FREE(fds); + return status; + } + + *pfds = fds; + *pnum_fds = num_fds; + return NT_STATUS_OK; +} + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/rpc_sock_helper.h b/source3/rpc_server/rpc_sock_helper.h new file mode 100644 index 0000000..9c8128a --- /dev/null +++ b/source3/rpc_server/rpc_sock_helper.h @@ -0,0 +1,36 @@ +/* + * Unix SMB/CIFS implementation. + * + * RPC Socket Helper + * + * 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/>. + */ + + +#ifndef _RPC_SOCK_HELPER_H_ +#define _RPC_SOCK_HELPER_H_ + +#include "rpc_server.h" + +NTSTATUS dcesrv_create_binding_sockets( + struct dcerpc_binding *b, + TALLOC_CTX *mem_ctx, + size_t *pnum_fds, + int **fds); + +#endif /* _RPC_SOCK_HELPER_H_ */ + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/rpc_worker.c b/source3/rpc_server/rpc_worker.c new file mode 100644 index 0000000..bf9671d --- /dev/null +++ b/source3/rpc_server/rpc_worker.c @@ -0,0 +1,1287 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "lib/cmdline/cmdline.h" +#include "rpc_worker.h" +#include "rpc_config.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/rpc/dcerpc_util.h" +#include "source3/librpc/gen_ndr/ndr_rpc_host.h" +#include "lib/util/debug.h" +#include "lib/util/fault.h" +#include "rpc_server.h" +#include "rpc_pipes.h" +#include "source3/smbd/proto.h" +#include "source3/lib/smbd_shim.h" +#include "source3/lib/global_contexts.h" +#include "source3/lib/util_procid.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/named_pipe_auth/npa_tstream.h" +#include "libcli/smb/smb_constants.h" +#include "lib/param/param.h" +#include "lib/util/idtree_random.h" +#include "lib/util/tevent_unix.h" +#include "lib/async_req/async_sock.h" +#include "lib/util/dlinklist.h" +#include "source3/include/auth.h" +#include "nsswitch/winbind_client.h" +#include "source3/include/messages.h" +#include "libcli/security/security_token.h" +#include "libcli/security/dom_sid.h" +#include "source3/include/proto.h" + +/* + * This is the generic code that becomes the + * template that all rpcd_* instances that + * serve DCERPC can use to provide services to samba-dcerpcd. + * + * The external entry point is: + * rpc_worker_main() which takes an argc/argv list + * and two functions: + * + * get_interfaces() - List all interfaces that this server provides + * get_servers() - Provide the RPC server implementations + * + * Each rpcd_* service needs only to provide + * the implementations of get_interfaces() and get_servers() + * and call rpc_worker_main() from their main() function + * to provide services that can be connected to from samba-dcerpcd. + */ + +struct rpc_worker { + struct dcerpc_ncacn_conn *conns; + struct server_id rpc_host_pid; + struct messaging_context *msg_ctx; + struct dcesrv_context *dce_ctx; + + struct dcesrv_context_callbacks cb; + + struct rpc_worker_status status; + + bool done; +}; + +static void rpc_worker_print_interface( + FILE *f, const struct ndr_interface_table *t) +{ + const struct ndr_interface_string_array *endpoints = t->endpoints; + uint32_t i; + struct ndr_syntax_id_buf id_buf; + + fprintf(f, + "%s %s\n", + ndr_syntax_id_buf_string(&t->syntax_id, &id_buf), + t->name); + + for (i=0; i<endpoints->count; i++) { + fprintf(f, " %s\n", endpoints->names[i]); + } +} + +static NTSTATUS rpc_worker_report_status(struct rpc_worker *worker) +{ + uint8_t buf[16]; + DATA_BLOB blob = { .data = buf, .length = sizeof(buf), }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + worker->status.num_association_groups = worker->dce_ctx->assoc_groups_num; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(rpc_worker_status, &worker->status); + } + + ndr_err = ndr_push_struct_into_fixed_blob( + &blob, + &worker->status, + (ndr_push_flags_fn_t)ndr_push_rpc_worker_status); + SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + + status = messaging_send( + worker->msg_ctx, + worker->rpc_host_pid, + MSG_RPC_WORKER_STATUS, + &blob); + return status; +} + +static void rpc_worker_connection_terminated( + struct dcesrv_connection *conn, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + conn->transport.private_data, struct dcerpc_ncacn_conn); + struct dcerpc_ncacn_conn *w = NULL; + NTSTATUS status; + bool found = false; + + /* + * We need to drop the association group reference + * explicitly here in order to avoid the order given + * by the destructors. rpc_worker_report_status() below, + * expects worker->dce_ctx->assoc_groups_num to be updated + * already. + */ + if (conn->assoc_group != NULL) { + talloc_unlink(conn, conn->assoc_group); + conn->assoc_group = NULL; + } + + SMB_ASSERT(worker->status.num_connections > 0); + + for (w = worker->conns; w != NULL; w = w->next) { + if (w == ncacn_conn) { + found = true; + break; + } + } + SMB_ASSERT(found); + + DLIST_REMOVE(worker->conns, ncacn_conn); + + worker->status.num_connections -= 1; + + status = rpc_worker_report_status(worker); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_worker_report_status returned %s\n", + nt_errstr(status)); + } +} + +static int dcesrv_connection_destructor(struct dcesrv_connection *conn) +{ + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + conn->transport.private_data, + struct dcerpc_ncacn_conn); + + if (ncacn_conn->termination_fn != NULL) { + ncacn_conn->termination_fn(conn, ncacn_conn->termination_data); + } + + return 0; +} + +/* + * A new client has been passed to us from samba-dcerpcd. + */ +static void rpc_worker_new_client( + struct rpc_worker *worker, + struct rpc_host_client *client, + int sock) +{ + struct dcesrv_context *dce_ctx = worker->dce_ctx; + struct named_pipe_auth_req_info8 *info8 = client->npa_info8; + struct tsocket_address *remote_client_addr = NULL; + struct tsocket_address *local_server_addr = NULL; + struct dcerpc_binding *b = NULL; + enum dcerpc_transport_t transport; + struct dcesrv_endpoint *ep = NULL; + struct tstream_context *tstream = NULL; + struct dcerpc_ncacn_conn *ncacn_conn = NULL; + struct dcesrv_connection *dcesrv_conn = NULL; + DATA_BLOB buffer = { .data = NULL }; + struct ncacn_packet *pkt = NULL; + struct security_token *token = NULL; + uint32_t npa_flags, state_flags; + bool found_npa_flags; + NTSTATUS status; + int ret; + + DBG_DEBUG("Got new conn sock %d for binding %s\n", + sock, + client->binding); + + status = dcerpc_parse_binding(client, client->binding, &b); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n", + client->binding, + nt_errstr(status)); + goto fail; + } + transport = dcerpc_binding_get_transport(b); + + status = dcesrv_find_endpoint(dce_ctx, b, &ep); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) && + ((transport == NCACN_IP_TCP) || (transport == NCALRPC)) && + (dcerpc_binding_get_string_option(b, "endpoint") != NULL)) { + /* + * We have two kinds of servers: Those who explicitly + * bind to a port (e.g. 135 for epmapper) and those + * who just specify a transport. The client specified + * a port (or socket name), but we did not find this + * in the list of servers having specified a + * port. Retry just matching for the transport, + * catching the servers that did not explicitly + * specify a port. + * + * This is not fully correct, what we should do is + * that once the port the server listens on has been + * finalized we should mark this in the server list, + * but for now it works. We don't have the same RPC + * interface listening twice on different ports. + */ + struct dcerpc_binding *b_without_port = dcerpc_binding_dup( + client, b); + if (b_without_port == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + status = dcerpc_binding_set_string_option( + b_without_port, "endpoint", NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not delete endpoint: %s\n", + nt_errstr(status)); + TALLOC_FREE(b_without_port); + goto fail; + } + + status = dcesrv_find_endpoint(dce_ctx, b_without_port, &ep); + + TALLOC_FREE(b_without_port); + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not find endpoint for %s: %s\n", + client->binding, + nt_errstr(status)); + goto fail; + } + + ncacn_conn = talloc(dce_ctx, struct dcerpc_ncacn_conn); + if (ncacn_conn == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + *ncacn_conn = (struct dcerpc_ncacn_conn) { + .endpoint = ep, + .sock = sock, + .termination_fn = rpc_worker_connection_terminated, + .termination_data = worker, + }; + + if (transport == NCALRPC) { + ret = tsocket_address_unix_from_path(ncacn_conn, + info8->remote_client_addr, + &remote_client_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_unix_from_path" + "(%s) failed: %s\n", + info8->remote_client_addr, + strerror(errno)); + goto fail; + } + + ncacn_conn->remote_client_name = + talloc_strdup(ncacn_conn, info8->remote_client_name); + if (ncacn_conn->remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->remote_client_name); + goto fail; + } + + ret = tsocket_address_unix_from_path(ncacn_conn, + info8->local_server_addr, + &local_server_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_unix_from_path" + "(%s) failed: %s\n", + info8->local_server_addr, + strerror(errno)); + goto fail; + } + + ncacn_conn->local_server_name = + talloc_strdup(ncacn_conn, info8->local_server_name); + if (ncacn_conn->local_server_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->local_server_name); + goto fail; + } + } else { + ret = tsocket_address_inet_from_strings( + ncacn_conn, + "ip", + info8->remote_client_addr, + info8->remote_client_port, + &remote_client_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_inet_from_strings" + "(%s, %" PRIu16 ") failed: %s\n", + info8->remote_client_addr, + info8->remote_client_port, + strerror(errno)); + goto fail; + } + ncacn_conn->remote_client_name = + talloc_strdup(ncacn_conn, info8->remote_client_name); + if (ncacn_conn->remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->remote_client_name); + goto fail; + } + + ret = tsocket_address_inet_from_strings( + ncacn_conn, + "ip", + info8->local_server_addr, + info8->local_server_port, + &local_server_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_inet_from_strings" + "(%s, %" PRIu16 ") failed: %s\n", + info8->local_server_addr, + info8->local_server_port, + strerror(errno)); + goto fail; + } + ncacn_conn->local_server_name = + talloc_strdup(ncacn_conn, info8->local_server_name); + if (ncacn_conn->local_server_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->local_server_name); + goto fail; + } + } + + if (transport == NCACN_NP) { + ret = tstream_npa_existing_socket( + ncacn_conn, + sock, + FILE_TYPE_MESSAGE_MODE_PIPE, + &tstream); + if (ret == -1) { + DBG_DEBUG("tstream_npa_existing_socket failed: %s\n", + strerror(errno)); + goto fail; + } + + /* + * "transport" so far is implicitly assigned by the + * socket that the client connected to, passed in from + * samba-dcerpcd via the binding. For NCACN_NP (root + * only by unix permissions) we got a + * named_pipe_auth_req_info8 where the transport can + * be overridden. + */ + transport = info8->transport; + } else { + ret = tstream_bsd_existing_socket( + ncacn_conn, sock, &tstream); + if (ret == -1) { + DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n", + strerror(errno)); + goto fail; + } + /* as server we want to fail early */ + tstream_bsd_fail_readv_first_error(tstream, true); + } + sock = -1; + + token = info8->session_info->session_info->security_token; + + if (security_token_is_system(token) && (transport != NCALRPC)) { + DBG_DEBUG("System token only allowed on NCALRPC\n"); + goto fail; + } + + state_flags = DCESRV_CALL_STATE_FLAG_MAY_ASYNC; + + found_npa_flags = security_token_find_npa_flags(token, &npa_flags); + if (found_npa_flags) { + if (npa_flags & SAMBA_NPA_FLAGS_WINBIND_OFF) { + state_flags |= + DCESRV_CALL_STATE_FLAG_WINBIND_OFF; + } + + /* + * Delete the flags so that we don't bail in + * local_np_connect_send() on subsequent + * connects. Once we connect to another RPC service, a + * new flags sid will be added if required. + */ + security_token_del_npa_flags(token); + } + + ncacn_conn->p.msg_ctx = global_messaging_context(); + ncacn_conn->p.transport = transport; + + status = dcesrv_endpoint_connect(dce_ctx, + ncacn_conn, + ep, + info8->session_info->session_info, + global_event_context(), + state_flags, + &dcesrv_conn); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Failed to connect to endpoint: %s\n", + nt_errstr(status)); + goto fail; + } + + talloc_set_destructor(dcesrv_conn, dcesrv_connection_destructor); + + dcesrv_conn->transport.private_data = ncacn_conn; + dcesrv_conn->transport.report_output_data = + dcesrv_sock_report_output_data; + dcesrv_conn->transport.terminate_connection = + dcesrv_transport_terminate_connection; + + dcesrv_conn->send_queue = tevent_queue_create( + dcesrv_conn, "dcesrv send queue"); + if (dcesrv_conn->send_queue == NULL) { + DBG_DEBUG("tevent_queue_create failed\n"); + goto fail; + } + + dcesrv_conn->stream = talloc_move(dcesrv_conn, &tstream); + dcesrv_conn->local_address = + talloc_move(dcesrv_conn, &local_server_addr); + dcesrv_conn->remote_address = + talloc_move(dcesrv_conn, &remote_client_addr); + + if (client->bind_packet.length == 0) { + DBG_DEBUG("Expected bind packet\n"); + goto fail; + } + + buffer = (DATA_BLOB) { + .data = talloc_move(dcesrv_conn, &client->bind_packet.data), + .length = client->bind_packet.length, + }; + + pkt = talloc(dcesrv_conn, struct ncacn_packet); + if (pkt == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + + status = dcerpc_pull_ncacn_packet(pkt, &buffer, pkt); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_pull_ncacn_packet failed: %s\n", + nt_errstr(status)); + goto fail; + } + + TALLOC_FREE(client); + + DLIST_ADD(worker->conns, ncacn_conn); + worker->status.num_connections += 1; + + dcesrv_loop_next_packet(dcesrv_conn, pkt, buffer); + + return; +fail: + TALLOC_FREE(ncacn_conn); + TALLOC_FREE(dcesrv_conn); + TALLOC_FREE(client); + if (sock != -1) { + close(sock); + } + + /* + * Parent thinks it successfully sent us a client. Tell it + * that we declined. + */ + status = rpc_worker_report_status(worker); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_worker_report_status returned %s\n", + nt_errstr(status)); + } +} + +/* + * New client message processing. + */ +static bool rpc_worker_new_client_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcesrv_context *dce_ctx = worker->dce_ctx; + struct rpc_host_client *client = NULL; + enum ndr_err_code ndr_err; + int sock; + + if (rec->msg_type != MSG_RPC_HOST_NEW_CLIENT) { + return false; + } + + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + client = talloc(dce_ctx, struct rpc_host_client); + if (client == NULL) { + DBG_DEBUG("talloc failed\n"); + return false; + } + + ndr_err = ndr_pull_struct_blob_all( + &rec->buf, + client, + client, + (ndr_pull_flags_fn_t)ndr_pull_rpc_host_client); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_DEBUG("ndr_pull_rpc_host_client failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(client); + return false; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(rpc_host_client, client); + } + + sock = rec->fds[0]; + rec->fds[0] = -1; + + rpc_worker_new_client(worker, client, sock); + + return false; +} + +/* + * Return your status message processing. + */ +static bool rpc_worker_status_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcerpc_ncacn_conn *conn = NULL; + FILE *f = NULL; + int fd; + + if (rec->msg_type != MSG_RPC_DUMP_STATUS) { + return false; + } + + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + fd = dup(rec->fds[0]); + if (fd == -1) { + DBG_DEBUG("dup(%"PRIi64") failed: %s\n", + rec->fds[0], + strerror(errno)); + return false; + } + + f = fdopen(fd, "w"); + if (f == NULL) { + DBG_DEBUG("fdopen failed: %s\n", strerror(errno)); + close(fd); + return false; + } + + for (conn = worker->conns; conn != NULL; conn = conn->next) { + char *endpoint = NULL; + + endpoint = dcerpc_binding_string( + conn, conn->endpoint->ep_description); + + fprintf(f, + "endpoint=%s client=%s server=%s\n", + endpoint ? endpoint : "UNKNOWN", + conn->remote_client_name, + conn->local_server_name); + TALLOC_FREE(endpoint); + } + + fclose(f); + + return false; +} + +/* + take a reference to an existing association group + */ +static struct dcesrv_assoc_group *rpc_worker_assoc_group_reference( + struct dcesrv_connection *conn, + uint32_t id) +{ + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport( + endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + void *id_ptr = NULL; + + /* find an association group given a assoc_group_id */ + id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id & UINT16_MAX); + if (id_ptr == NULL) { + DBG_NOTICE("Failed to find assoc_group 0x%08x\n", id); + return NULL; + } + assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group); + + if (assoc_group->transport != transport) { + const char *at = derpc_transport_string_by_transport( + assoc_group->transport); + const char *ct = derpc_transport_string_by_transport( + transport); + + DBG_NOTICE("assoc_group 0x%08x (transport %s) " + "is not available on transport %s\n", + id, at, ct); + return NULL; + } + + /* + * Yes, this is a talloc_reference: The assoc group must be + * removed when all connections go. This should be replaced by + * adding a linked list of dcesrv_connection structs to the + * assoc group. + */ + return talloc_reference(conn, assoc_group); +} + +static int rpc_worker_assoc_group_destructor( + struct dcesrv_assoc_group *assoc_group) +{ + int ret; + + ret = idr_remove( + assoc_group->dce_ctx->assoc_groups_idr, + assoc_group->id & UINT16_MAX); + if (ret != 0) { + DBG_WARNING("Failed to remove assoc_group 0x%08x\n", + assoc_group->id); + } + + SMB_ASSERT(assoc_group->dce_ctx->assoc_groups_num > 0); + assoc_group->dce_ctx->assoc_groups_num -= 1; + return 0; +} + +/* + allocate a new association group + */ +static struct dcesrv_assoc_group *rpc_worker_assoc_group_new( + struct dcesrv_connection *conn, uint16_t worker_index) +{ + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport( + endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + int id; + + assoc_group = talloc_zero(conn, struct dcesrv_assoc_group); + if (assoc_group == NULL) { + return NULL; + } + + /* + * We use 16-bit to encode the worker index, + * have 16-bits left within the worker to form a + * 32-bit association group id. + */ + id = idr_get_new_random( + dce_ctx->assoc_groups_idr, assoc_group, 1, UINT16_MAX); + if (id == -1) { + talloc_free(assoc_group); + DBG_WARNING("Out of association groups!\n"); + return NULL; + } + assoc_group->id = (((uint32_t)worker_index) << 16) | id; + assoc_group->transport = transport; + assoc_group->dce_ctx = dce_ctx; + + talloc_set_destructor(assoc_group, rpc_worker_assoc_group_destructor); + + SMB_ASSERT(dce_ctx->assoc_groups_num < UINT16_MAX); + dce_ctx->assoc_groups_num += 1; + + return assoc_group; +} + +static NTSTATUS rpc_worker_assoc_group_find( + struct dcesrv_call_state *call, + void *private_data) +{ + struct rpc_worker *w = talloc_get_type_abort( + private_data, struct rpc_worker); + uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id; + + if (assoc_group_id != 0) { + uint16_t worker_index = (assoc_group_id & 0xffff0000) >> 16; + if (worker_index != w->status.worker_index) { + DBG_DEBUG("Wrong worker id %"PRIu16", " + "expected %"PRIu32"\n", + worker_index, + w->status.worker_index); + return NT_STATUS_NOT_FOUND; + } + call->conn->assoc_group = rpc_worker_assoc_group_reference( + call->conn, assoc_group_id); + } else { + call->conn->assoc_group = rpc_worker_assoc_group_new( + call->conn, w->status.worker_index); + } + + if (call->conn->assoc_group == NULL) { + /* TODO Return correct status */ + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static struct rpc_worker *rpc_worker_new( + TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx) +{ + struct rpc_worker *worker = NULL; + + worker = talloc_zero(mem_ctx, struct rpc_worker); + if (worker == NULL) { + return NULL; + } + + worker->rpc_host_pid = (struct server_id) { .pid = 0 }; + worker->msg_ctx = msg_ctx; + + worker->cb = (struct dcesrv_context_callbacks) { + .log.successful_authz = dcesrv_log_successful_authz, + .auth.gensec_prepare = dcesrv_auth_gensec_prepare, + .auth.become_root = become_root, + .auth.unbecome_root = unbecome_root, + .assoc_group.find = rpc_worker_assoc_group_find, + .assoc_group.private_data = worker, + }; + + worker->dce_ctx = global_dcesrv_context(); + if (worker->dce_ctx == NULL) { + goto fail; + } + dcesrv_context_set_callbacks(worker->dce_ctx, &worker->cb); + + return worker; +fail: + TALLOC_FREE(worker); + return NULL; +} + +static struct dcesrv_context *rpc_worker_dce_ctx(struct rpc_worker *w) +{ + return w->dce_ctx; +} + +struct rpc_worker_state { + struct tevent_context *ev; + struct rpc_worker *w; + struct tevent_req *new_client_req; + struct tevent_req *status_req; + struct tevent_req *finish_req; +}; + +static void rpc_worker_done(struct tevent_req *subreq); +static void rpc_worker_shutdown( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +static struct tevent_req *rpc_worker_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct rpc_worker *w, + pid_t rpc_host_pid, + int server_index, + int worker_index) +{ + struct tevent_req *req = NULL; + struct rpc_worker_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, struct rpc_worker_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->w = w; + + if ((server_index < 0) || ((unsigned)server_index > UINT32_MAX)) { + DBG_ERR("Invalid server index %d\n", server_index); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + if ((worker_index < 0) || ((unsigned)worker_index > UINT16_MAX)) { + DBG_ERR("Invalid worker index %d\n", worker_index); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + w->rpc_host_pid = pid_to_procid(rpc_host_pid); + + w->status = (struct rpc_worker_status) { + .server_index = server_index, + .worker_index = worker_index, + }; + + /* Wait for new client messages. */ + state->new_client_req = messaging_filtered_read_send( + w, + messaging_tevent_context(w->msg_ctx), + w->msg_ctx, + rpc_worker_new_client_filter, + w); + if (tevent_req_nomem(state->new_client_req, req)) { + return tevent_req_post(req, ev); + } + + /* Wait for report your status messages. */ + state->status_req = messaging_filtered_read_send( + w, + messaging_tevent_context(w->msg_ctx), + w->msg_ctx, + rpc_worker_status_filter, + w); + if (tevent_req_nomem(state->status_req, req)) { + return tevent_req_post(req, ev); + } + + /* Wait for shutdown messages. */ + status = messaging_register( + w->msg_ctx, req, MSG_SHUTDOWN, rpc_worker_shutdown); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("messaging_register failed: %s\n", + nt_errstr(status)); + tevent_req_error(req, map_errno_from_nt_status(status)); + return tevent_req_post(req, ev); + } + + state->finish_req = wait_for_read_send(state, ev, 0, false); + if (tevent_req_nomem(state->finish_req, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(state->finish_req, rpc_worker_done, req); + + rpc_worker_report_status(w); + + return req; +} + +static void rpc_worker_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int err = 0; + bool ok; + + ok = wait_for_read_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, err); + return; + } + tevent_req_done(req); +} + +static void rpc_worker_shutdown( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + tevent_req_done(req); +} + +static int rpc_worker_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static void sig_term_handler( + struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + exit(0); +} + +static void sig_hup_handler( + struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + change_to_root_user(); + lp_load_with_shares(get_dyn_CONFIGFILE()); +} + +static NTSTATUS register_ep_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + NTSTATUS status; + + DBG_DEBUG("Registering server %s\n", ep_server->name); + + status = dcerpc_register_ep_server(ep_server); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { + DBG_ERR("Failed to register '%s' endpoint server: %s\n", + ep_server->name, + nt_errstr(status)); + return status; + } + + status = dcesrv_init_ep_server(dce_ctx, ep_server->name); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dcesrv_init_ep_server(%s) failed: %s\n", + ep_server->name, + nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +/** + * @brief Main function for RPC server implementations + * + * This function provides all that is necessary to run a RPC server + * inside the samba-dcerpcd framework. Just pass argv and argc on to + * this function. + * + * The get_interfaces() callback provides the information that is + * passed to samba-dcerpcd via --list-interfaces, it should not do any + * real RPC server initialization work. Quickly after this function is + * called by rpc_worker_main, the process exits again. It should + * return the number of interfaces provided. + * + * get_servers() is called when the process is about to do the real + * work. So more heavy-weight initialization should happen here. It + * should return NT_STATUS_OK and the number of server implementations provided. + * + * @param[in] argc argc from main() + * @param[in] argv argv from main() + * @param[in] get_interfaces List all interfaces that this server provides + * @param[in] get_servers Provide the RPC server implementations + * @param[in] private_data Passed to the callback functions + * @return 0 It should never return except on successful process exit + */ + +int rpc_worker_main( + int argc, + const char *argv[], + const char *daemon_config_name, + int num_workers, + int idle_seconds, + size_t (*get_interfaces)( + const struct ndr_interface_table ***ifaces, + void *private_data), + NTSTATUS (*get_servers)( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***ep_servers, + size_t *num_ep_servers, + void *private_data), + void *private_data) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *progname = getprogname(); + TALLOC_CTX *frame = NULL; + struct tevent_context *ev_ctx = NULL; + struct tevent_req *req = NULL; + struct messaging_context *msg_ctx = NULL; + struct dcesrv_context *dce_ctx = NULL; + struct tevent_signal *se = NULL; + poptContext pc; + int opt; + NTSTATUS status; + int ret; + int worker_group = -1; + int worker_index = -1; + bool log_stdout; + int list_interfaces = 0; + struct rpc_worker *worker = NULL; + const struct dcesrv_endpoint_server **ep_servers; + size_t i, num_servers; + bool ok; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { + .longName = "list-interfaces", + .argInfo = POPT_ARG_NONE, + .arg = &list_interfaces, + .descrip = "List the interfaces provided", + }, + { + .longName = "worker-group", + .argInfo = POPT_ARG_INT, + .arg = &worker_group, + .descrip = "Group index in status message", + }, + { + .longName = "worker-index", + .argInfo = POPT_ARG_INT, + .arg = &worker_index, + .descrip = "Worker index in status message", + }, + POPT_COMMON_SAMBA + POPT_TABLEEND + }; + static const struct smbd_shim smbd_shim_fns = { + .become_authenticated_pipe_user = + smbd_become_authenticated_pipe_user, + .unbecome_authenticated_pipe_user = + smbd_unbecome_authenticated_pipe_user, + .become_root = smbd_become_root, + .unbecome_root = smbd_unbecome_root, + }; + + closefrom(3); + talloc_enable_null_tracking(); + frame = talloc_stackframe(); + umask(0); + smb_init_locale(); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_SERVER, + true /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(frame); + exit(ENOMEM); + } + + pc = samba_popt_get_context(progname, argc, argv, long_options, 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(frame); + exit(1); + } + + while ((opt = poptGetNextOpt(pc)) != -1) { + d_fprintf(stderr, + "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), + poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + TALLOC_FREE(frame); + exit(1); + }; + poptFreeContext(pc); + + if (list_interfaces != 0) { + const struct ndr_interface_table **ifaces = NULL; + size_t num_ifaces; + + num_workers = lp_parm_int( + -1, daemon_config_name, "num_workers", num_workers); + idle_seconds = lp_parm_int( + -1, daemon_config_name, "idle_seconds", idle_seconds); + + DBG_DEBUG("daemon=%s, num_workers=%d, idle_seconds=%d\n", + daemon_config_name, + num_workers, + idle_seconds); + + fprintf(stdout, "%d\n%d\n", num_workers, idle_seconds); + + num_ifaces = get_interfaces(&ifaces, private_data); + + for (i=0; i<num_ifaces; i++) { + rpc_worker_print_interface(stdout, ifaces[i]); + } + + TALLOC_FREE(frame); + exit(0); + } + + log_stdout = (debug_get_log_type() == DEBUG_STDOUT); + if (log_stdout != 0) { + setup_logging(argv[0], DEBUG_STDOUT); + } else { + setup_logging(argv[0], DEBUG_FILE); + } + + set_smbd_shim(&smbd_shim_fns); + + dump_core_setup(progname, lp_logfile(talloc_tos(), lp_sub)); + + /* POSIX demands that signals are inherited. If the invoking + * process has these signals masked, we will have problems, as + * we won't receive them. */ + BlockSignals(False, SIGHUP); + BlockSignals(False, SIGUSR1); + BlockSignals(False, SIGTERM); + +#if defined(SIGFPE) + /* we are never interested in SIGFPE */ + BlockSignals(True,SIGFPE); +#endif + /* We no longer use USR2... */ +#if defined(SIGUSR2) + BlockSignals(True, SIGUSR2); +#endif + /* Ignore children - no zombies. */ + CatchChild(); + + reopen_logs(); + + DBG_STARTUP_NOTICE("%s version %s started.\n%s\n", + progname, + samba_version_string(), + samba_copyright_string()); + + msg_ctx = global_messaging_context(); + if (msg_ctx == NULL) { + DBG_ERR("global_messaging_context() failed\n"); + TALLOC_FREE(frame); + exit(1); + } + ev_ctx = messaging_tevent_context(msg_ctx); + + worker = rpc_worker_new(ev_ctx, msg_ctx); + if (worker == NULL) { + DBG_ERR("rpc_worker_new failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + dce_ctx = rpc_worker_dce_ctx(worker); + + se = tevent_add_signal( + ev_ctx, ev_ctx, SIGTERM, 0, sig_term_handler, NULL); + if (se == NULL) { + DBG_ERR("tevent_add_signal failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + BlockSignals(false, SIGTERM); + + se = tevent_add_signal( + ev_ctx, ev_ctx, SIGHUP, 0, sig_hup_handler, NULL); + if (se == NULL) { + DBG_ERR("tevent_add_signal failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + BlockSignals(false, SIGHUP); + + (void)winbind_off(); + ok = init_guest_session_info(NULL); + (void)winbind_on(); + if (!ok) { + DBG_WARNING("init_guest_session_info failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + status = init_system_session_info(NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("init_system_session_info failed: %s\n", + nt_errstr(status)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + DBG_INFO("Initializing DCE/RPC registered endpoint servers\n"); + + status = get_servers(dce_ctx, + &ep_servers, + &num_servers, + private_data); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("get_servers failed: %s\n", nt_errstr(status)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + DBG_DEBUG("get_servers() returned %zu servers\n", num_servers); + + for (i=0; i<num_servers; i++) { + status = register_ep_server(dce_ctx, ep_servers[i]); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("register_ep_server failed: %s\n", + nt_errstr(status)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + } + + req = rpc_worker_send( + ev_ctx, ev_ctx, worker, getppid(), worker_group, worker_index); + if (req == NULL) { + DBG_ERR("rpc_worker_send failed\n"); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + DBG_DEBUG("%s worker running\n", progname); + + while (tevent_req_is_in_progress(req)) { + TALLOC_CTX *loop_frame = NULL; + + loop_frame = talloc_stackframe(); + + ret = tevent_loop_once(ev_ctx); + + TALLOC_FREE(loop_frame); + + if (ret != 0) { + DBG_WARNING("tevent_req_once() failed: %s\n", + strerror(errno)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + } + + status = dcesrv_shutdown_registered_ep_servers(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Shutdown failed with: %s\n", + nt_errstr(status)); + } + + ret = rpc_worker_recv(req); + if (ret != 0) { + DBG_DEBUG("rpc_worker_recv returned %s\n", strerror(ret)); + global_messaging_context_free(); + TALLOC_FREE(frame); + exit(1); + } + + TALLOC_FREE(frame); + return 0; +} diff --git a/source3/rpc_server/rpc_worker.h b/source3/rpc_server/rpc_worker.h new file mode 100644 index 0000000..54cc53f --- /dev/null +++ b/source3/rpc_server/rpc_worker.h @@ -0,0 +1,40 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 __RPC_WORKER_H__ +#define __RPC_WORKER_H__ + +#include "replace.h" +#include "librpc/rpc/dcesrv_core.h" + +int rpc_worker_main( + int argc, + const char *argv[], + const char *daemon_config_name, + int num_workers, + int idle_seconds, + size_t (*get_interfaces)( + const struct ndr_interface_table ***ifaces, + void *private_data), + NTSTATUS (*get_servers)( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***ep_servers, + size_t *num_ep_servers, + void *private_data), + void *private_data); + +#endif /* __RPC_WORKER_H__ */ diff --git a/source3/rpc_server/rpcd_classic.c b/source3/rpc_server/rpcd_classic.c new file mode 100644 index 0000000..2b7e939 --- /dev/null +++ b/source3/rpc_server/rpcd_classic.c @@ -0,0 +1,149 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "rpc_worker.h" +#include "librpc/gen_ndr/ndr_srvsvc.h" +#include "librpc/gen_ndr/ndr_srvsvc_scompat.h" +#include "librpc/gen_ndr/ndr_dfs.h" +#include "librpc/gen_ndr/ndr_dfs_scompat.h" +#include "librpc/gen_ndr/ndr_wkssvc.h" +#include "librpc/gen_ndr/ndr_wkssvc_scompat.h" +#include "librpc/gen_ndr/ndr_svcctl.h" +#include "librpc/gen_ndr/ndr_svcctl_scompat.h" +#include "librpc/gen_ndr/ndr_ntsvcs.h" +#include "librpc/gen_ndr/ndr_ntsvcs_scompat.h" +#include "librpc/gen_ndr/ndr_eventlog.h" +#include "librpc/gen_ndr/ndr_eventlog_scompat.h" +#include "librpc/gen_ndr/ndr_initshutdown.h" +#include "librpc/gen_ndr/ndr_initshutdown_scompat.h" +#include "source3/include/secrets.h" +#include "locking/share_mode_lock.h" +#include "source3/smbd/proto.h" + +static size_t classic_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_srvsvc, + &ndr_table_netdfs, + &ndr_table_initshutdown, + &ndr_table_svcctl, + &ndr_table_ntsvcs, + &ndr_table_eventlog, + /* + * This last item is truncated from the list by the + * num_ifaces -= 1 below. Take care when adding new + * services. + */ + &ndr_table_wkssvc, + }; + size_t num_ifaces = ARRAY_SIZE(ifaces); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC wkssvc is provided by the 'samba' + * binary from source4/ + */ + num_ifaces -= 1; + break; + default: + break; + } + + *pifaces = ifaces; + return num_ifaces; + +} + +static NTSTATUS classic_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[7] = { NULL }; + size_t num_servers = ARRAY_SIZE(ep_servers); + NTSTATUS status; + bool ok; + + ep_servers[0] = srvsvc_get_ep_server(); + ep_servers[1] = netdfs_get_ep_server(); + ep_servers[2] = initshutdown_get_ep_server(); + ep_servers[3] = svcctl_get_ep_server(); + ep_servers[4] = ntsvcs_get_ep_server(); + ep_servers[5] = eventlog_get_ep_server(); + ep_servers[6] = wkssvc_get_ep_server(); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC wkssvc is provided by the 'samba' + * binary from source4/ + */ + num_servers -= 1; + break; + default: + break; + } + + ok = secrets_init(); + if (!ok) { + DBG_ERR("secrets_init() failed\n"); + exit(1); + } + + ok = locking_init(); + if (!ok) { + DBG_ERR("locking_init() failed\n"); + exit(1); + } + + status = share_info_db_init(); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("share_info_db_init failed: %s\n", nt_errstr(status)); + exit(1); + } + + lp_load_with_shares(get_dyn_CONFIGFILE()); + + mangle_reset_cache(); + + status = dcesrv_register_default_auth_types_machine_principal(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_ep_servers = ep_servers; + *_num_ep_servers = num_servers; + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_classic", + 5, + 60, + classic_interfaces, + classic_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_epmapper.c b/source3/rpc_server/rpcd_epmapper.c new file mode 100644 index 0000000..9b2cc4f --- /dev/null +++ b/source3/rpc_server/rpcd_epmapper.c @@ -0,0 +1,109 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "replace.h" +#include "rpc_worker.h" +#include "librpc/gen_ndr/ndr_epmapper.h" +#include "librpc/gen_ndr/ndr_epmapper_scompat.h" +#include "param/loadparm.h" +#include "libds/common/roles.h" + +static size_t epmapper_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_epmapper, + }; + size_t num_ifaces = ARRAY_SIZE(ifaces); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC epmapper is provided by the 'samba' + * binary from source4/ + */ + num_ifaces = 0; + break; + default: + break; + } + + *pifaces = ifaces; + return num_ifaces; +} + +static NTSTATUS epmapper_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[] = { NULL }; + size_t num_servers = ARRAY_SIZE(ep_servers); + NTSTATUS status; + + /* + * Windows Server 2022 registers the following auth_types + * all with an empty principal name: + * + * principle name for proto 9 (spnego) is '' + * principle name for proto 10 (ntlmssp) is '' + * principle name for proto 14 is '' + * principle name for proto 16 (gssapi_krb5) is '' + * principle name for proto 22 is '' + * principle name for proto 30 is '' + * principle name for proto 31 is '' + * + * We only register what we also support. + */ + status = dcesrv_register_default_auth_types(dce_ctx, ""); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ep_servers[0] = epmapper_get_ep_server(); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC epmapper is provided by the 'samba' + * binary from source4/ + */ + num_servers = 0; + break; + default: + break; + } + + *_ep_servers = ep_servers; + *_num_ep_servers = num_servers; + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_epmapper", + 1, + 10, + epmapper_interfaces, + epmapper_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_fsrvp.c b/source3/rpc_server/rpcd_fsrvp.c new file mode 100644 index 0000000..f7db544 --- /dev/null +++ b/source3/rpc_server/rpcd_fsrvp.c @@ -0,0 +1,82 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "rpc_worker.h" +#include "librpc/gen_ndr/ndr_fsrvp.h" +#include "librpc/gen_ndr/ndr_fsrvp_scompat.h" + +static size_t fsrvp_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_FileServerVssAgent, + }; + + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + /* + * For now, don't do shadow copies on the AD DC. This + * might change in the future, but there's a + * recommendation to split DCs from file servers. + * + * But then we need to put the snap logic into the ad + * dc testenv's smb.conf. + */ + *pifaces = NULL; + return 0; + } + + *pifaces = ifaces; + return ARRAY_SIZE(ifaces); +} + +static NTSTATUS fsrvp_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + *_ep_servers = NULL; + *_num_ep_servers = 0; + return NT_STATUS_OK; + } + + lp_load_with_shares(get_dyn_CONFIGFILE()); + + ep_servers[0] = FileServerVssAgent_get_ep_server(); + + *_ep_servers = ep_servers; + *_num_ep_servers = ARRAY_SIZE(ep_servers); + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_fsrvp", + 5, + 60, + fsrvp_interfaces, + fsrvp_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_lsad.c b/source3/rpc_server/rpcd_lsad.c new file mode 100644 index 0000000..d00f704 --- /dev/null +++ b/source3/rpc_server/rpcd_lsad.c @@ -0,0 +1,141 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "rpc_worker.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_lsa_scompat.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "librpc/gen_ndr/ndr_samr_scompat.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_scompat.h" +#include "librpc/gen_ndr/ndr_dssetup.h" +#include "librpc/gen_ndr/ndr_dssetup_scompat.h" +#include "source3/include/auth.h" +#include "source3/include/secrets.h" + +static size_t lsad_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_lsarpc, + &ndr_table_samr, + &ndr_table_dssetup, + /* + * This last item is truncated from the list by the + * num_ifaces -= 1 below for the fileserver. Take + * care when adding new services. + */ + &ndr_table_netlogon, + }; + size_t num_ifaces = ARRAY_SIZE(ifaces); + + switch(lp_server_role()) { + case ROLE_STANDALONE: + case ROLE_DOMAIN_MEMBER: + /* no netlogon for non-dc */ + num_ifaces -= 1; + break; + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * All these services are provided by the 'samba' + * binary from source4, not this code which is the + * source3 / NT4-like "classic" DC implementation + */ + num_ifaces = 0; + break; + default: + break; + } + + *pifaces = ifaces; + return num_ifaces; +} + +static NTSTATUS lsad_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[4] = { NULL, }; + size_t num_servers = ARRAY_SIZE(ep_servers); + NTSTATUS status; + bool ok; + + ep_servers[0] = lsarpc_get_ep_server(); + ep_servers[1] = samr_get_ep_server(); + ep_servers[2] = dssetup_get_ep_server(); + ep_servers[3] = netlogon_get_ep_server(); + + ok = secrets_init(); + if (!ok) { + DBG_ERR("secrets_init() failed\n"); + exit(1); + } + + status = dcesrv_register_default_auth_types_machine_principal(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch(lp_server_role()) { + case ROLE_STANDALONE: + case ROLE_DOMAIN_MEMBER: + /* no netlogon for non-dc */ + num_servers -= 1; + break; + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * All these services are provided by the 'samba' + * binary from source4, not this code which is the + * source3 / NT4-like "classic" DC implementation + */ + num_servers = 0; + break; + default: + /* + * As DC we also register schannel with an + * empty principal + */ + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_SCHANNEL, + ""); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + break; + } + + *_ep_servers = ep_servers; + *_num_ep_servers = num_servers; + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_lsad", + 5, + 60, + lsad_interfaces, + lsad_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_mdssvc.c b/source3/rpc_server/rpcd_mdssvc.c new file mode 100644 index 0000000..c872aa6 --- /dev/null +++ b/source3/rpc_server/rpcd_mdssvc.c @@ -0,0 +1,71 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "source3/locking/proto.h" +#include "rpc_worker.h" +#include "librpc/gen_ndr/ndr_mdssvc.h" +#include "librpc/gen_ndr/ndr_mdssvc_scompat.h" + +static size_t mdssvc_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_mdssvc, + }; + + *pifaces = ifaces; + return ARRAY_SIZE(ifaces); +} + +static NTSTATUS mdssvc_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + bool ok; + + lp_load_with_shares(get_dyn_CONFIGFILE()); + + ok = posix_locking_init(false); + if (!ok) { + DBG_ERR("posix_locking_init() failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + ep_servers[0] = mdssvc_get_ep_server(); + + *_ep_servers = ep_servers; + *_num_ep_servers = ARRAY_SIZE(ep_servers); + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_mdssvc", + 5, + 60, + mdssvc_interfaces, + mdssvc_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_rpcecho.c b/source3/rpc_server/rpcd_rpcecho.c new file mode 100644 index 0000000..5466663 --- /dev/null +++ b/source3/rpc_server/rpcd_rpcecho.c @@ -0,0 +1,89 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "replace.h" +#include "rpc_worker.h" +#include "librpc/gen_ndr/ndr_echo.h" +#include "librpc/gen_ndr/ndr_echo_scompat.h" +#include "param/loadparm.h" +#include "libds/common/roles.h" + +static size_t rpcecho_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_rpcecho, + }; + size_t num_ifaces = ARRAY_SIZE(ifaces); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC rpcecho is provided by the 'samba' + * binary from source4/ + */ + num_ifaces = 0; + break; + default: + break; + } + + *pifaces = ifaces; + return num_ifaces; +} + +static NTSTATUS rpcecho_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + size_t num_servers = ARRAY_SIZE(ep_servers); + + ep_servers[0] = rpcecho_get_ep_server(); + + switch(lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* + * On the AD DC rpcecho is provided by the 'samba' + * binary from source4/ + */ + num_servers = 0; + break; + default: + break; + } + + *_ep_servers = ep_servers; + *_num_ep_servers = num_servers; + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_rpcecho", + 1, + 1, + rpcecho_interfaces, + rpcecho_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_spoolss.c b/source3/rpc_server/rpcd_spoolss.c new file mode 100644 index 0000000..43c7d53 --- /dev/null +++ b/source3/rpc_server/rpcd_spoolss.c @@ -0,0 +1,91 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "rpc_worker.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "librpc/gen_ndr/ndr_spoolss_scompat.h" +#include "source3/locking/share_mode_lock.h" +#include "source3/printing/queue_process.h" +#include "source3/include/messages.h" +#include "source3/include/secrets.h" +#include "source3/smbd/proto.h" + +static size_t spoolss_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_spoolss, + }; + *pifaces = ifaces; + return ARRAY_SIZE(ifaces); +} + +static NTSTATUS spoolss_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + struct messaging_context *msg_ctx = global_messaging_context(); + struct tevent_context *ev_ctx = messaging_tevent_context(msg_ctx); + bool ok; + + ep_servers[0] = spoolss_get_ep_server(); + + ok = secrets_init(); + if (!ok) { + DBG_ERR("secrets_init() failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + ok = locking_init(); + if (!ok) { + DBG_ERR("locking_init() failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + lp_load_with_shares(get_dyn_CONFIGFILE()); + + ok = printing_subsystem_init(ev_ctx, msg_ctx, dce_ctx); + if (!ok) { + DBG_ERR("printing_subsystem_init() failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + mangle_reset_cache(); + + *_ep_servers = ep_servers; + *_num_ep_servers = ARRAY_SIZE(ep_servers); + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_spoolss", + 5, + 60, + spoolss_interfaces, + spoolss_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_winreg.c b/source3/rpc_server/rpcd_winreg.c new file mode 100644 index 0000000..44715d8 --- /dev/null +++ b/source3/rpc_server/rpcd_winreg.c @@ -0,0 +1,71 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 "rpc_worker.h" +#include "librpc/gen_ndr/ndr_winreg.h" +#include "librpc/gen_ndr/ndr_winreg_scompat.h" +#include "source3/registry/reg_init_full.h" + +static size_t winreg_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_winreg, + }; + *pifaces = ifaces; + return ARRAY_SIZE(ifaces); +} + +static NTSTATUS winreg_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + WERROR werr; + + ep_servers[0] = winreg_get_ep_server(); + + werr = registry_init_full(); + if (!W_ERROR_IS_OK(werr)) { + DBG_ERR("registry_init_full() failed: %s\n", + win_errstr(werr)); + return werror_to_ntstatus(werr); + } + + lp_load_with_shares(get_dyn_CONFIGFILE()); + + *_ep_servers = ep_servers; + *_num_ep_servers = ARRAY_SIZE(ep_servers); + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_winreg", + 5, + 60, + winreg_interfaces, + winreg_servers, + NULL); +} diff --git a/source3/rpc_server/rpcd_witness.c b/source3/rpc_server/rpcd_witness.c new file mode 100644 index 0000000..9dcf180 --- /dev/null +++ b/source3/rpc_server/rpcd_witness.c @@ -0,0 +1,120 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2023 Stefan Metzmacher + * + * 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 "rpc_worker.h" +#include "librpc/gen_ndr/ndr_witness.h" +#include "librpc/gen_ndr/ndr_witness_scompat.h" + +static size_t witness_interfaces( + const struct ndr_interface_table ***pifaces, + void *private_data) +{ + static const struct ndr_interface_table *ifaces[] = { + &ndr_table_witness, + }; + + if (!lp_clustering()) { + /* + * Without clustering there's no need for witness. + */ + *pifaces = NULL; + return 0; + } + + *pifaces = ifaces; + return ARRAY_SIZE(ifaces); +} + +static NTSTATUS witness_servers( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***_ep_servers, + size_t *_num_ep_servers, + void *private_data) +{ + static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL }; + char *principal = NULL; + NTSTATUS status; + + if (!lp_clustering()) { + /* + * Without clustering there's no need for witness. + */ + *_ep_servers = NULL; + *_num_ep_servers = 0; + return NT_STATUS_OK; + } + + principal = talloc_asprintf(talloc_tos(), + "cifs/%s", + lp_netbios_name()); + if (principal == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_NTLMSSP, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_SPNEGO, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (lp_security() == SEC_ADS) { + status = dcesrv_auth_type_principal_register(dce_ctx, + DCERPC_AUTH_TYPE_KRB5, + principal); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + TALLOC_FREE(principal); + + /* + * We prefer NDR64 for witness, + * as it's a very simple protocol and + * we support it from the beginning, + * which means it's well tested. + */ + dce_ctx->preferred_transfer = &ndr_transfer_syntax_ndr64; + + ep_servers[0] = witness_get_ep_server(); + + *_ep_servers = ep_servers; + *_num_ep_servers = ARRAY_SIZE(ep_servers); + return NT_STATUS_OK; +} + +int main(int argc, const char *argv[]) +{ + return rpc_worker_main( + argc, + argv, + "rpcd_witness", + 5, + 60, + witness_interfaces, + witness_servers, + NULL); +} diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c new file mode 100644 index 0000000..5fcc08e --- /dev/null +++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c @@ -0,0 +1,1421 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Andrew Bartlett 2001-2004 + + 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/>. +*/ + +/* These comments regard the code to change the user's unix password: */ + +/* fork a child process to exec passwd and write to its + * tty to change a users password. This is running as the + * user who is attempting to change the password. + */ + +/* + * This code was copied/borrowed and stolen from various sources. + * The primary source was the poppasswd.c from the authors of POPMail. This software + * was included as a client to change passwords using the 'passwd' program + * on the remote machine. + * + * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson + * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences + * and rights to modify, distribute or incorporate this change to the CAP suite or + * using it for any other reason are granted, so long as this disclaimer is left intact. + */ + +/* + This code was hacked considerably for inclusion in Samba, primarily + by Andrew.Tridgell@anu.edu.au. The biggest change was the addition + of the "password chat" option, which allows the easy runtime + specification of the expected sequence of events to change a + password. + */ + +#include "includes.h" +#include "system/terminal.h" +#include "system/passwd.h" +#include "system/filesys.h" +#include "../libcli/auth/libcli_auth.h" +#include "rpc_server/samr/srv_samr_util.h" +#include "passdb.h" +#include "auth.h" +#include "lib/util/sys_rw.h" +#include "librpc/rpc/dcerpc_samr.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#ifndef ALLOW_CHANGE_PASSWORD +#if (defined(HAVE_TERMIOS_H) && defined(HAVE_DUP2) && defined(HAVE_SETSID)) +#define ALLOW_CHANGE_PASSWORD 1 +#endif +#endif + +#if ALLOW_CHANGE_PASSWORD + +static int findpty(char **slave) +{ + int master = -1; + char *line = NULL; + DIR *dirp = NULL; + const char *dpname; + + *slave = NULL; + +#if defined(HAVE_GRANTPT) +#if defined(HAVE_POSIX_OPENPT) + master = posix_openpt(O_RDWR|O_NOCTTY); +#else + /* Try to open /dev/ptmx. If that fails, fall through to old method. */ + master = open("/dev/ptmx", O_RDWR, 0); +#endif + if (master >= 0) { + grantpt(master); + unlockpt(master); + line = (char *)ptsname(master); + if (line) { + *slave = SMB_STRDUP(line); + } + + if (*slave == NULL) { + DEBUG(0, + ("findpty: Unable to create master/slave pty pair.\n")); + /* Stop fd leak on error. */ + close(master); + return -1; + } else { + DEBUG(10, + ("findpty: Allocated slave pty %s\n", *slave)); + return (master); + } + } +#endif /* HAVE_GRANTPT */ + + line = SMB_STRDUP("/dev/ptyXX"); + if (!line) { + return (-1); + } + + dirp = opendir("/dev"); + if (!dirp) { + SAFE_FREE(line); + return (-1); + } + + while ((dpname = readdirname(dirp)) != NULL) { + if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) { + DEBUG(3, + ("pty: try to open %s, line was %s\n", dpname, + line)); + line[8] = dpname[3]; + line[9] = dpname[4]; + if ((master = open(line, O_RDWR, 0)) >= 0) { + DEBUG(3, ("pty: opened %s\n", line)); + line[5] = 't'; + *slave = line; + closedir(dirp); + return (master); + } + } + } + closedir(dirp); + SAFE_FREE(line); + return (-1); +} + +static int dochild(int master, const char *slavedev, const struct passwd *pass, + const char *passwordprogram, bool as_root) +{ + int slave; + struct termios stermios; + gid_t gid; + uid_t uid; + char * const eptrs[1] = { NULL }; + + if (pass == NULL) + { + DEBUG(0, + ("dochild: user doesn't exist in the UNIX password database.\n")); + return False; + } + + gid = pass->pw_gid; + uid = pass->pw_uid; + + gain_root_privilege(); + + /* Start new session - gets rid of controlling terminal. */ + if (setsid() < 0) + { + DEBUG(3, + ("Weirdness, couldn't let go of controlling terminal\n")); + return (False); + } + + /* Open slave pty and acquire as new controlling terminal. */ + if ((slave = open(slavedev, O_RDWR, 0)) < 0) + { + DEBUG(3, ("More weirdness, could not open %s\n", slavedev)); + return (False); + } +#if defined(TIOCSCTTY) && !defined(SUNOS5) + /* + * On patched Solaris 10 TIOCSCTTY is defined but seems not to work, + * see the discussion under + * https://bugzilla.samba.org/show_bug.cgi?id=5366. + */ + if (ioctl(slave, TIOCSCTTY, 0) < 0) + { + DEBUG(3, ("Error in ioctl call for slave pty\n")); + /* return(False); */ + } +#elif defined(I_PUSH) && defined(I_FIND) + if (ioctl(slave, I_FIND, "ptem") == 0) { + ioctl(slave, I_PUSH, "ptem"); + } + if (ioctl(slave, I_FIND, "ldterm") == 0) { + ioctl(slave, I_PUSH, "ldterm"); + } +#endif + + /* Close master. */ + close(master); + + /* Make slave stdin/out/err of child. */ + + if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) + { + DEBUG(3, ("Could not re-direct stdin\n")); + return (False); + } + if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) + { + DEBUG(3, ("Could not re-direct stdout\n")); + return (False); + } + if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) + { + DEBUG(3, ("Could not re-direct stderr\n")); + return (False); + } + if (slave > 2) + close(slave); + + /* Set proper terminal attributes - no echo, canonical input processing, + no map NL to CR/NL on output. */ + + if (tcgetattr(0, &stermios) < 0) + { + DEBUG(3, + ("could not read default terminal attributes on pty\n")); + return (False); + } + stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + stermios.c_lflag |= ICANON; +#ifdef ONLCR + stermios.c_oflag &= ~(ONLCR); +#endif + if (tcsetattr(0, TCSANOW, &stermios) < 0) + { + DEBUG(3, ("could not set attributes of pty\n")); + return (False); + } + + /* make us completely into the right uid */ + if (!as_root) + { + become_user_permanently(uid, gid); + } + + DEBUG(10, + ("Invoking '%s' as password change program.\n", + passwordprogram)); + + /* execl() password-change application */ + if (execle("/bin/sh", "sh", "-c", passwordprogram, NULL, eptrs) < 0) + { + DEBUG(3, ("Bad status returned from %s\n", passwordprogram)); + return (False); + } + return (True); +} + +static int expect(int master, char *issue, char *expected) +{ + char buffer[1024]; + int attempts, timeout, nread; + size_t len; + bool match = False; + + for (attempts = 0; attempts < 2; attempts++) { + NTSTATUS status; + if (!strequal(issue, ".")) { + if (lp_passwd_chat_debug()) + DEBUG(100, ("expect: sending [%s]\n", issue)); + + if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) { + DEBUG(2,("expect: (short) write returned %d\n", + (int)len )); + return False; + } + } + + if (strequal(expected, ".")) + return True; + + /* Initial timeout. */ + timeout = lp_passwd_chat_timeout() * 1000; + nread = 0; + buffer[nread] = 0; + + while (True) { + status = read_fd_with_timeout( + master, buffer + nread, 1, + sizeof(buffer) - nread - 1, + timeout, &len); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("expect: read error %s\n", + nt_errstr(status))); + break; + } + nread += len; + buffer[nread] = 0; + + { + /* Eat leading/trailing whitespace before match. */ + char *str = SMB_STRDUP(buffer); + if (!str) { + DEBUG(2,("expect: ENOMEM\n")); + return False; + } + trim_char(str, ' ', ' '); + + if ((match = unix_wild_match(expected, str)) == True) { + /* Now data has started to return, lower timeout. */ + timeout = lp_passwd_chat_timeout() * 100; + } + SAFE_FREE(str); + } + } + + if (lp_passwd_chat_debug()) + DEBUG(100, ("expect: expected [%s] received [%s] match %s\n", + expected, buffer, match ? "yes" : "no" )); + + if (match) + break; + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("expect: %s\n", nt_errstr(status))); + return False; + } + } + + DEBUG(10,("expect: returning %s\n", match ? "True" : "False" )); + return match; +} + +static void pwd_sub(char *buf) +{ + all_string_sub(buf, "\\n", "\n", 0); + all_string_sub(buf, "\\r", "\r", 0); + all_string_sub(buf, "\\s", " ", 0); + all_string_sub(buf, "\\t", "\t", 0); +} + +static int talktochild(int master, const char *seq) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int count = 0; + char *issue; + char *expected; + + issue = talloc_strdup(frame, "."); + if (!issue) { + TALLOC_FREE(frame); + return false; + } + + while (next_token_talloc(frame, &seq, &expected, NULL)) { + pwd_sub(expected); + count++; + + if (!expect(master, issue, expected)) { + DEBUG(3, ("Response %d incorrect\n", count)); + TALLOC_FREE(frame); + return false; + } + + if (!next_token_talloc(frame, &seq, &issue, NULL)) { + issue = talloc_strdup(frame, "."); + if (!issue) { + TALLOC_FREE(frame); + return false; + } + } + pwd_sub(issue); + } + + if (!strequal(issue, ".")) { + /* we have one final issue to send */ + expected = talloc_strdup(frame, "."); + if (!expected) { + TALLOC_FREE(frame); + return false; + } + if (!expect(master, issue, expected)) { + TALLOC_FREE(frame); + return False; + } + } + TALLOC_FREE(frame); + return (count > 0); +} + +static bool chat_with_program(char *passwordprogram, const struct passwd *pass, + char *chatsequence, bool as_root) +{ + char *slavedev = NULL; + int master; + pid_t pid, wpid; + int wstat; + bool chstat = False; + void (*saved_handler)(int); + + if (pass == NULL) { + DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n")); + return False; + } + + /* allocate a pseudo-terminal device */ + if ((master = findpty(&slavedev)) < 0) { + DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name)); + return (False); + } + + /* + * We need to temporarily stop CatchChild from eating + * SIGCLD signals as it also eats the exit status code. JRA. + */ + + saved_handler = CatchChildLeaveStatus(); + + if ((pid = fork()) < 0) { + DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name)); + SAFE_FREE(slavedev); + close(master); + (void)CatchSignal(SIGCLD, saved_handler); + return (False); + } + + /* we now have a pty */ + if (pid > 0) { /* This is the parent process */ + /* Don't need this anymore in parent. */ + SAFE_FREE(slavedev); + + if ((chstat = talktochild(master, chatsequence)) == False) { + DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name)); + kill(pid, SIGKILL); /* be sure to end this process */ + } + + while ((wpid = waitpid(pid, &wstat, 0)) < 0) { + if (errno == EINTR) { + errno = 0; + continue; + } + break; + } + + if (wpid < 0) { + DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n")); + close(master); + (void)CatchSignal(SIGCLD, saved_handler); + return (False); + } + + /* + * Go back to ignoring children. + */ + (void)CatchSignal(SIGCLD, saved_handler); + + close(master); + + if (pid != wpid) { + DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n")); + return (False); + } + if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) { + DEBUG(3, ("chat_with_program: The process exited with status %d \ +while we were waiting\n", WEXITSTATUS(wstat))); + return (False); + } +#if defined(WIFSIGNALLED) && defined(WTERMSIG) + else if (WIFSIGNALLED(wstat)) { + DEBUG(3, ("chat_with_program: The process was killed by signal %d \ +while we were waiting\n", WTERMSIG(wstat))); + return (False); + } +#endif + } else { + /* CHILD */ + + /* + * Lose any elevated privileges. + */ + drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); + drop_effective_capability(DMAPI_ACCESS_CAPABILITY); + + /* make sure it doesn't freeze */ + alarm(20); + + if (as_root) + become_root(); + + DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name, + (int)getuid(), (int)getgid(), BOOLSTR(as_root) )); + chstat = dochild(master, slavedev, pass, passwordprogram, as_root); + + if (as_root) + unbecome_root(); + + /* + * The child should never return from dochild() .... + */ + + DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat)); + exit(1); + } + + if (chstat) + DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n", + (chstat ? "" : "un"), pass->pw_name)); + return (chstat); +} + +bool chgpasswd(const char *name, const char *rhost, const struct passwd *pass, + const char *oldpass, const char *newpass, bool as_root) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *passwordprogram = NULL; + char *chatsequence = NULL; + size_t i; + size_t len; + TALLOC_CTX *ctx = talloc_tos(); + + if (!oldpass) { + oldpass = ""; + } + + DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name)); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass)); +#endif + + /* Take the passed information and test it for minimum criteria */ + + /* Password is same as old password */ + if (strcmp(oldpass, newpass) == 0) { + /* don't allow same password */ + DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */ + return (False); /* inform the user */ + } + + /* + * Check the old and new passwords don't contain any control + * characters. + */ + + len = strlen(oldpass); + for (i = 0; i < len; i++) { + if (iscntrl((int)oldpass[i])) { + DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n")); + return False; + } + } + + len = strlen(newpass); + for (i = 0; i < len; i++) { + if (iscntrl((int)newpass[i])) { + DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n")); + return False; + } + } + +#ifdef WITH_PAM + if (lp_pam_password_change()) { + bool ret; +#ifdef HAVE_SETLOCALE + const char *prevlocale = setlocale(LC_ALL, "C"); +#endif + + if (as_root) + become_root(); + + if (pass) { + ret = smb_pam_passchange(pass->pw_name, rhost, + oldpass, newpass); + } else { + ret = smb_pam_passchange(name, rhost, oldpass, + newpass); + } + + if (as_root) + unbecome_root(); + +#ifdef HAVE_SETLOCALE + setlocale(LC_ALL, prevlocale); +#endif + + return ret; + } +#endif + + /* A non-PAM password change just doesn't make sense without a valid local user */ + + if (pass == NULL) { + DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name)); + return false; + } + + passwordprogram = lp_passwd_program(ctx, lp_sub); + if (!passwordprogram || !*passwordprogram) { + DEBUG(2, ("chgpasswd: Null password program - no password changing\n")); + return false; + } + chatsequence = lp_passwd_chat(ctx, lp_sub); + if (!chatsequence || !*chatsequence) { + DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n")); + return false; + } + + if (as_root) { + /* The password program *must* contain the user name to work. Fail if not. */ + if (strstr_m(passwordprogram, "%u") == NULL) { + DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \ +the string %%u, and the given string %s does not.\n", passwordprogram )); + return false; + } + } + + passwordprogram = talloc_string_sub(ctx, passwordprogram, "%u", name); + if (!passwordprogram) { + return false; + } + + /* note that we do NOT substitute the %o and %n in the password program + as this would open up a security hole where the user could use + a new password containing shell escape characters */ + + chatsequence = talloc_string_sub(ctx, chatsequence, "%u", name); + if (!chatsequence) { + return false; + } + chatsequence = talloc_all_string_sub(ctx, + chatsequence, + "%o", + oldpass); + if (!chatsequence) { + return false; + } + chatsequence = talloc_all_string_sub(ctx, + chatsequence, + "%n", + newpass); + if (chatsequence == NULL) { + return false; + } + return chat_with_program(passwordprogram, + pass, + chatsequence, + as_root); +} + +#else /* ALLOW_CHANGE_PASSWORD */ + +bool chgpasswd(const char *name, const struct passwd *pass, + const char *oldpass, const char *newpass, bool as_root) +{ + DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name)); + return (False); +} +#endif /* ALLOW_CHANGE_PASSWORD */ + +/*********************************************************** + Decrypt and verify a user password change. + + The 516 byte long buffers are encrypted with the old NT and + old LM passwords, and if the NT passwords are present, both + buffers contain a unicode string. + + After decrypting the buffers, check the password is correct by + matching the old hashed passwords with the passwords in the passdb. + +************************************************************/ + +static NTSTATUS check_oem_password(const char *user, + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + struct samu *sampass, + char **pp_new_passwd) +{ + uchar null_pw[16]; + uchar null_ntpw[16]; + uint8_t *password_encrypted; + const uint8_t *encryption_key; + const uint8_t *lanman_pw, *nt_pw; + uint32_t acct_ctrl; + size_t new_pw_len; + uchar new_nt_hash[16]; + uchar new_lm_hash[16]; + uchar verifier[16]; + char no_pw[2]; + + bool nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted); + bool lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted); + enum ntlm_auth_level ntlm_auth_level = lp_ntlm_auth(); + + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t enc_key; + int rc; + + /* this call should be disabled without NTLM auth */ + if (ntlm_auth_level == NTLM_AUTH_DISABLED) { + DBG_WARNING("NTLM password changes not" + "permitted by configuration.\n"); + return NT_STATUS_NTLM_BLOCKED; + } + + acct_ctrl = pdb_get_acct_ctrl(sampass); +#if 0 + /* I am convinced this check here is wrong, it is valid to + * change a password of a user that has a disabled account - gd */ + + if (acct_ctrl & ACB_DISABLED) { + DEBUG(2,("check_lanman_password: account %s disabled.\n", user)); + return NT_STATUS_ACCOUNT_DISABLED; + } +#endif + if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) { + /* construct a null password (in case one is needed */ + no_pw[0] = 0; + no_pw[1] = 0; + nt_lm_owf_gen(no_pw, null_ntpw, null_pw); + lanman_pw = null_pw; + nt_pw = null_pw; + + } else { + /* save pointers to passwords so we don't have to keep looking them up */ + if (lp_lanman_auth()) { + lanman_pw = pdb_get_lanman_passwd(sampass); + } else { + lanman_pw = NULL; + } + nt_pw = pdb_get_nt_passwd(sampass); + } + + if (nt_pw && nt_pass_set) { + /* IDEAL Case: passwords are in unicode, and we can + * read use the password encrypted with the NT hash + */ + password_encrypted = password_encrypted_with_nt_hash; + encryption_key = nt_pw; + } else if (lanman_pw && lm_pass_set) { + /* password may still be in unicode, but use LM hash version */ + password_encrypted = password_encrypted_with_lm_hash; + encryption_key = lanman_pw; + } else if (nt_pass_set) { + DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n", + user)); + return NT_STATUS_WRONG_PASSWORD; + } else if (lm_pass_set) { + if (lp_lanman_auth()) { + DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n", + user)); + } else { + DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n", + user)); + } + return NT_STATUS_WRONG_PASSWORD; + } else { + DEBUG(1, ("password change requested for user %s, but no password supplied!\n", + user)); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Decrypt the password with the key + */ + enc_key = (gnutls_datum_t) { + .data = discard_const_p(unsigned char, encryption_key), + .size = 16, + }; + + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_key, + NULL); + if (rc < 0) { + GNUTLS_FIPS140_SET_STRICT_MODE(); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + password_encrypted, + 516); + gnutls_cipher_deinit(cipher_hnd); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + if (!decode_pw_buffer(talloc_tos(), + password_encrypted, + pp_new_passwd, + &new_pw_len, + nt_pass_set ? CH_UTF16 : CH_DOS)) { + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * To ensure we got the correct new password, hash it and + * use it as a key to test the passed old password. + */ + + if (nt_pass_set) { + /* NT passwords, verify the NT hash. */ + + /* Calculate the MD4 hash (NT compatible) of the password */ + memset(new_nt_hash, '\0', 16); + E_md4hash(*pp_new_passwd, new_nt_hash); + + if (nt_pw) { + /* + * check the NT verifier + */ + rc = E_old_pw_hash(new_nt_hash, nt_pw, verifier); + if (rc != 0) { + NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER; + return gnutls_error_to_ntstatus(rc, status); + } + if (!mem_equal_const_time(verifier, old_nt_hash_encrypted, 16)) { + DEBUG(0, ("check_oem_password: old nt " + "password doesn't match.\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* We could check the LM password here, but there is + * little point, we already know the password is + * correct, and the LM password might not even be + * present. */ + + /* Further, LM hash generation algorithms + * differ with charset, so we could + * incorrectly fail a perfectly valid password + * change */ +#ifdef DEBUG_PASSWORD + DEBUG(100, + ("check_oem_password: password %s ok\n", *pp_new_passwd)); +#endif + return NT_STATUS_OK; + } + + if (lanman_pw) { + /* + * check the lm verifier + */ + rc = E_old_pw_hash(new_nt_hash, lanman_pw, verifier); + if (rc != 0) { + NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER; + return gnutls_error_to_ntstatus(rc, status); + } + if (!mem_equal_const_time(verifier, old_lm_hash_encrypted, 16)) { + DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); + return NT_STATUS_WRONG_PASSWORD; + } +#ifdef DEBUG_PASSWORD + DEBUG(100, + ("check_oem_password: password %s ok\n", *pp_new_passwd)); +#endif + return NT_STATUS_OK; + } + } + + if (lanman_pw && lm_pass_set) { + + E_deshash(*pp_new_passwd, new_lm_hash); + + /* + * check the lm verifier + */ + rc = E_old_pw_hash(new_lm_hash, lanman_pw, verifier); + if (rc != 0) { + NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER; + return gnutls_error_to_ntstatus(rc, status); + } + if (!mem_equal_const_time(verifier, old_lm_hash_encrypted, 16)) { + DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); + return NT_STATUS_WRONG_PASSWORD; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100, + ("check_oem_password: password %s ok\n", *pp_new_passwd)); +#endif + return NT_STATUS_OK; + } + + /* should not be reached */ + return NT_STATUS_WRONG_PASSWORD; +} + +static bool password_in_history(uint8_t nt_pw[NT_HASH_LEN], + uint32_t pw_history_len, + const uint8_t *pw_history) +{ + int i; + + dump_data(100, nt_pw, NT_HASH_LEN); + dump_data(100, pw_history, PW_HISTORY_ENTRY_LEN * pw_history_len); + + for (i=0; i<pw_history_len; i++) { + uint8_t new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN]; + const uint8_t *current_salt; + const uint8_t *old_nt_pw_salted_md5_hash; + + current_salt = &pw_history[i*PW_HISTORY_ENTRY_LEN]; + old_nt_pw_salted_md5_hash = current_salt + PW_HISTORY_SALT_LEN; + + if (all_zero(old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) { + /* Ignore zero valued entries. */ + continue; + } + + if (all_zero(current_salt, PW_HISTORY_SALT_LEN)) { + /* + * New format: zero salt and then plain nt hash. + * Directly compare the hashes. + */ + if (mem_equal_const_time(nt_pw, old_nt_pw_salted_md5_hash, + SALTED_MD5_HASH_LEN)) + { + return true; + } + } else { + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + /* + * Old format: md5sum of salted nt hash. + * Create salted version of new pw to compare. + */ + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return false; + } + + rc = gnutls_hash(hash_hnd, current_salt, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return false; + } + rc = gnutls_hash(hash_hnd, nt_pw, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return false; + } + gnutls_hash_deinit(hash_hnd, new_nt_pw_salted_md5_hash); + + if (mem_equal_const_time(new_nt_pw_salted_md5_hash, + old_nt_pw_salted_md5_hash, + SALTED_MD5_HASH_LEN)) { + return true; + } + } + } + return false; +} + +/*********************************************************** + This routine takes the given password and checks it against + the password history. Returns True if this password has been + found in the history list. +************************************************************/ + +static bool check_passwd_history(struct samu *sampass, const char *plaintext) +{ + uchar new_nt_p16[NT_HASH_LEN]; + const uint8_t *nt_pw; + const uint8_t *pwhistory; + uint32_t pwHisLen, curr_pwHisLen; + + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHisLen); + if (pwHisLen == 0) { + return False; + } + + pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen); + if (!pwhistory || curr_pwHisLen == 0) { + return False; + } + + /* Only examine the minimum of the current history len and + the stored history len. Avoids race conditions. */ + pwHisLen = MIN(pwHisLen,curr_pwHisLen); + + nt_pw = pdb_get_nt_passwd(sampass); + + E_md4hash(plaintext, new_nt_p16); + + if (mem_equal_const_time(nt_pw, new_nt_p16, NT_HASH_LEN)) { + DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n", + pdb_get_username(sampass) )); + return True; + } + + if (password_in_history(new_nt_p16, pwHisLen, pwhistory)) { + DEBUG(1,("check_passwd_history: proposed new password for " + "user %s found in history list !\n", + pdb_get_username(sampass) )); + return true; + } + return false; +} + +/*********************************************************** +************************************************************/ + +NTSTATUS check_password_complexity(const char *username, + const char *fullname, + const char *password, + enum samPwdChangeReason *samr_reject_reason) +{ + TALLOC_CTX *tosctx = talloc_tos(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int check_ret; + char *cmd; + + /* Use external script to check password complexity */ + if ((lp_check_password_script(tosctx, lp_sub) == NULL) + || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){ + return NT_STATUS_OK; + } + + cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u", + username); + if (!cmd) { + return NT_STATUS_PASSWORD_RESTRICTION; + } + + check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1); + if (check_ret != 0) { + return map_nt_error_from_unix_common(errno); + } + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + if (fullname != NULL) { + check_ret = setenv("SAMBA_CPS_FULL_NAME", fullname, 1); + } else { + unsetenv("SAMBA_CPS_FULL_NAME"); + } + if (check_ret != 0) { + return map_nt_error_from_unix_common(errno); + } + check_ret = smbrunsecret(cmd, password); + unsetenv("SAMBA_CPS_ACCOUNT_NAME"); + unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME"); + unsetenv("SAMBA_CPS_FULL_NAME"); + DEBUG(5,("check_password_complexity: check password script (%s) " + "returned [%d]\n", cmd, check_ret)); + TALLOC_FREE(cmd); + + if (check_ret != 0) { + DEBUG(1,("check_password_complexity: " + "check password script said new password is not good " + "enough!\n")); + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + return NT_STATUS_OK; +} + +/*********************************************************** + Code to change the oem password. Changes both the lanman + and NT hashes. Old_passwd is almost always NULL. + NOTE this function is designed to be called as root. Check the old password + is correct before calling. JRA. +************************************************************/ + +NTSTATUS change_oem_password(struct samu *hnd, const char *rhost, + char *old_passwd, char *new_passwd, + bool as_root, + enum samPwdChangeReason *samr_reject_reason) +{ + uint32_t min_len; + uint32_t refuse; + TALLOC_CTX *tosctx = talloc_tos(); + struct passwd *pass = NULL; + const char *username = pdb_get_username(hnd); + const char *fullname = pdb_get_fullname(hnd); + time_t can_change_time = pdb_get_pass_can_change_time(hnd); + NTSTATUS status; + + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR; + } + + /* check to see if the secdesc has previously been set to disallow */ + if (!pdb_get_pass_can_change(hnd)) { + DEBUG(1, ("user %s does not have permissions to change password\n", username)); + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR; + } + return NT_STATUS_ACCOUNT_RESTRICTION; + } + + /* check to see if it is a Machine account and if the policy + * denies machines to change the password. * + * Should we deny also SRVTRUST and/or DOMSTRUST ? .SSS. */ + if (pdb_get_acct_ctrl(hnd) & ACB_WSTRUST) { + if (pdb_get_account_policy(PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, &refuse) && refuse) { + DEBUG(1, ("Machine %s cannot change password now, " + "denied by Refuse Machine Password Change policy\n", + username)); + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR; + } + return NT_STATUS_ACCOUNT_RESTRICTION; + } + } + + /* removed calculation here, because passdb now calculates + based on policy. jmcd */ + if ((can_change_time != 0) && (time(NULL) < can_change_time)) { + DEBUG(1, ("user %s cannot change password now, must " + "wait until %s\n", username, + http_timestring(tosctx, can_change_time))); + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR; + } + return NT_STATUS_ACCOUNT_RESTRICTION; + } + + if (pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &min_len) && (str_charnum(new_passwd) < min_len)) { + DBG_WARNING("user %s cannot change password - " + "password too short\n" + " account policy min password len = %"PRIu32"\n", + username, + min_len); + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT; + } + return NT_STATUS_PASSWORD_RESTRICTION; +/* return NT_STATUS_PWD_TOO_SHORT; */ + } + + if (check_passwd_history(hnd,new_passwd)) { + if (samr_reject_reason) { + *samr_reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; + } + return NT_STATUS_PASSWORD_RESTRICTION; + } + + pass = Get_Pwnam_alloc(tosctx, username); + if (!pass) { + DEBUG(1, ("change_oem_password: Username %s does not exist in system !?!\n", username)); + return NT_STATUS_ACCESS_DENIED; + } + + status = check_password_complexity(username, + fullname, + new_passwd, + samr_reject_reason); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(pass); + return status; + } + + /* + * If unix password sync was requested, attempt to change + * the /etc/passwd database first. Return failure if this cannot + * be done. + * + * This occurs before the oem change, because we don't want to + * update it if chgpasswd failed. + * + * Conditional on lp_unix_password_sync() because we don't want + * to touch the unix db unless we have admin permission. + */ + + if(lp_unix_password_sync() && + !chgpasswd(username, rhost, pass, old_passwd, new_passwd, + as_root)) { + TALLOC_FREE(pass); + return NT_STATUS_ACCESS_DENIED; + } + + TALLOC_FREE(pass); + + if (!pdb_set_plaintext_passwd (hnd, new_passwd)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* Now write it into the file. */ + return pdb_update_sam_account (hnd); +} + +/*********************************************************** + Code to check and change the OEM hashed password. +************************************************************/ + +NTSTATUS pass_oem_change(char *user, const char *rhost, + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + enum samPwdChangeReason *reject_reason) +{ + char *new_passwd = NULL; + struct samu *sampass = NULL; + NTSTATUS nt_status; + bool ret = false; + bool updated_badpw = false; + NTSTATUS update_login_attempts_status; + char *mutex_name_by_user = NULL; + struct named_mutex *mtx = NULL; + + if (!(sampass = samu_new(NULL))) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = pdb_getsampwnam(sampass, user); + unbecome_root(); + + if (ret == false) { + DEBUG(0,("pass_oem_change: getsmbpwnam returned NULL\n")); + nt_status = NT_STATUS_NO_SUCH_USER; + goto done; + } + + /* Quit if the account was locked out. */ + if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) { + DEBUG(3,("check_sam_security: Account for user %s was locked out.\n", user)); + nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT; + goto done; + } + + nt_status = check_oem_password(user, + password_encrypted_with_lm_hash, + old_lm_hash_encrypted, + password_encrypted_with_nt_hash, + old_nt_hash_encrypted, + sampass, + &new_passwd); + + /* + * We must re-load the sam account information under a mutex + * lock to ensure we don't miss any concurrent account lockout + * changes. + */ + + /* Clear out old sampass info. */ + TALLOC_FREE(sampass); + + sampass = samu_new(NULL); + if (sampass == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mutex_name_by_user = talloc_asprintf(NULL, + "check_sam_security_mutex_%s", + user); + if (mutex_name_by_user == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Grab the named mutex under root with 30 second timeout. */ + become_root(); + mtx = grab_named_mutex(NULL, mutex_name_by_user, 30); + if (mtx != NULL) { + /* Re-load the account information if we got the mutex. */ + ret = pdb_getsampwnam(sampass, user); + } + unbecome_root(); + + /* Everything from here on until mtx is freed is done under the mutex.*/ + + if (mtx == NULL) { + DBG_ERR("Acquisition of mutex %s failed " + "for user %s\n", + mutex_name_by_user, + user); + nt_status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + if (!ret) { + /* + * Re-load of account failed. This could only happen if the + * user was deleted in the meantime. + */ + DBG_NOTICE("reload of user '%s' in passdb failed.\n", + user); + nt_status = NT_STATUS_NO_SUCH_USER; + goto done; + } + + /* + * Check if the account is now locked out - now under the mutex. + * This can happen if the server is under + * a password guess attack and the ACB_AUTOLOCK is set by + * another process. + */ + if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) { + DBG_NOTICE("Account for user %s was locked out.\n", user); + nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT; + goto done; + } + + /* + * Notify passdb backend of login success/failure. If not + * NT_STATUS_OK the backend doesn't like the login + */ + update_login_attempts_status = pdb_update_login_attempts(sampass, + NT_STATUS_IS_OK(nt_status)); + + if (!NT_STATUS_IS_OK(nt_status)) { + bool increment_bad_pw_count = false; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD) && + (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) && + NT_STATUS_IS_OK(update_login_attempts_status)) + { + increment_bad_pw_count = true; + } + + if (increment_bad_pw_count) { + pdb_increment_bad_password_count(sampass); + updated_badpw = true; + } else { + pdb_update_bad_password_count(sampass, + &updated_badpw); + } + } else { + + if ((pdb_get_acct_ctrl(sampass) & ACB_NORMAL) && + (pdb_get_bad_password_count(sampass) > 0)){ + pdb_set_bad_password_count(sampass, 0, PDB_CHANGED); + pdb_set_bad_password_time(sampass, 0, PDB_CHANGED); + updated_badpw = true; + } + } + + if (updated_badpw) { + NTSTATUS update_status; + become_root(); + update_status = pdb_update_sam_account(sampass); + unbecome_root(); + + if (!NT_STATUS_IS_OK(update_status)) { + DEBUG(1, ("Failed to modify entry: %s\n", + nt_errstr(update_status))); + } + } + + if (!NT_STATUS_IS_OK(nt_status)) { + goto done; + } + + /* We've already checked the old password here.... */ + become_root(); + nt_status = change_oem_password(sampass, rhost, NULL, new_passwd, + True, reject_reason); + unbecome_root(); + + BURN_STR(new_passwd); + +done: + TALLOC_FREE(sampass); + TALLOC_FREE(mutex_name_by_user); + TALLOC_FREE(mtx); + + return nt_status; +} + +NTSTATUS samr_set_password_aes(TALLOC_CTX *mem_ctx, + const DATA_BLOB *cdk, + struct samr_EncryptedPasswordAES *pwbuf, + char **new_password_str) +{ + DATA_BLOB pw_data = data_blob_null; + DATA_BLOB new_password = data_blob_null; + const DATA_BLOB ciphertext = + data_blob_const(pwbuf->cipher, pwbuf->cipher_len); + DATA_BLOB iv = data_blob_const(pwbuf->salt, sizeof(pwbuf->salt)); + NTSTATUS status; + bool ok; + + *new_password_str = NULL; + + status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt( + mem_ctx, + &ciphertext, + cdk, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + pwbuf->auth_data, + &pw_data); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_WRONG_PASSWORD; + } + + ok = decode_pwd_string_from_buffer514(mem_ctx, + pw_data.data, + CH_UTF16, + &new_password); + TALLOC_FREE(pw_data.data); + if (!ok) { + DBG_NOTICE("samr: failed to decode password buffer\n"); + return NT_STATUS_WRONG_PASSWORD; + } + + *new_password_str = talloc_strndup(mem_ctx, + (char *)new_password.data, + new_password.length); + TALLOC_FREE(new_password.data); + if (*new_password_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_keep_secret(*new_password_str); + + return NT_STATUS_OK; +} diff --git a/source3/rpc_server/samr/srv_samr_nt.c b/source3/rpc_server/samr/srv_samr_nt.c new file mode 100644 index 0000000..d26a8d5 --- /dev/null +++ b/source3/rpc_server/samr/srv_samr_nt.c @@ -0,0 +1,7910 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Paul Ashton 1997, + * Copyright (C) Marc Jacobsen 1999, + * Copyright (C) Jeremy Allison 2001-2008, + * Copyright (C) Jean François Micouleau 1998-2001, + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002, + * Copyright (C) Gerald (Jerry) Carter 2003-2004, + * Copyright (C) Simo Sorce 2003. + * Copyright (C) Volker Lendecke 2005. + * Copyright (C) Guenther Deschner 2008. + * + * 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/>. + */ + +/* + * This is the implementation of the SAMR code. + */ + +#include "includes.h" +#include "system/passwd.h" +#include "../libcli/auth/libcli_auth.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "../librpc/gen_ndr/ndr_samr.h" +#include "../librpc/gen_ndr/ndr_samr_scompat.h" +#include "rpc_server/samr/srv_samr_util.h" +#include "secrets.h" +#include "rpc_client/init_lsa.h" +#include "../libcli/security/security.h" +#include "passdb.h" +#include "auth.h" +#include "rpc_server/srv_access_check.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/util/base64.h" +#include "param/param.h" +#include "librpc/rpc/dcerpc_helper.h" +#include "librpc/rpc/dcerpc_samr.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include "lib/global_contexts.h" +#include "nsswitch/winbind_client.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define SAMR_USR_RIGHTS_WRITE_PW \ + ( READ_CONTROL_ACCESS | \ + SAMR_USER_ACCESS_CHANGE_PASSWORD | \ + SAMR_USER_ACCESS_SET_LOC_COM) +#define SAMR_USR_RIGHTS_CANT_WRITE_PW \ + ( READ_CONTROL_ACCESS | SAMR_USER_ACCESS_SET_LOC_COM ) + +#define DISP_INFO_CACHE_TIMEOUT 10 + +#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */ +#define MAX_SAM_ENTRIES_W95 50 + +enum samr_handle { + SAMR_HANDLE_CONNECT, + SAMR_HANDLE_DOMAIN, + SAMR_HANDLE_USER, + SAMR_HANDLE_GROUP, + SAMR_HANDLE_ALIAS +}; + +struct samr_info { + uint32_t access_granted; + struct dom_sid sid; + struct disp_info *disp_info; +}; + +typedef struct disp_info { + struct dom_sid sid; /* identify which domain this is. */ + struct pdb_search *users; /* querydispinfo 1 and 4 */ + struct pdb_search *machines; /* querydispinfo 2 */ + struct pdb_search *groups; /* querydispinfo 3 and 5, enumgroups */ + struct pdb_search *aliases; /* enumaliases */ + + uint32_t enum_acb_mask; + struct pdb_search *enum_users; /* enumusers with a mask */ + + struct tevent_timer *cache_timeout_event; /* cache idle timeout + * handler. */ +} DISP_INFO; + +static const struct generic_mapping sam_generic_mapping = { + GENERIC_RIGHTS_SAM_READ, + GENERIC_RIGHTS_SAM_WRITE, + GENERIC_RIGHTS_SAM_EXECUTE, + GENERIC_RIGHTS_SAM_ALL_ACCESS}; +static const struct generic_mapping dom_generic_mapping = { + GENERIC_RIGHTS_DOMAIN_READ, + GENERIC_RIGHTS_DOMAIN_WRITE, + GENERIC_RIGHTS_DOMAIN_EXECUTE, + GENERIC_RIGHTS_DOMAIN_ALL_ACCESS}; +static const struct generic_mapping usr_generic_mapping = { + GENERIC_RIGHTS_USER_READ, + GENERIC_RIGHTS_USER_WRITE, + GENERIC_RIGHTS_USER_EXECUTE, + GENERIC_RIGHTS_USER_ALL_ACCESS}; +static const struct generic_mapping usr_nopwchange_generic_mapping = { + GENERIC_RIGHTS_USER_READ, + GENERIC_RIGHTS_USER_WRITE, + GENERIC_RIGHTS_USER_EXECUTE & ~SAMR_USER_ACCESS_CHANGE_PASSWORD, + GENERIC_RIGHTS_USER_ALL_ACCESS}; +static const struct generic_mapping grp_generic_mapping = { + GENERIC_RIGHTS_GROUP_READ, + GENERIC_RIGHTS_GROUP_WRITE, + GENERIC_RIGHTS_GROUP_EXECUTE, + GENERIC_RIGHTS_GROUP_ALL_ACCESS}; +static const struct generic_mapping ali_generic_mapping = { + GENERIC_RIGHTS_ALIAS_READ, + GENERIC_RIGHTS_ALIAS_WRITE, + GENERIC_RIGHTS_ALIAS_EXECUTE, + GENERIC_RIGHTS_ALIAS_ALL_ACCESS}; + +/******************************************************************* +*******************************************************************/ +static NTSTATUS create_samr_policy_handle(TALLOC_CTX *mem_ctx, + struct pipes_struct *p, + enum samr_handle type, + uint32_t acc_granted, + struct dom_sid *sid, + struct disp_info *disp_info, + struct policy_handle *handle) +{ + struct samr_info *info = NULL; + bool ok; + + ZERO_STRUCTP(handle); + + info = talloc_zero(mem_ctx, struct samr_info); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + info->access_granted = acc_granted; + + if (sid != NULL) { + sid_copy(&info->sid, sid); + } + + if (disp_info != NULL) { + info->disp_info = disp_info; + } + + ok = create_policy_hnd(p, handle, type, info); + if (!ok) { + talloc_free(info); + ZERO_STRUCTP(handle); + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS samr_handle_access_check(uint32_t access_granted, + uint32_t access_required, + uint32_t *paccess_granted) +{ + if ((access_required & access_granted) != access_required) { + if (root_mode()) { + DBG_INFO("ACCESS should be DENIED (granted: " + "%#010x; required: %#010x) but overwritten " + "by euid == 0\n", access_granted, + access_required); + goto okay; + } + DBG_NOTICE("ACCESS DENIED (granted: %#010x; required: " + "%#010x)\n", access_granted, access_required); + return NT_STATUS_ACCESS_DENIED; + } + +okay: + if (paccess_granted != NULL) { + *paccess_granted = access_granted; + } + return NT_STATUS_OK; +} + +static void *samr_policy_handle_find(struct pipes_struct *p, + const struct policy_handle *handle, + uint8_t handle_type, + uint32_t access_required, + uint32_t *access_granted, + NTSTATUS *pstatus) +{ + struct samr_info *info = NULL; + NTSTATUS status; + + info = find_policy_by_hnd(p, + handle, + handle_type, + struct samr_info, + &status); + if (!NT_STATUS_IS_OK(status)) { + *pstatus = NT_STATUS_INVALID_HANDLE; + return NULL; + } + + status = samr_handle_access_check(info->access_granted, + access_required, + access_granted); + if (!NT_STATUS_IS_OK(status)) { + *pstatus = status; + return NULL; + } + + *pstatus = NT_STATUS_OK; + return info; +} + +static NTSTATUS make_samr_object_sd( TALLOC_CTX *ctx, struct security_descriptor **psd, size_t *sd_size, + const struct generic_mapping *map, + struct dom_sid *sid, uint32_t sid_access ) +{ + struct dom_sid domadmin_sid; + struct security_ace ace[5]; /* at most 5 entries */ + size_t i = 0; + + struct security_acl *psa = NULL; + + /* basic access for Everyone */ + + init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED, + map->generic_execute | map->generic_read, 0); + + /* add Full Access 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */ + + init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators, + SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0); + init_sec_ace(&ace[i++], &global_sid_Builtin_Account_Operators, + SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0); + + /* Add Full Access for Domain Admins if we are a DC */ + + if ( IS_DC ) { + sid_compose(&domadmin_sid, get_global_sam_sid(), + DOMAIN_RID_ADMINS); + init_sec_ace(&ace[i++], &domadmin_sid, + SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0); + } + + /* if we have a sid, give it some special access */ + + if ( sid ) { + init_sec_ace(&ace[i++], sid, SEC_ACE_TYPE_ACCESS_ALLOWED, sid_access, 0); + } + + /* create the security descriptor */ + + if ((psa = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace)) == NULL) + return NT_STATUS_NO_MEMORY; + + if ((*psd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1, + SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL, + psa, sd_size)) == NULL) + return NT_STATUS_NO_MEMORY; + + return NT_STATUS_OK; +} + +/******************************************************************* + Fetch or create a dispinfo struct. +********************************************************************/ + +static DISP_INFO *get_samr_dispinfo_by_sid(const struct dom_sid *psid) +{ + /* + * We do a static cache for DISP_INFO's here. Explanation can be found + * in Jeremy's checkin message to r11793: + * + * Fix the SAMR cache so it works across completely insane + * client behaviour (ie.: + * open pipe/open SAMR handle/enumerate 0 - 1024 + * close SAMR handle, close pipe. + * open pipe/open SAMR handle/enumerate 1024 - 2048... + * close SAMR handle, close pipe. + * And on ad-nausium. Amazing.... probably object-oriented + * client side programming in action yet again. + * This change should *massively* improve performance when + * enumerating users from an LDAP database. + * Jeremy. + * + * "Our" and the builtin domain are the only ones where we ever + * enumerate stuff, so just cache 2 entries. + */ + + static struct disp_info *builtin_dispinfo; + static struct disp_info *domain_dispinfo; + + /* There are two cases to consider here: + 1) The SID is a domain SID and we look for an equality match, or + 2) This is an account SID and so we return the DISP_INFO* for our + domain */ + + if (psid == NULL) { + return NULL; + } + + if (sid_check_is_builtin(psid) || sid_check_is_in_builtin(psid)) { + /* + * Necessary only once, but it does not really hurt. + */ + if (builtin_dispinfo == NULL) { + builtin_dispinfo = talloc_zero(NULL, struct disp_info); + if (builtin_dispinfo == NULL) { + return NULL; + } + } + sid_copy(&builtin_dispinfo->sid, &global_sid_Builtin); + + return builtin_dispinfo; + } + + if (sid_check_is_our_sam(psid) || sid_check_is_in_our_sam(psid)) { + /* + * Necessary only once, but it does not really hurt. + */ + if (domain_dispinfo == NULL) { + domain_dispinfo = talloc_zero(NULL, struct disp_info); + if (domain_dispinfo == NULL) { + return NULL; + } + } + sid_copy(&domain_dispinfo->sid, get_global_sam_sid()); + + return domain_dispinfo; + } + + return NULL; +} + +/******************************************************************* + Function to free the per SID data. + ********************************************************************/ + +static void free_samr_cache(DISP_INFO *disp_info) +{ + struct dom_sid_buf buf; + + DEBUG(10, ("free_samr_cache: deleting cache for SID %s\n", + dom_sid_str_buf(&disp_info->sid, &buf))); + + /* We need to become root here because the paged search might have to + * tell the LDAP server we're not interested in the rest anymore. */ + + become_root(); + + TALLOC_FREE(disp_info->users); + TALLOC_FREE(disp_info->machines); + TALLOC_FREE(disp_info->groups); + TALLOC_FREE(disp_info->aliases); + TALLOC_FREE(disp_info->enum_users); + + unbecome_root(); +} + +/******************************************************************* + Idle event handler. Throw away the disp info cache. + ********************************************************************/ + +static void disp_info_cache_idle_timeout_handler(struct tevent_context *ev_ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + DISP_INFO *disp_info = (DISP_INFO *)private_data; + + TALLOC_FREE(disp_info->cache_timeout_event); + + DEBUG(10, ("disp_info_cache_idle_timeout_handler: caching timed " + "out\n")); + free_samr_cache(disp_info); +} + +/******************************************************************* + Setup cache removal idle event handler. + ********************************************************************/ + +static void set_disp_info_cache_timeout(DISP_INFO *disp_info, time_t secs_fromnow) +{ + struct dom_sid_buf buf; + + /* Remove any pending timeout and update. */ + + TALLOC_FREE(disp_info->cache_timeout_event); + + DEBUG(10,("set_disp_info_cache_timeout: caching enumeration for " + "SID %s for %u seconds\n", + dom_sid_str_buf(&disp_info->sid, &buf), + (unsigned int)secs_fromnow )); + + disp_info->cache_timeout_event = tevent_add_timer( + global_event_context(), NULL, + timeval_current_ofs(secs_fromnow, 0), + disp_info_cache_idle_timeout_handler, (void *)disp_info); +} + +/******************************************************************* + Force flush any cache. We do this on any samr_set_xxx call. + We must also remove the timeout handler. + ********************************************************************/ + +static void force_flush_samr_cache(const struct dom_sid *sid) +{ + struct disp_info *disp_info = get_samr_dispinfo_by_sid(sid); + + if ((disp_info == NULL) || (disp_info->cache_timeout_event == NULL)) { + return; + } + + DEBUG(10,("force_flush_samr_cache: clearing idle event\n")); + TALLOC_FREE(disp_info->cache_timeout_event); + free_samr_cache(disp_info); +} + +/******************************************************************* + Ensure password info is never given out. Paranioa... JRA. + ********************************************************************/ + +static void samr_clear_sam_passwd(struct samu *sam_pass) +{ + + if (!sam_pass) + return; + + /* These now zero out the old password */ + + pdb_set_lanman_passwd(sam_pass, NULL, PDB_DEFAULT); + pdb_set_nt_passwd(sam_pass, NULL, PDB_DEFAULT); +} + +static uint32_t count_sam_users(struct disp_info *info, uint32_t acct_flags) +{ + struct samr_displayentry *entry; + + if (sid_check_is_builtin(&info->sid)) { + /* No users in builtin. */ + return 0; + } + + if (info->users == NULL) { + info->users = pdb_search_users(info, acct_flags); + if (info->users == NULL) { + return 0; + } + } + /* Fetch the last possible entry, thus trigger an enumeration */ + pdb_search_entries(info->users, 0xffffffff, 1, &entry); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT); + + return info->users->num_entries; +} + +static uint32_t count_sam_groups(struct disp_info *info) +{ + struct samr_displayentry *entry; + + if (sid_check_is_builtin(&info->sid)) { + /* No groups in builtin. */ + return 0; + } + + if (info->groups == NULL) { + info->groups = pdb_search_groups(info); + if (info->groups == NULL) { + return 0; + } + } + /* Fetch the last possible entry, thus trigger an enumeration */ + pdb_search_entries(info->groups, 0xffffffff, 1, &entry); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT); + + return info->groups->num_entries; +} + +static uint32_t count_sam_aliases(struct disp_info *info) +{ + struct samr_displayentry *entry; + + if (info->aliases == NULL) { + info->aliases = pdb_search_aliases(info, &info->sid); + if (info->aliases == NULL) { + return 0; + } + } + /* Fetch the last possible entry, thus trigger an enumeration */ + pdb_search_entries(info->aliases, 0xffffffff, 1, &entry); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT); + + return info->aliases->num_entries; +} + +/******************************************************************* + _samr_Close + ********************************************************************/ + +NTSTATUS _samr_Close(struct pipes_struct *p, struct samr_Close *r) +{ + if (!close_policy_hnd(p, r->in.handle)) { + return NT_STATUS_INVALID_HANDLE; + } + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_OpenDomain + ********************************************************************/ + +NTSTATUS _samr_OpenDomain(struct pipes_struct *p, + struct samr_OpenDomain *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + NTSTATUS status; + size_t sd_size; + uint32_t extra_access = SAMR_DOMAIN_ACCESS_CREATE_USER; + struct disp_info *disp_info = NULL; + + /* find the connection policy handle. */ + (void)samr_policy_handle_find(p, + r->in.connect_handle, + SAMR_HANDLE_CONNECT, + 0, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /*check if access can be granted as requested by client. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd( p->mem_ctx, &psd, &sd_size, &dom_generic_mapping, NULL, 0 ); + se_map_generic( &des_access, &dom_generic_mapping ); + + /* + * Users with SeAddUser get the ability to manipulate groups + * and aliases. + */ + if (security_token_has_privilege( + session_info->security_token, SEC_PRIV_ADD_USERS)) { + extra_access |= (SAMR_DOMAIN_ACCESS_CREATE_GROUP | + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS | + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT | + SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS | + SAMR_DOMAIN_ACCESS_CREATE_ALIAS); + } + + /* + * Users with SeMachineAccount or SeAddUser get additional + * SAMR_DOMAIN_ACCESS_CREATE_USER access. + */ + + status = access_check_object( psd, session_info->security_token, + SEC_PRIV_MACHINE_ACCOUNT, SEC_PRIV_ADD_USERS, + extra_access, des_access, + &acc_granted, "_samr_OpenDomain" ); + + if ( !NT_STATUS_IS_OK(status) ) + return status; + + if (!sid_check_is_our_sam(r->in.sid) && + !sid_check_is_builtin(r->in.sid)) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + disp_info = get_samr_dispinfo_by_sid(r->in.sid); + + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_DOMAIN, + acc_granted, + r->in.sid, + disp_info, + r->out.domain_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("_samr_OpenDomain: %d\n", __LINE__)); + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_GetUserPwInfo + ********************************************************************/ + +NTSTATUS _samr_GetUserPwInfo(struct pipes_struct *p, + struct samr_GetUserPwInfo *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct samr_info *uinfo; + enum lsa_SidType sid_type; + uint32_t min_password_length = 0; + uint32_t password_properties = 0; + bool ret = false; + NTSTATUS status; + + DEBUG(5,("_samr_GetUserPwInfo: %d\n", __LINE__)); + + uinfo = samr_policy_handle_find(p, r->in.user_handle, + SAMR_HANDLE_USER, + SAMR_USER_ACCESS_GET_ATTRIBUTES, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_check_is_in_our_sam(&uinfo->sid)) { + return NT_STATUS_OBJECT_TYPE_MISMATCH; + } + + become_root(); + ret = lookup_sid(p->mem_ctx, &uinfo->sid, NULL, NULL, &sid_type); + unbecome_root(); + if (ret == false) { + return NT_STATUS_NO_SUCH_USER; + } + + switch (sid_type) { + case SID_NAME_USER: + become_root(); + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, + &min_password_length); + pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, + &password_properties); + unbecome_root(); + + if (lp_check_password_script(talloc_tos(), lp_sub) + && *lp_check_password_script(talloc_tos(), lp_sub)) { + password_properties |= DOMAIN_PASSWORD_COMPLEX; + } + + break; + default: + break; + } + + r->out.info->min_password_length = min_password_length; + r->out.info->password_properties = password_properties; + + DEBUG(5,("_samr_GetUserPwInfo: %d\n", __LINE__)); + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_SetSecurity + ********************************************************************/ + +NTSTATUS _samr_SetSecurity(struct pipes_struct *p, + struct samr_SetSecurity *r) +{ + struct samr_info *uinfo; + uint32_t i; + struct security_acl *dacl; + bool ret; + struct samu *sampass=NULL; + NTSTATUS status; + + uinfo = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_USER, + SAMR_USER_ACCESS_SET_ATTRIBUTES, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!(sampass = samu_new( p->mem_ctx))) { + DEBUG(0,("No memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* get the user record */ + become_root(); + ret = pdb_getsampwsid(sampass, &uinfo->sid); + unbecome_root(); + + if (!ret) { + struct dom_sid_buf buf; + DEBUG(4, ("User %s not found\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + TALLOC_FREE(sampass); + return NT_STATUS_INVALID_HANDLE; + } + + dacl = r->in.sdbuf->sd->dacl; + for (i=0; i < dacl->num_aces; i++) { + if (dom_sid_equal(&uinfo->sid, &dacl->aces[i].trustee)) { + ret = pdb_set_pass_can_change(sampass, + (dacl->aces[i].access_mask & + SAMR_USER_ACCESS_CHANGE_PASSWORD) ? + True: False); + break; + } + } + + if (!ret) { + TALLOC_FREE(sampass); + return NT_STATUS_ACCESS_DENIED; + } + + become_root(); + status = pdb_update_sam_account(sampass); + unbecome_root(); + + TALLOC_FREE(sampass); + + return status; +} + +/******************************************************************* + build correct perms based on policies and password times for _samr_query_sec_obj +*******************************************************************/ +static bool check_change_pw_access(TALLOC_CTX *mem_ctx, struct dom_sid *user_sid) +{ + struct samu *sampass=NULL; + bool ret; + + if ( !(sampass = samu_new( mem_ctx )) ) { + DEBUG(0,("No memory!\n")); + return False; + } + + become_root(); + ret = pdb_getsampwsid(sampass, user_sid); + unbecome_root(); + + if (ret == False) { + struct dom_sid_buf buf; + DEBUG(4,("User %s not found\n", + dom_sid_str_buf(user_sid, &buf))); + TALLOC_FREE(sampass); + return False; + } + + DEBUG(3,("User:[%s]\n", pdb_get_username(sampass) )); + + if (pdb_get_pass_can_change(sampass)) { + TALLOC_FREE(sampass); + return True; + } + TALLOC_FREE(sampass); + return False; +} + + +/******************************************************************* + _samr_QuerySecurity + ********************************************************************/ + +NTSTATUS _samr_QuerySecurity(struct pipes_struct *p, + struct samr_QuerySecurity *r) +{ + struct samr_info *info; + NTSTATUS status; + struct security_descriptor * psd = NULL; + size_t sd_size = 0; + struct dom_sid_buf buf; + + info = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_CONNECT, + SEC_STD_READ_CONTROL, + NULL, + &status); + if (NT_STATUS_IS_OK(status)) { + DEBUG(5,("_samr_QuerySecurity: querying security on SAM\n")); + status = make_samr_object_sd(p->mem_ctx, &psd, &sd_size, + &sam_generic_mapping, NULL, 0); + goto done; + } + + info = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_DOMAIN, + SEC_STD_READ_CONTROL, + NULL, + &status); + if (NT_STATUS_IS_OK(status)) { + DEBUG(5,("_samr_QuerySecurity: querying security on Domain " + "with SID: %s\n", + dom_sid_str_buf(&info->sid, &buf))); + /* + * TODO: Builtin probably needs a different SD with restricted + * write access + */ + status = make_samr_object_sd(p->mem_ctx, &psd, &sd_size, + &dom_generic_mapping, NULL, 0); + goto done; + } + + info = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_USER, + SEC_STD_READ_CONTROL, + NULL, + &status); + if (NT_STATUS_IS_OK(status)) { + DEBUG(10,("_samr_QuerySecurity: querying security on user " + "Object with SID: %s\n", + dom_sid_str_buf(&info->sid, &buf))); + if (check_change_pw_access(p->mem_ctx, &info->sid)) { + status = make_samr_object_sd( + p->mem_ctx, &psd, &sd_size, + &usr_generic_mapping, + &info->sid, SAMR_USR_RIGHTS_WRITE_PW); + } else { + status = make_samr_object_sd( + p->mem_ctx, &psd, &sd_size, + &usr_nopwchange_generic_mapping, + &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW); + } + goto done; + } + + info = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_GROUP, + SEC_STD_READ_CONTROL, + NULL, + &status); + if (NT_STATUS_IS_OK(status)) { + /* + * TODO: different SDs have to be generated for aliases groups + * and users. Currently all three get a default user SD + */ + DEBUG(10,("_samr_QuerySecurity: querying security on group " + "Object with SID: %s\n", + dom_sid_str_buf(&info->sid, &buf))); + status = make_samr_object_sd( + p->mem_ctx, &psd, &sd_size, + &usr_nopwchange_generic_mapping, + &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW); + goto done; + } + + info = samr_policy_handle_find(p, + r->in.handle, + SAMR_HANDLE_ALIAS, + SEC_STD_READ_CONTROL, + NULL, + &status); + if (NT_STATUS_IS_OK(status)) { + /* + * TODO: different SDs have to be generated for aliases groups + * and users. Currently all three get a default user SD + */ + DEBUG(10,("_samr_QuerySecurity: querying security on alias " + "Object with SID: %s\n", + dom_sid_str_buf(&info->sid, &buf))); + status = make_samr_object_sd( + p->mem_ctx, &psd, &sd_size, + &usr_nopwchange_generic_mapping, + &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW); + goto done; + } + + return NT_STATUS_OBJECT_TYPE_MISMATCH; +done: + if ((*r->out.sdbuf = make_sec_desc_buf(p->mem_ctx, sd_size, psd)) == NULL) + return NT_STATUS_NO_MEMORY; + + return status; +} + +/******************************************************************* +makes a SAM_ENTRY / UNISTR2* structure from a user list. +********************************************************************/ + +static NTSTATUS make_user_sam_entry_list(TALLOC_CTX *ctx, + struct samr_SamEntry **sam_pp, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + struct samr_SamEntry *sam; + + *sam_pp = NULL; + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + sam = talloc_zero_array(ctx, struct samr_SamEntry, num_entries); + if (sam == NULL) { + DEBUG(0, ("make_user_sam_entry_list: TALLOC_ZERO failed!\n")); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries; i++) { +#if 0 + /* + * usrmgr expects a non-NULL terminated string with + * trust relationships + */ + if (entries[i].acct_flags & ACB_DOMTRUST) { + init_unistr2(&uni_temp_name, entries[i].account_name, + UNI_FLAGS_NONE); + } else { + init_unistr2(&uni_temp_name, entries[i].account_name, + UNI_STR_TERMINATE); + } +#endif + init_lsa_String(&sam[i].name, entries[i].account_name); + sam[i].idx = entries[i].rid; + } + + *sam_pp = sam; + + return NT_STATUS_OK; +} + +#define MAX_SAM_ENTRIES MAX_SAM_ENTRIES_W2K + +/******************************************************************* + _samr_EnumDomainUsers + ********************************************************************/ + +NTSTATUS _samr_EnumDomainUsers(struct pipes_struct *p, + struct samr_EnumDomainUsers *r) +{ + NTSTATUS status; + struct samr_info *dinfo; + uint32_t num_account; + uint32_t enum_context = *r->in.resume_handle; + enum remote_arch_types ra_type = get_remote_arch(); + int max_sam_entries = (ra_type == RA_WIN95) ? MAX_SAM_ENTRIES_W95 : MAX_SAM_ENTRIES_W2K; + uint32_t max_entries = max_sam_entries; + struct samr_displayentry *entries = NULL; + struct samr_SamArray *samr_array = NULL; + struct samr_SamEntry *samr_entries = NULL; + + DEBUG(5,("_samr_EnumDomainUsers: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray); + if (!samr_array) { + return NT_STATUS_NO_MEMORY; + } + *r->out.sam = samr_array; + + if (sid_check_is_builtin(&dinfo->sid)) { + /* No users in builtin. */ + *r->out.resume_handle = *r->in.resume_handle; + DEBUG(5,("_samr_EnumDomainUsers: No users in BUILTIN\n")); + return status; + } + + become_root(); + + /* AS ROOT !!!! */ + + if ((dinfo->disp_info->enum_users != NULL) && + (dinfo->disp_info->enum_acb_mask != r->in.acct_flags)) { + TALLOC_FREE(dinfo->disp_info->enum_users); + } + + if (dinfo->disp_info->enum_users == NULL) { + dinfo->disp_info->enum_users = pdb_search_users( + dinfo->disp_info, r->in.acct_flags); + dinfo->disp_info->enum_acb_mask = r->in.acct_flags; + } + + if (dinfo->disp_info->enum_users == NULL) { + /* END AS ROOT !!!! */ + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + + num_account = pdb_search_entries(dinfo->disp_info->enum_users, + enum_context, max_entries, + &entries); + + /* END AS ROOT !!!! */ + + unbecome_root(); + + if (num_account == 0) { + DEBUG(5, ("_samr_EnumDomainUsers: enumeration handle over " + "total entries\n")); + *r->out.resume_handle = *r->in.resume_handle; + return NT_STATUS_OK; + } + + status = make_user_sam_entry_list(p->mem_ctx, &samr_entries, + num_account, enum_context, + entries); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (max_entries <= num_account) { + status = STATUS_MORE_ENTRIES; + } else { + status = NT_STATUS_OK; + } + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT); + + DEBUG(5, ("_samr_EnumDomainUsers: %d\n", __LINE__)); + + samr_array->count = num_account; + samr_array->entries = samr_entries; + + *r->out.resume_handle = *r->in.resume_handle + num_account; + *r->out.num_entries = num_account; + + DEBUG(5,("_samr_EnumDomainUsers: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* +makes a SAM_ENTRY / UNISTR2* structure from a group list. +********************************************************************/ + +static void make_group_sam_entry_list(TALLOC_CTX *ctx, + struct samr_SamEntry **sam_pp, + uint32_t num_sam_entries, + struct samr_displayentry *entries) +{ + struct samr_SamEntry *sam; + uint32_t i; + + *sam_pp = NULL; + + if (num_sam_entries == 0) { + return; + } + + sam = talloc_zero_array(ctx, struct samr_SamEntry, num_sam_entries); + if (sam == NULL) { + return; + } + + for (i = 0; i < num_sam_entries; i++) { + /* + * JRA. I think this should include the null. TNG does not. + */ + init_lsa_String(&sam[i].name, entries[i].account_name); + sam[i].idx = entries[i].rid; + } + + *sam_pp = sam; +} + +/******************************************************************* + _samr_EnumDomainGroups + ********************************************************************/ + +NTSTATUS _samr_EnumDomainGroups(struct pipes_struct *p, + struct samr_EnumDomainGroups *r) +{ + NTSTATUS status; + struct samr_info *dinfo; + struct samr_displayentry *groups; + uint32_t num_groups; + struct samr_SamArray *samr_array = NULL; + struct samr_SamEntry *samr_entries = NULL; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("_samr_EnumDomainGroups: %d\n", __LINE__)); + + samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray); + if (!samr_array) { + return NT_STATUS_NO_MEMORY; + } + *r->out.sam = samr_array; + + if (sid_check_is_builtin(&dinfo->sid)) { + /* No groups in builtin. */ + *r->out.resume_handle = *r->in.resume_handle; + DEBUG(5,("_samr_EnumDomainGroups: No groups in BUILTIN\n")); + return status; + } + + /* the domain group array is being allocated in the function below */ + + become_root(); + + if (dinfo->disp_info->groups == NULL) { + dinfo->disp_info->groups = pdb_search_groups(dinfo->disp_info); + + if (dinfo->disp_info->groups == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + } + + num_groups = pdb_search_entries(dinfo->disp_info->groups, + *r->in.resume_handle, + MAX_SAM_ENTRIES, &groups); + unbecome_root(); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT); + + make_group_sam_entry_list(p->mem_ctx, &samr_entries, + num_groups, groups); + + if (MAX_SAM_ENTRIES <= num_groups) { + status = STATUS_MORE_ENTRIES; + } else { + status = NT_STATUS_OK; + } + + samr_array->count = num_groups; + samr_array->entries = samr_entries; + + *r->out.num_entries = num_groups; + *r->out.resume_handle = num_groups + *r->in.resume_handle; + + DEBUG(5,("_samr_EnumDomainGroups: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _samr_EnumDomainAliases + ********************************************************************/ + +NTSTATUS _samr_EnumDomainAliases(struct pipes_struct *p, + struct samr_EnumDomainAliases *r) +{ + NTSTATUS status; + struct samr_info *dinfo; + struct samr_displayentry *aliases; + uint32_t num_aliases = 0; + struct samr_SamArray *samr_array = NULL; + struct samr_SamEntry *samr_entries = NULL; + struct dom_sid_buf buf; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("_samr_EnumDomainAliases: sid %s\n", + dom_sid_str_buf(&dinfo->sid, &buf))); + + samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray); + if (!samr_array) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + + if (dinfo->disp_info->aliases == NULL) { + dinfo->disp_info->aliases = pdb_search_aliases( + dinfo->disp_info, &dinfo->sid); + if (dinfo->disp_info->aliases == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + } + + num_aliases = pdb_search_entries(dinfo->disp_info->aliases, + *r->in.resume_handle, + MAX_SAM_ENTRIES, &aliases); + unbecome_root(); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT); + + make_group_sam_entry_list(p->mem_ctx, &samr_entries, + num_aliases, aliases); + + DEBUG(5,("_samr_EnumDomainAliases: %d\n", __LINE__)); + + if (MAX_SAM_ENTRIES <= num_aliases) { + status = STATUS_MORE_ENTRIES; + } else { + status = NT_STATUS_OK; + } + + samr_array->count = num_aliases; + samr_array->entries = samr_entries; + + *r->out.sam = samr_array; + *r->out.num_entries = num_aliases; + *r->out.resume_handle = num_aliases + *r->in.resume_handle; + + return status; +} + +/******************************************************************* + inits a samr_DispInfoGeneral structure. +********************************************************************/ + +static NTSTATUS init_samr_dispinfo_1(TALLOC_CTX *ctx, + struct samr_DispInfoGeneral *r, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + + DEBUG(10, ("init_samr_dispinfo_1: num_entries: %d\n", num_entries)); + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + r->count = num_entries; + + r->entries = talloc_zero_array(ctx, struct samr_DispEntryGeneral, num_entries); + if (!r->entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries ; i++) { + + init_lsa_String(&r->entries[i].account_name, + entries[i].account_name); + + init_lsa_String(&r->entries[i].description, + entries[i].description); + + init_lsa_String(&r->entries[i].full_name, + entries[i].fullname); + + r->entries[i].rid = entries[i].rid; + r->entries[i].acct_flags = entries[i].acct_flags; + r->entries[i].idx = start_idx+i+1; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + inits a samr_DispInfoFull structure. +********************************************************************/ + +static NTSTATUS init_samr_dispinfo_2(TALLOC_CTX *ctx, + struct samr_DispInfoFull *r, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + + DEBUG(10, ("init_samr_dispinfo_2: num_entries: %d\n", num_entries)); + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + r->count = num_entries; + + r->entries = talloc_zero_array(ctx, struct samr_DispEntryFull, num_entries); + if (!r->entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries ; i++) { + + init_lsa_String(&r->entries[i].account_name, + entries[i].account_name); + + init_lsa_String(&r->entries[i].description, + entries[i].description); + + r->entries[i].rid = entries[i].rid; + r->entries[i].acct_flags = entries[i].acct_flags; + r->entries[i].idx = start_idx+i+1; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + inits a samr_DispInfoFullGroups structure. +********************************************************************/ + +static NTSTATUS init_samr_dispinfo_3(TALLOC_CTX *ctx, + struct samr_DispInfoFullGroups *r, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + + DEBUG(5, ("init_samr_dispinfo_3: num_entries: %d\n", num_entries)); + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + r->count = num_entries; + + r->entries = talloc_zero_array(ctx, struct samr_DispEntryFullGroup, num_entries); + if (!r->entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries ; i++) { + + init_lsa_String(&r->entries[i].account_name, + entries[i].account_name); + + init_lsa_String(&r->entries[i].description, + entries[i].description); + + r->entries[i].rid = entries[i].rid; + r->entries[i].acct_flags = entries[i].acct_flags; + r->entries[i].idx = start_idx+i+1; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + inits a samr_DispInfoAscii structure. +********************************************************************/ + +static NTSTATUS init_samr_dispinfo_4(TALLOC_CTX *ctx, + struct samr_DispInfoAscii *r, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + + DEBUG(5, ("init_samr_dispinfo_4: num_entries: %d\n", num_entries)); + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + r->count = num_entries; + + r->entries = talloc_zero_array(ctx, struct samr_DispEntryAscii, num_entries); + if (!r->entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries ; i++) { + + init_lsa_AsciiStringLarge(&r->entries[i].account_name, + entries[i].account_name); + + r->entries[i].idx = start_idx+i+1; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + inits a samr_DispInfoAscii structure. +********************************************************************/ + +static NTSTATUS init_samr_dispinfo_5(TALLOC_CTX *ctx, + struct samr_DispInfoAscii *r, + uint32_t num_entries, + uint32_t start_idx, + struct samr_displayentry *entries) +{ + uint32_t i; + + DEBUG(5, ("init_samr_dispinfo_5: num_entries: %d\n", num_entries)); + + if (num_entries == 0) { + return NT_STATUS_OK; + } + + r->count = num_entries; + + r->entries = talloc_zero_array(ctx, struct samr_DispEntryAscii, num_entries); + if (!r->entries) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_entries ; i++) { + + init_lsa_AsciiStringLarge(&r->entries[i].account_name, + entries[i].account_name); + + r->entries[i].idx = start_idx+i+1; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_QueryDisplayInfo + ********************************************************************/ + +NTSTATUS _samr_QueryDisplayInfo(struct pipes_struct *p, + struct samr_QueryDisplayInfo *r) +{ + NTSTATUS status; + struct samr_info *dinfo; + uint32_t struct_size=0x20; /* W2K always reply that, client doesn't care */ + + uint32_t max_entries = r->in.max_entries; + + union samr_DispInfo *disp_info = r->out.info; + + uint32_t temp_size=0; + NTSTATUS disp_ret = NT_STATUS_UNSUCCESSFUL; + uint32_t num_account = 0; + enum remote_arch_types ra_type = get_remote_arch(); + uint32_t max_sam_entries = (ra_type == RA_WIN95) ? + MAX_SAM_ENTRIES_W95 : MAX_SAM_ENTRIES_W2K; + struct samr_displayentry *entries = NULL; + + DEBUG(5,("_samr_QueryDisplayInfo: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (sid_check_is_builtin(&dinfo->sid)) { + DEBUG(5,("_samr_QueryDisplayInfo: no users in BUILTIN\n")); + return NT_STATUS_OK; + } + + /* + * calculate how many entries we will return. + * based on + * - the number of entries the client asked + * - our limit on that + * - the starting point (enumeration context) + * - the buffer size the client will accept + */ + + /* + * We are a lot more like W2K. Instead of reading the SAM + * each time to find the records we need to send back, + * we read it once and link that copy to the sam handle. + * For large user list (over the MAX_SAM_ENTRIES) + * it's a definitive win. + * second point to notice: between enumerations + * our sam is now the same as it's a snapshoot. + * third point: got rid of the static SAM_USER_21 struct + * no more intermediate. + * con: it uses much more memory, as a full copy is stored + * in memory. + * + * If you want to change it, think twice and think + * of the second point , that's really important. + * + * JFM, 12/20/2001 + */ + + if ((r->in.level < 1) || (r->in.level > 5)) { + DEBUG(0,("_samr_QueryDisplayInfo: Unknown info level (%u)\n", + (unsigned int)r->in.level )); + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* first limit the number of entries we will return */ + if (r->in.max_entries > max_sam_entries) { + DEBUG(5, ("_samr_QueryDisplayInfo: client requested %d " + "entries, limiting to %d\n", r->in.max_entries, + max_sam_entries)); + max_entries = max_sam_entries; + } + + /* calculate the size and limit on the number of entries we will + * return */ + + temp_size=max_entries*struct_size; + + if (temp_size > r->in.buf_size) { + max_entries = MIN((r->in.buf_size / struct_size),max_entries); + DEBUG(5, ("_samr_QueryDisplayInfo: buffer size limits to " + "only %d entries\n", max_entries)); + } + + become_root(); + + /* The following done as ROOT. Don't return without unbecome_root(). */ + + switch (r->in.level) { + case 1: + case 4: + if (dinfo->disp_info->users == NULL) { + dinfo->disp_info->users = pdb_search_users( + dinfo->disp_info, ACB_NORMAL); + if (dinfo->disp_info->users == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_QueryDisplayInfo: starting user enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } else { + DEBUG(10,("_samr_QueryDisplayInfo: using cached user enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } + + num_account = pdb_search_entries(dinfo->disp_info->users, + r->in.start_idx, max_entries, + &entries); + break; + case 2: + if (dinfo->disp_info->machines == NULL) { + dinfo->disp_info->machines = pdb_search_users( + dinfo->disp_info, ACB_WSTRUST|ACB_SVRTRUST); + if (dinfo->disp_info->machines == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_QueryDisplayInfo: starting machine enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } else { + DEBUG(10,("_samr_QueryDisplayInfo: using cached machine enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } + + num_account = pdb_search_entries(dinfo->disp_info->machines, + r->in.start_idx, max_entries, + &entries); + break; + case 3: + case 5: + if (dinfo->disp_info->groups == NULL) { + dinfo->disp_info->groups = pdb_search_groups( + dinfo->disp_info); + if (dinfo->disp_info->groups == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_QueryDisplayInfo: starting group enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } else { + DEBUG(10,("_samr_QueryDisplayInfo: using cached group enumeration at index %u\n", + (unsigned int)r->in.start_idx)); + } + + num_account = pdb_search_entries(dinfo->disp_info->groups, + r->in.start_idx, max_entries, + &entries); + break; + default: + unbecome_root(); + smb_panic("info class changed"); + break; + } + unbecome_root(); + + + /* Now create reply structure */ + switch (r->in.level) { + case 1: + disp_ret = init_samr_dispinfo_1(p->mem_ctx, &disp_info->info1, + num_account, r->in.start_idx, + entries); + break; + case 2: + disp_ret = init_samr_dispinfo_2(p->mem_ctx, &disp_info->info2, + num_account, r->in.start_idx, + entries); + break; + case 3: + disp_ret = init_samr_dispinfo_3(p->mem_ctx, &disp_info->info3, + num_account, r->in.start_idx, + entries); + break; + case 4: + disp_ret = init_samr_dispinfo_4(p->mem_ctx, &disp_info->info4, + num_account, r->in.start_idx, + entries); + break; + case 5: + disp_ret = init_samr_dispinfo_5(p->mem_ctx, &disp_info->info5, + num_account, r->in.start_idx, + entries); + break; + default: + smb_panic("info class changed"); + break; + } + + if (!NT_STATUS_IS_OK(disp_ret)) + return disp_ret; + + if (max_entries <= num_account) { + status = STATUS_MORE_ENTRIES; + } else { + status = NT_STATUS_OK; + } + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT); + + DEBUG(5, ("_samr_QueryDisplayInfo: %d\n", __LINE__)); + + *r->out.total_size = num_account * struct_size; + *r->out.returned_size = num_account ? temp_size : 0; + + return status; +} + +/**************************************************************** + _samr_QueryDisplayInfo2 +****************************************************************/ + +NTSTATUS _samr_QueryDisplayInfo2(struct pipes_struct *p, + struct samr_QueryDisplayInfo2 *r) +{ + struct samr_QueryDisplayInfo q; + + q.in.domain_handle = r->in.domain_handle; + q.in.level = r->in.level; + q.in.start_idx = r->in.start_idx; + q.in.max_entries = r->in.max_entries; + q.in.buf_size = r->in.buf_size; + + q.out.total_size = r->out.total_size; + q.out.returned_size = r->out.returned_size; + q.out.info = r->out.info; + + return _samr_QueryDisplayInfo(p, &q); +} + +/**************************************************************** + _samr_QueryDisplayInfo3 +****************************************************************/ + +NTSTATUS _samr_QueryDisplayInfo3(struct pipes_struct *p, + struct samr_QueryDisplayInfo3 *r) +{ + struct samr_QueryDisplayInfo q; + + q.in.domain_handle = r->in.domain_handle; + q.in.level = r->in.level; + q.in.start_idx = r->in.start_idx; + q.in.max_entries = r->in.max_entries; + q.in.buf_size = r->in.buf_size; + + q.out.total_size = r->out.total_size; + q.out.returned_size = r->out.returned_size; + q.out.info = r->out.info; + + return _samr_QueryDisplayInfo(p, &q); +} + +/******************************************************************* + _samr_QueryAliasInfo + ********************************************************************/ + +NTSTATUS _samr_QueryAliasInfo(struct pipes_struct *p, + struct samr_QueryAliasInfo *r) +{ + struct samr_info *ainfo; + struct acct_info *info; + NTSTATUS status; + union samr_AliasInfo *alias_info = NULL; + const char *alias_name = NULL; + const char *alias_description = NULL; + + DEBUG(5,("_samr_QueryAliasInfo: %d\n", __LINE__)); + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SAMR_ALIAS_ACCESS_LOOKUP_INFO, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + alias_info = talloc_zero(p->mem_ctx, union samr_AliasInfo); + if (!alias_info) { + return NT_STATUS_NO_MEMORY; + } + + info = talloc_zero(p->mem_ctx, struct acct_info); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + status = pdb_get_aliasinfo(&ainfo->sid, info); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(info); + return status; + } + + alias_name = talloc_steal(r, info->acct_name); + alias_description = talloc_steal(r, info->acct_desc); + TALLOC_FREE(info); + + switch (r->in.level) { + case ALIASINFOALL: + alias_info->all.name.string = alias_name; + alias_info->all.num_members = 1; /* ??? */ + alias_info->all.description.string = alias_description; + break; + case ALIASINFONAME: + alias_info->name.string = alias_name; + break; + case ALIASINFODESCRIPTION: + alias_info->description.string = alias_description; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = alias_info; + + DEBUG(5,("_samr_QueryAliasInfo: %d\n", __LINE__)); + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_LookupNames + ********************************************************************/ + +NTSTATUS _samr_LookupNames(struct pipes_struct *p, + struct samr_LookupNames *r) +{ + struct samr_info *dinfo; + NTSTATUS status; + uint32_t *rid; + enum lsa_SidType *type; + uint32_t i, num_rids = r->in.num_names; + struct samr_Ids rids, types; + uint32_t num_mapped = 0; + struct dom_sid_buf buf; + + DEBUG(5,("_samr_LookupNames: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + 0 /* Don't know the acc_bits yet */, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_rids > MAX_SAM_ENTRIES) { + num_rids = MAX_SAM_ENTRIES; + DEBUG(5,("_samr_LookupNames: truncating entries to %d\n", num_rids)); + } + + rid = talloc_array(p->mem_ctx, uint32_t, num_rids); + NT_STATUS_HAVE_NO_MEMORY(rid); + + type = talloc_array(p->mem_ctx, enum lsa_SidType, num_rids); + NT_STATUS_HAVE_NO_MEMORY(type); + + DEBUG(5,("_samr_LookupNames: looking name on SID %s\n", + dom_sid_str_buf(&dinfo->sid, &buf))); + + for (i = 0; i < num_rids; i++) { + + status = NT_STATUS_NONE_MAPPED; + type[i] = SID_NAME_UNKNOWN; + + rid[i] = 0xffffffff; + + if (sid_check_is_builtin(&dinfo->sid)) { + if (lookup_builtin_name(r->in.names[i].string, + &rid[i])) + { + type[i] = SID_NAME_ALIAS; + } + } else { + lookup_global_sam_name(r->in.names[i].string, 0, + &rid[i], &type[i]); + } + + if (type[i] != SID_NAME_UNKNOWN) { + num_mapped++; + } + } + + if (num_mapped == num_rids) { + status = NT_STATUS_OK; + } else if (num_mapped == 0) { + status = NT_STATUS_NONE_MAPPED; + } else { + status = STATUS_SOME_UNMAPPED; + } + + rids.count = num_rids; + rids.ids = rid; + + types.count = num_rids; + types.ids = talloc_array(p->mem_ctx, uint32_t, num_rids); + NT_STATUS_HAVE_NO_MEMORY(type); + for (i = 0; i < num_rids; i++) { + types.ids[i] = (type[i] & 0xffffffff); + } + + *r->out.rids = rids; + *r->out.types = types; + + DEBUG(5,("_samr_LookupNames: %d\n", __LINE__)); + + return status; +} + +/**************************************************************** + _samr_ChangePasswordUser. + + So old it is just not worth implementing + because it does not supply a plaintext and so we can't do password + complexity checking and cannot update other services that use a + plaintext password via passwd chat/pam password change/ldap password + sync. +****************************************************************/ + +NTSTATUS _samr_ChangePasswordUser(struct pipes_struct *p, + struct samr_ChangePasswordUser *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/******************************************************************* + _samr_ChangePasswordUser2 + ********************************************************************/ + +NTSTATUS _samr_ChangePasswordUser2(struct pipes_struct *p, + struct samr_ChangePasswordUser2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + char *user_name = NULL; + char *rhost; + const char *wks = NULL; + bool encrypted; + + DEBUG(5,("_samr_ChangePasswordUser2: %d\n", __LINE__)); + + if (!r->in.account->string) { + return NT_STATUS_INVALID_PARAMETER; + } + if (r->in.server && r->in.server->string) { + wks = r->in.server->string; + } + + DEBUG(5,("_samr_ChangePasswordUser2: user: %s wks: %s\n", user_name, wks)); + + /* + * Pass the user through the NT -> unix user mapping + * function. + */ + + (void)map_username(talloc_tos(), r->in.account->string, &user_name); + if (!user_name) { + return NT_STATUS_NO_MEMORY; + } + + rhost = tsocket_address_inet_addr_string(remote_address, + talloc_tos()); + if (rhost == NULL) { + return NT_STATUS_NO_MEMORY; + } + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + /* + * UNIX username case mangling not required, pass_oem_change + * is case insensitive. + */ + + status = pass_oem_change(user_name, + rhost, + r->in.lm_password->data, + r->in.lm_verifier->hash, + r->in.nt_password->data, + r->in.nt_verifier->hash, + NULL); + + DEBUG(5,("_samr_ChangePasswordUser2: %d\n", __LINE__)); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return NT_STATUS_WRONG_PASSWORD; + } + + return status; +} + +/**************************************************************** + _samr_OemChangePasswordUser2 +****************************************************************/ + +NTSTATUS _samr_OemChangePasswordUser2(struct pipes_struct *p, + struct samr_OemChangePasswordUser2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status; + char *user_name = NULL; + const char *wks = NULL; + char *rhost; + bool encrypted; + + DEBUG(5,("_samr_OemChangePasswordUser2: %d\n", __LINE__)); + + if (!r->in.account->string) { + return NT_STATUS_INVALID_PARAMETER; + } + if (r->in.server && r->in.server->string) { + wks = r->in.server->string; + } + + DEBUG(5,("_samr_OemChangePasswordUser2: user: %s wks: %s\n", user_name, wks)); + + /* + * Pass the user through the NT -> unix user mapping + * function. + */ + + (void)map_username(talloc_tos(), r->in.account->string, &user_name); + if (!user_name) { + return NT_STATUS_NO_MEMORY; + } + + /* + * UNIX username case mangling not required, pass_oem_change + * is case insensitive. + */ + + if (!r->in.hash || !r->in.password) { + return NT_STATUS_INVALID_PARAMETER; + } + + rhost = tsocket_address_inet_addr_string(remote_address, + talloc_tos()); + if (rhost == NULL) { + return NT_STATUS_NO_MEMORY; + } + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + status = pass_oem_change(user_name, + rhost, + r->in.password->data, + r->in.hash->hash, + 0, + 0, + NULL); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return NT_STATUS_WRONG_PASSWORD; + } + + DEBUG(5,("_samr_OemChangePasswordUser2: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _samr_ChangePasswordUser3 + ********************************************************************/ + +NTSTATUS _samr_ChangePasswordUser3(struct pipes_struct *p, + struct samr_ChangePasswordUser3 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + NTSTATUS status; + char *user_name = NULL; + const char *wks = NULL; + enum samPwdChangeReason reject_reason; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t tmp; + char *rhost; + + DEBUG(5,("_samr_ChangePasswordUser3: %d\n", __LINE__)); + + if (!r->in.account->string) { + return NT_STATUS_INVALID_PARAMETER; + } + if (r->in.server && r->in.server->string) { + wks = r->in.server->string; + } + + DEBUG(5,("_samr_ChangePasswordUser3: user: %s wks: %s\n", user_name, wks)); + + /* + * Pass the user through the NT -> unix user mapping + * function. + */ + + (void)map_username(talloc_tos(), r->in.account->string, &user_name); + if (!user_name) { + return NT_STATUS_NO_MEMORY; + } + + rhost = tsocket_address_inet_addr_string(remote_address, + talloc_tos()); + if (rhost == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * UNIX username case mangling not required, pass_oem_change + * is case insensitive. + */ + + status = pass_oem_change(user_name, + rhost, + r->in.lm_password->data, + r->in.lm_verifier->hash, + r->in.nt_password->data, + r->in.nt_verifier->hash, + &reject_reason); + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return NT_STATUS_WRONG_PASSWORD; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION) || + NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_RESTRICTION)) { + + time_t u_expire, u_min_age; + uint32_t account_policy_temp; + + dominfo = talloc_zero(p->mem_ctx, struct samr_DomInfo1); + if (!dominfo) { + return NT_STATUS_NO_MEMORY; + } + + reject = talloc_zero(p->mem_ctx, + struct userPwdChangeFailureInformation); + if (!reject) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + + /* AS ROOT !!! */ + + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &tmp); + dominfo->min_password_length = tmp; + + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &tmp); + dominfo->password_history_length = tmp; + + pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, + &dominfo->password_properties); + + pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &account_policy_temp); + u_expire = account_policy_temp; + + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &account_policy_temp); + u_min_age = account_policy_temp; + + /* !AS ROOT */ + + unbecome_root(); + + unix_to_nt_time_abs((NTTIME *)&dominfo->max_password_age, u_expire); + unix_to_nt_time_abs((NTTIME *)&dominfo->min_password_age, u_min_age); + + if (lp_check_password_script(talloc_tos(), lp_sub) + && *lp_check_password_script(talloc_tos(), lp_sub)) { + dominfo->password_properties |= DOMAIN_PASSWORD_COMPLEX; + } + + reject->extendedFailureReason = reject_reason; + + *r->out.dominfo = dominfo; + *r->out.reject = reject; + } + + DEBUG(5,("_samr_ChangePasswordUser3: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* +makes a SAMR_R_LOOKUP_RIDS structure. +********************************************************************/ + +static bool make_samr_lookup_rids(TALLOC_CTX *ctx, uint32_t num_names, + const char **names, + struct lsa_String **lsa_name_array_p) +{ + struct lsa_String *lsa_name_array = NULL; + uint32_t i; + + *lsa_name_array_p = NULL; + + if (num_names != 0) { + lsa_name_array = talloc_zero_array(ctx, struct lsa_String, num_names); + if (!lsa_name_array) { + return false; + } + } + + for (i = 0; i < num_names; i++) { + DEBUG(10, ("names[%d]:%s\n", i, names[i] && *names[i] ? names[i] : "")); + init_lsa_String(&lsa_name_array[i], names[i]); + } + + *lsa_name_array_p = lsa_name_array; + + return true; +} + +/******************************************************************* + _samr_LookupRids + ********************************************************************/ + +NTSTATUS _samr_LookupRids(struct pipes_struct *p, + struct samr_LookupRids *r) +{ + struct samr_info *dinfo; + NTSTATUS status; + const char **names; + enum lsa_SidType *attrs = NULL; + uint32_t *wire_attrs = NULL; + int num_rids = (int)r->in.num_rids; + int i; + struct lsa_Strings names_array; + struct samr_Ids types_array; + struct lsa_String *lsa_names = NULL; + + DEBUG(5,("_samr_LookupRids: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + 0 /* Don't know the acc_bits yet */, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_rids > 1000) { + DEBUG(0, ("Got asked for %d rids (more than 1000) -- according " + "to samba4 idl this is not possible\n", num_rids)); + return NT_STATUS_UNSUCCESSFUL; + } + + if (num_rids) { + names = talloc_zero_array(p->mem_ctx, const char *, num_rids); + attrs = talloc_zero_array(p->mem_ctx, enum lsa_SidType, num_rids); + wire_attrs = talloc_zero_array(p->mem_ctx, uint32_t, num_rids); + + if ((names == NULL) || (attrs == NULL) || (wire_attrs==NULL)) + return NT_STATUS_NO_MEMORY; + } else { + names = NULL; + attrs = NULL; + wire_attrs = NULL; + } + + become_root(); /* lookup_sid can require root privs */ + status = pdb_lookup_rids(&dinfo->sid, num_rids, r->in.rids, + names, attrs); + unbecome_root(); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) && (num_rids == 0)) { + status = NT_STATUS_OK; + } + + if (!make_samr_lookup_rids(p->mem_ctx, num_rids, names, + &lsa_names)) { + return NT_STATUS_NO_MEMORY; + } + + /* Convert from enum lsa_SidType to uint32_t for wire format. */ + for (i = 0; i < num_rids; i++) { + wire_attrs[i] = (uint32_t)attrs[i]; + } + + names_array.count = num_rids; + names_array.names = lsa_names; + + types_array.count = num_rids; + types_array.ids = wire_attrs; + + *r->out.names = names_array; + *r->out.types = types_array; + + DEBUG(5,("_samr_LookupRids: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _samr_OpenUser +********************************************************************/ + +NTSTATUS _samr_OpenUser(struct pipes_struct *p, + struct samr_OpenUser *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct samu *sampass=NULL; + struct dom_sid sid; + struct samr_info *dinfo; + struct security_descriptor *psd = NULL; + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + uint32_t extra_access = 0; + size_t sd_size; + bool ret; + NTSTATUS nt_status; + + /* These two privileges, if != SEC_PRIV_INVALID, indicate + * privileges that the user must have to complete this + * operation in defience of the fixed ACL */ + enum sec_privilege needed_priv_1, needed_priv_2; + NTSTATUS status; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ( !(sampass = samu_new( p->mem_ctx )) ) { + return NT_STATUS_NO_MEMORY; + } + + /* append the user's RID to it */ + + if (!sid_compose(&sid, &dinfo->sid, r->in.rid)) + return NT_STATUS_NO_SUCH_USER; + + /* check if access can be granted as requested by client. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &usr_generic_mapping, &sid, SAMR_USR_RIGHTS_WRITE_PW); + se_map_generic(&des_access, &usr_generic_mapping); + + /* + * Get the sampass first as we need to check privileges + * based on what kind of user object this is. + * But don't reveal info too early if it didn't exist. + */ + + become_root(); + ret=pdb_getsampwsid(sampass, &sid); + unbecome_root(); + + needed_priv_1 = SEC_PRIV_INVALID; + needed_priv_2 = SEC_PRIV_INVALID; + /* + * We do the override access checks on *open*, not at + * SetUserInfo time. + */ + if (ret) { + uint32_t acb_info = pdb_get_acct_ctrl(sampass); + + if (acb_info & ACB_WSTRUST) { + /* + * SeMachineAccount is needed to add + * GENERIC_RIGHTS_USER_WRITE to a machine + * account. + */ + needed_priv_1 = SEC_PRIV_MACHINE_ACCOUNT; + } + if (acb_info & ACB_NORMAL) { + /* + * SeAddUsers is needed to add + * GENERIC_RIGHTS_USER_WRITE to a normal + * account. + */ + needed_priv_1 = SEC_PRIV_ADD_USERS; + } + /* + * Cheat - we have not set a specific privilege for + * server (BDC) or domain trust account, so allow + * GENERIC_RIGHTS_USER_WRITE if pipe user is in + * DOMAIN_RID_ADMINS. + */ + if (acb_info & (ACB_SVRTRUST|ACB_DOMTRUST)) { + if (lp_enable_privileges() && + nt_token_check_domain_rid( + session_info->security_token, + DOMAIN_RID_ADMINS)) { + des_access &= ~GENERIC_RIGHTS_USER_WRITE; + extra_access = GENERIC_RIGHTS_USER_WRITE; + DEBUG(4,("_samr_OpenUser: Allowing " + "GENERIC_RIGHTS_USER_WRITE for " + "rid admins\n")); + } + } + } + + TALLOC_FREE(sampass); + + nt_status = access_check_object(psd, session_info->security_token, + needed_priv_1, needed_priv_2, + GENERIC_RIGHTS_USER_WRITE, des_access, + &acc_granted, "_samr_OpenUser"); + + if ( !NT_STATUS_IS_OK(nt_status) ) + return nt_status; + + /* check that the SID exists in our domain. */ + if (ret == False) { + return NT_STATUS_NO_SUCH_USER; + } + + /* If we did the rid admins hack above, allow access. */ + acc_granted |= extra_access; + + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_USER, + acc_granted, + &sid, + NULL, + r->out.user_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/************************************************************************* + *************************************************************************/ + +static NTSTATUS init_samr_parameters_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + struct lsa_BinaryString **_r) +{ + struct lsa_BinaryString *r; + + if (!blob || !_r) { + return NT_STATUS_INVALID_PARAMETER; + } + + r = talloc_zero(mem_ctx, struct lsa_BinaryString); + if (!r) { + return NT_STATUS_NO_MEMORY; + } + + r->array = talloc_zero_array(mem_ctx, uint16_t, blob->length/2); + if (!r->array) { + return NT_STATUS_NO_MEMORY; + } + memcpy(r->array, blob->data, blob->length); + r->size = blob->length; + r->length = blob->length; + + if (!r->array) { + return NT_STATUS_NO_MEMORY; + } + + *_r = r; + + return NT_STATUS_OK; +} + +/************************************************************************* + *************************************************************************/ + +static struct samr_LogonHours get_logon_hours_from_pdb(TALLOC_CTX *mem_ctx, + struct samu *pw) +{ + struct samr_LogonHours hours; + const int units_per_week = 168; + + ZERO_STRUCT(hours); + hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week); + if (!hours.bits) { + return hours; + } + + hours.units_per_week = units_per_week; + memset(hours.bits, 0xFF, units_per_week); + + if (pdb_get_hours(pw)) { + memcpy(hours.bits, pdb_get_hours(pw), + MIN(pdb_get_hours_len(pw), units_per_week)); + } + + return hours; +} + +/************************************************************************* + get_user_info_1. + *************************************************************************/ + +static NTSTATUS get_user_info_1(TALLOC_CTX *mem_ctx, + struct samr_UserInfo1 *r, + struct samu *pw, + struct dom_sid *domain_sid) +{ + const struct dom_sid *sid_group; + uint32_t primary_gid; + + become_root(); + sid_group = pdb_get_group_sid(pw); + unbecome_root(); + + if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) { + struct dom_sid_buf buf1, buf2; + + DEBUG(0, ("get_user_info_1: User %s has Primary Group SID %s, \n" + "which conflicts with the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_group, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw)); + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + r->primary_gid = primary_gid; + r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw)); + r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_2. + *************************************************************************/ + +static NTSTATUS get_user_info_2(TALLOC_CTX *mem_ctx, + struct samr_UserInfo2 *r, + struct samu *pw) +{ + r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw)); + r->reserved.string = NULL; + r->country_code = pdb_get_country_code(pw); + r->code_page = pdb_get_code_page(pw); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_3. + *************************************************************************/ + +static NTSTATUS get_user_info_3(TALLOC_CTX *mem_ctx, + struct samr_UserInfo3 *r, + struct samu *pw, + struct dom_sid *domain_sid) +{ + const struct dom_sid *sid_user, *sid_group; + uint32_t rid, primary_gid; + struct dom_sid_buf buf1, buf2; + + sid_user = pdb_get_user_sid(pw); + + if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) { + DEBUG(0, ("get_user_info_3: User %s has SID %s, \nwhich conflicts with " + "the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_user, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + become_root(); + sid_group = pdb_get_group_sid(pw); + unbecome_root(); + + if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) { + DEBUG(0, ("get_user_info_3: User %s has Primary Group SID %s, \n" + "which conflicts with the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_group, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw)); + unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw)); + unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw)); + unix_to_nt_time(&r->allow_password_change, pdb_get_pass_can_change_time(pw)); + unix_to_nt_time(&r->force_password_change, pdb_get_pass_must_change_time(pw)); + + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw)); + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw)); + r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw)); + r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw)); + r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw)); + r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw)); + + r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw); + r->rid = rid; + r->primary_gid = primary_gid; + r->acct_flags = pdb_get_acct_ctrl(pw); + r->bad_password_count = pdb_get_bad_password_count(pw); + r->logon_count = pdb_get_logon_count(pw); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_4. + *************************************************************************/ + +static NTSTATUS get_user_info_4(TALLOC_CTX *mem_ctx, + struct samr_UserInfo4 *r, + struct samu *pw) +{ + r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_5. + *************************************************************************/ + +static NTSTATUS get_user_info_5(TALLOC_CTX *mem_ctx, + struct samr_UserInfo5 *r, + struct samu *pw, + struct dom_sid *domain_sid) +{ + const struct dom_sid *sid_user, *sid_group; + uint32_t rid, primary_gid; + struct dom_sid_buf buf1, buf2; + + sid_user = pdb_get_user_sid(pw); + + if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) { + DEBUG(0, ("get_user_info_5: User %s has SID %s, \nwhich conflicts with " + "the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_user, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + become_root(); + sid_group = pdb_get_group_sid(pw); + unbecome_root(); + + if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) { + DEBUG(0, ("get_user_info_5: User %s has Primary Group SID %s, \n" + "which conflicts with the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_group, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw)); + unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw)); + unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw)); + unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw)); + + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw)); + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw)); + r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw)); + r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw)); + r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw)); + r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw)); + r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw)); + + r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw); + r->rid = rid; + r->primary_gid = primary_gid; + r->acct_flags = pdb_get_acct_ctrl(pw); + r->bad_password_count = pdb_get_bad_password_count(pw); + r->logon_count = pdb_get_logon_count(pw); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_6. + *************************************************************************/ + +static NTSTATUS get_user_info_6(TALLOC_CTX *mem_ctx, + struct samr_UserInfo6 *r, + struct samu *pw) +{ + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw)); + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_7. Safe. Only gives out account_name. + *************************************************************************/ + +static NTSTATUS get_user_info_7(TALLOC_CTX *mem_ctx, + struct samr_UserInfo7 *r, + struct samu *smbpass) +{ + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(smbpass)); + if (!r->account_name.string) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_8. + *************************************************************************/ + +static NTSTATUS get_user_info_8(TALLOC_CTX *mem_ctx, + struct samr_UserInfo8 *r, + struct samu *pw) +{ + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_9. Only gives out primary group SID. + *************************************************************************/ + +static NTSTATUS get_user_info_9(TALLOC_CTX *mem_ctx, + struct samr_UserInfo9 *r, + struct samu *smbpass) +{ + r->primary_gid = pdb_get_group_rid(smbpass); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_10. + *************************************************************************/ + +static NTSTATUS get_user_info_10(TALLOC_CTX *mem_ctx, + struct samr_UserInfo10 *r, + struct samu *pw) +{ + r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw)); + r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_11. + *************************************************************************/ + +static NTSTATUS get_user_info_11(TALLOC_CTX *mem_ctx, + struct samr_UserInfo11 *r, + struct samu *pw) +{ + r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_12. + *************************************************************************/ + +static NTSTATUS get_user_info_12(TALLOC_CTX *mem_ctx, + struct samr_UserInfo12 *r, + struct samu *pw) +{ + r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_13. + *************************************************************************/ + +static NTSTATUS get_user_info_13(TALLOC_CTX *mem_ctx, + struct samr_UserInfo13 *r, + struct samu *pw) +{ + r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_14. + *************************************************************************/ + +static NTSTATUS get_user_info_14(TALLOC_CTX *mem_ctx, + struct samr_UserInfo14 *r, + struct samu *pw) +{ + r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_16. Safe. Only gives out acb bits. + *************************************************************************/ + +static NTSTATUS get_user_info_16(TALLOC_CTX *mem_ctx, + struct samr_UserInfo16 *r, + struct samu *smbpass) +{ + r->acct_flags = pdb_get_acct_ctrl(smbpass); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_17. + *************************************************************************/ + +static NTSTATUS get_user_info_17(TALLOC_CTX *mem_ctx, + struct samr_UserInfo17 *r, + struct samu *pw) +{ + unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw)); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_18. OK - this is the killer as it gives out password info. + Ensure that this is only allowed on an encrypted connection with a root + user. JRA. + *************************************************************************/ + +static NTSTATUS get_user_info_18(struct pipes_struct *p, + TALLOC_CTX *mem_ctx, + struct samr_UserInfo18 *r, + struct dom_sid *user_sid) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct samu *smbpass=NULL; + bool ret; + const uint8_t *nt_pass = NULL; + const uint8_t *lm_pass = NULL; + + ZERO_STRUCTP(r); + + if (p->transport != NCALRPC) { + return NT_STATUS_INVALID_INFO_CLASS; + } + + if (!security_token_is_system(session_info->security_token)) { + return NT_STATUS_ACCESS_DENIED; + } + + /* + * Do *NOT* do become_root()/unbecome_root() here ! JRA. + */ + + if ( !(smbpass = samu_new( mem_ctx )) ) { + return NT_STATUS_NO_MEMORY; + } + + ret = pdb_getsampwsid(smbpass, user_sid); + + if (ret == False) { + struct dom_sid_buf buf; + DEBUG(4, ("User %s not found\n", + dom_sid_str_buf(user_sid, &buf))); + TALLOC_FREE(smbpass); + return root_mode() ? NT_STATUS_NO_SUCH_USER : NT_STATUS_ACCESS_DENIED; + } + + DEBUG(3,("User:[%s] 0x%x\n", pdb_get_username(smbpass), pdb_get_acct_ctrl(smbpass) )); + + if ( pdb_get_acct_ctrl(smbpass) & ACB_DISABLED) { + TALLOC_FREE(smbpass); + return NT_STATUS_ACCOUNT_DISABLED; + } + + lm_pass = pdb_get_lanman_passwd(smbpass); + if (lm_pass != NULL) { + memcpy(r->lm_pwd.hash, lm_pass, 16); + r->lm_pwd_active = true; + } + + nt_pass = pdb_get_nt_passwd(smbpass); + if (nt_pass != NULL) { + memcpy(r->nt_pwd.hash, nt_pass, 16); + r->nt_pwd_active = true; + } + r->password_expired = 0; /* FIXME */ + + TALLOC_FREE(smbpass); + + return NT_STATUS_OK; +} + +/************************************************************************* + get_user_info_20 + *************************************************************************/ + +static NTSTATUS get_user_info_20(TALLOC_CTX *mem_ctx, + struct samr_UserInfo20 *r, + struct samu *sampass) +{ + const char *munged_dial = NULL; + DATA_BLOB blob; + NTSTATUS status; + struct lsa_BinaryString *parameters = NULL; + + ZERO_STRUCTP(r); + + munged_dial = pdb_get_munged_dial(sampass); + + DEBUG(3,("User:[%s] has [%s] (length: %d)\n", pdb_get_username(sampass), + munged_dial, (int)strlen(munged_dial))); + + if (munged_dial) { + blob = base64_decode_data_blob(munged_dial); + } else { + blob = data_blob_string_const_null(""); + } + + status = init_samr_parameters_string(mem_ctx, &blob, ¶meters); + data_blob_free(&blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->parameters = *parameters; + + return NT_STATUS_OK; +} + + +/************************************************************************* + get_user_info_21 + *************************************************************************/ + +static NTSTATUS get_user_info_21(TALLOC_CTX *mem_ctx, + struct samr_UserInfo21 *r, + struct samu *pw, + struct dom_sid *domain_sid, + uint32_t acc_granted) +{ + NTSTATUS status; + const struct dom_sid *sid_user, *sid_group; + uint32_t rid, primary_gid; + NTTIME force_password_change; + time_t must_change_time; + struct lsa_BinaryString *parameters = NULL; + const char *munged_dial = NULL; + DATA_BLOB blob; + struct dom_sid_buf buf1, buf2; + + ZERO_STRUCTP(r); + + sid_user = pdb_get_user_sid(pw); + + if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) { + DEBUG(0, ("get_user_info_21: User %s has SID %s, \nwhich conflicts with " + "the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_user, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + become_root(); + sid_group = pdb_get_group_sid(pw); + unbecome_root(); + + if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) { + DEBUG(0, ("get_user_info_21: User %s has Primary Group SID %s, \n" + "which conflicts with the domain sid %s. Failing operation.\n", + pdb_get_username(pw), + dom_sid_str_buf(sid_group, &buf1), + dom_sid_str_buf(domain_sid, &buf2))); + return NT_STATUS_UNSUCCESSFUL; + } + + unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw)); + unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw)); + unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw)); + unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw)); + unix_to_nt_time(&r->allow_password_change, pdb_get_pass_can_change_time(pw)); + + must_change_time = pdb_get_pass_must_change_time(pw); + if (pdb_is_password_change_time_max(must_change_time)) { + unix_to_nt_time_abs(&force_password_change, must_change_time); + } else { + unix_to_nt_time(&force_password_change, must_change_time); + } + + munged_dial = pdb_get_munged_dial(pw); + if (munged_dial) { + blob = base64_decode_data_blob(munged_dial); + } else { + blob = data_blob_string_const_null(""); + } + + status = init_samr_parameters_string(mem_ctx, &blob, ¶meters); + data_blob_free(&blob); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->force_password_change = force_password_change; + + r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw)); + r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw)); + r->home_directory.string = talloc_strdup(mem_ctx, pdb_get_homedir(pw)); + r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw)); + r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw)); + r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw)); + r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw)); + r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw)); + r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw)); + + r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw); + r->parameters = *parameters; + r->rid = rid; + r->primary_gid = primary_gid; + r->acct_flags = pdb_get_acct_ctrl(pw); + r->bad_password_count = pdb_get_bad_password_count(pw); + r->logon_count = pdb_get_logon_count(pw); + r->fields_present = pdb_build_fields_present(pw); + r->password_expired = (pdb_get_pass_must_change_time(pw) == 0) ? + PASS_MUST_CHANGE_AT_NEXT_LOGON : 0; + r->country_code = pdb_get_country_code(pw); + r->code_page = pdb_get_code_page(pw); + r->lm_password_set = 0; + r->nt_password_set = 0; + +#if 0 + + /* + Look at a user on a real NT4 PDC with usrmgr, press + 'ok'. Then you will see that fields_present is set to + 0x08f827fa. Look at the user immediately after that again, + and you will see that 0x00fffff is returned. This solves + the problem that you get access denied after having looked + at the user. + -- Volker + */ + +#endif + + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_QueryUserInfo + ********************************************************************/ + +NTSTATUS _samr_QueryUserInfo(struct pipes_struct *p, + struct samr_QueryUserInfo *r) +{ + NTSTATUS status; + union samr_UserInfo *user_info = NULL; + struct samr_info *uinfo; + struct dom_sid domain_sid; + uint32_t rid; + bool ret = false; + struct samu *pwd = NULL; + uint32_t acc_required, acc_granted; + struct dom_sid_buf buf; + + switch (r->in.level) { + case 1: /* UserGeneralInformation */ + /* USER_READ_GENERAL */ + acc_required = SAMR_USER_ACCESS_GET_NAME_ETC; + break; + case 2: /* UserPreferencesInformation */ + /* USER_READ_PREFERENCES | USER_READ_GENERAL */ + acc_required = SAMR_USER_ACCESS_GET_LOCALE | + SAMR_USER_ACCESS_GET_NAME_ETC; + break; + case 3: /* UserLogonInformation */ + /* USER_READ_GENERAL | USER_READ_PREFERENCES | USER_READ_LOGON | USER_READ_ACCOUNT */ + acc_required = SAMR_USER_ACCESS_GET_NAME_ETC | + SAMR_USER_ACCESS_GET_LOCALE | + SAMR_USER_ACCESS_GET_LOGONINFO | + SAMR_USER_ACCESS_GET_ATTRIBUTES; + break; + case 4: /* UserLogonHoursInformation */ + /* USER_READ_LOGON */ + acc_required = SAMR_USER_ACCESS_GET_LOGONINFO; + break; + case 5: /* UserAccountInformation */ + /* USER_READ_GENERAL | USER_READ_PREFERENCES | USER_READ_LOGON | USER_READ_ACCOUNT */ + acc_required = SAMR_USER_ACCESS_GET_NAME_ETC | + SAMR_USER_ACCESS_GET_LOCALE | + SAMR_USER_ACCESS_GET_LOGONINFO | + SAMR_USER_ACCESS_GET_ATTRIBUTES; + break; + case 6: /* UserNameInformation */ + case 7: /* UserAccountNameInformation */ + case 8: /* UserFullNameInformation */ + case 9: /* UserPrimaryGroupInformation */ + case 13: /* UserAdminCommentInformation */ + /* USER_READ_GENERAL */ + acc_required = SAMR_USER_ACCESS_GET_NAME_ETC; + break; + case 10: /* UserHomeInformation */ + case 11: /* UserScriptInformation */ + case 12: /* UserProfileInformation */ + case 14: /* UserWorkStationsInformation */ + /* USER_READ_LOGON */ + acc_required = SAMR_USER_ACCESS_GET_LOGONINFO; + break; + case 16: /* UserControlInformation */ + case 17: /* UserExpiresInformation */ + case 20: /* UserParametersInformation */ + /* USER_READ_ACCOUNT */ + acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES; + break; + case 21: /* UserAllInformation */ + /* FIXME! - gd */ + acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES; + break; + case 18: /* UserInternal1Information */ + /* FIXME! - gd */ + acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES; + break; + case 23: /* UserInternal4Information */ + case 24: /* UserInternal4InformationNew */ + case 25: /* UserInternal4InformationNew */ + case 26: /* UserInternal5InformationNew */ + default: + return NT_STATUS_INVALID_INFO_CLASS; + break; + } + + uinfo = samr_policy_handle_find(p, + r->in.user_handle, + SAMR_HANDLE_USER, + acc_required, + &acc_granted, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_sid = uinfo->sid; + + sid_split_rid(&domain_sid, &rid); + + if (!sid_check_is_in_our_sam(&uinfo->sid)) + return NT_STATUS_OBJECT_TYPE_MISMATCH; + + DEBUG(5,("_samr_QueryUserInfo: sid:%s\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + + user_info = talloc_zero(p->mem_ctx, union samr_UserInfo); + if (!user_info) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(5,("_samr_QueryUserInfo: user info level: %d\n", r->in.level)); + + if (!(pwd = samu_new(p->mem_ctx))) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = pdb_getsampwsid(pwd, &uinfo->sid); + unbecome_root(); + + if (ret == false) { + DEBUG(4,("User %s not found\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + TALLOC_FREE(pwd); + return NT_STATUS_NO_SUCH_USER; + } + + DEBUG(3,("User:[%s]\n", pdb_get_username(pwd))); + + samr_clear_sam_passwd(pwd); + + switch (r->in.level) { + case 1: + status = get_user_info_1(p->mem_ctx, &user_info->info1, pwd, &domain_sid); + break; + case 2: + status = get_user_info_2(p->mem_ctx, &user_info->info2, pwd); + break; + case 3: + status = get_user_info_3(p->mem_ctx, &user_info->info3, pwd, &domain_sid); + break; + case 4: + status = get_user_info_4(p->mem_ctx, &user_info->info4, pwd); + break; + case 5: + status = get_user_info_5(p->mem_ctx, &user_info->info5, pwd, &domain_sid); + break; + case 6: + status = get_user_info_6(p->mem_ctx, &user_info->info6, pwd); + break; + case 7: + status = get_user_info_7(p->mem_ctx, &user_info->info7, pwd); + break; + case 8: + status = get_user_info_8(p->mem_ctx, &user_info->info8, pwd); + break; + case 9: + status = get_user_info_9(p->mem_ctx, &user_info->info9, pwd); + break; + case 10: + status = get_user_info_10(p->mem_ctx, &user_info->info10, pwd); + break; + case 11: + status = get_user_info_11(p->mem_ctx, &user_info->info11, pwd); + break; + case 12: + status = get_user_info_12(p->mem_ctx, &user_info->info12, pwd); + break; + case 13: + status = get_user_info_13(p->mem_ctx, &user_info->info13, pwd); + break; + case 14: + status = get_user_info_14(p->mem_ctx, &user_info->info14, pwd); + break; + case 16: + status = get_user_info_16(p->mem_ctx, &user_info->info16, pwd); + break; + case 17: + status = get_user_info_17(p->mem_ctx, &user_info->info17, pwd); + break; + case 18: + /* level 18 is special */ + status = get_user_info_18(p, p->mem_ctx, &user_info->info18, + &uinfo->sid); + break; + case 20: + status = get_user_info_20(p->mem_ctx, &user_info->info20, pwd); + break; + case 21: + status = get_user_info_21(p->mem_ctx, &user_info->info21, pwd, &domain_sid, acc_granted); + break; + default: + status = NT_STATUS_INVALID_INFO_CLASS; + break; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + *r->out.info = user_info; + + done: + TALLOC_FREE(pwd); + + DEBUG(5,("_samr_QueryUserInfo: %d\n", __LINE__)); + + return status; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_QueryUserInfo2(struct pipes_struct *p, + struct samr_QueryUserInfo2 *r) +{ + struct samr_QueryUserInfo u; + + u.in.user_handle = r->in.user_handle; + u.in.level = r->in.level; + u.out.info = r->out.info; + + return _samr_QueryUserInfo(p, &u); +} + +/******************************************************************* + _samr_GetGroupsForUser + ********************************************************************/ + +NTSTATUS _samr_GetGroupsForUser(struct pipes_struct *p, + struct samr_GetGroupsForUser *r) +{ + struct samr_info *uinfo; + struct samu *sam_pass=NULL; + struct dom_sid *sids; + struct samr_RidWithAttribute dom_gid; + struct samr_RidWithAttribute *gids = NULL; + uint32_t primary_group_rid; + uint32_t num_groups = 0; + gid_t *unix_gids; + uint32_t i, num_gids; + bool ret; + NTSTATUS result; + bool success = False; + struct dom_sid_buf buf; + + struct samr_RidWithAttributeArray *rids = NULL; + + /* + * from the SID in the request: + * we should send back the list of DOMAIN GROUPS + * the user is a member of + * + * and only the DOMAIN GROUPS + * no ALIASES !!! neither aliases of the domain + * nor aliases of the builtin SID + * + * JFM, 12/2/2001 + */ + + DEBUG(5,("_samr_GetGroupsForUser: %d\n", __LINE__)); + + uinfo = samr_policy_handle_find(p, + r->in.user_handle, + SAMR_HANDLE_USER, + SAMR_USER_ACCESS_GET_GROUPS, + NULL, + &result); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + rids = talloc_zero(p->mem_ctx, struct samr_RidWithAttributeArray); + if (!rids) { + return NT_STATUS_NO_MEMORY; + } + + if (!sid_check_is_in_our_sam(&uinfo->sid)) + return NT_STATUS_OBJECT_TYPE_MISMATCH; + + if ( !(sam_pass = samu_new( p->mem_ctx )) ) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = pdb_getsampwsid(sam_pass, &uinfo->sid); + unbecome_root(); + + if (!ret) { + DEBUG(10, ("pdb_getsampwsid failed for %s\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + return NT_STATUS_NO_SUCH_USER; + } + + sids = NULL; + + /* make both calls inside the root block */ + become_root(); + result = pdb_enum_group_memberships(p->mem_ctx, sam_pass, + &sids, &unix_gids, &num_groups); + if ( NT_STATUS_IS_OK(result) ) { + success = sid_peek_check_rid(get_global_sam_sid(), + pdb_get_group_sid(sam_pass), + &primary_group_rid); + } + unbecome_root(); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("pdb_enum_group_memberships failed for %s\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + return result; + } + + if ( !success ) { + DEBUG(5, ("Group sid %s for user %s not in our domain\n", + dom_sid_str_buf(pdb_get_group_sid(sam_pass), &buf), + pdb_get_username(sam_pass))); + TALLOC_FREE(sam_pass); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + gids = NULL; + num_gids = 0; + + dom_gid.attributes = SE_GROUP_DEFAULT_FLAGS; + dom_gid.rid = primary_group_rid; + ADD_TO_ARRAY(p->mem_ctx, struct samr_RidWithAttribute, dom_gid, &gids, &num_gids); + + for (i=0; i<num_groups; i++) { + + if (!sid_peek_check_rid(get_global_sam_sid(), + &(sids[i]), &dom_gid.rid)) { + DEBUG(10, ("Found sid %s not in our domain\n", + dom_sid_str_buf(&sids[i], &buf))); + continue; + } + + if (dom_gid.rid == primary_group_rid) { + /* We added the primary group directly from the + * sam_account. The other SIDs are unique from + * enum_group_memberships */ + continue; + } + + ADD_TO_ARRAY(p->mem_ctx, struct samr_RidWithAttribute, dom_gid, &gids, &num_gids); + } + + rids->count = num_gids; + rids->rids = gids; + + *r->out.rids = rids; + + DEBUG(5,("_samr_GetGroupsForUser: %d\n", __LINE__)); + + return result; +} + +/******************************************************************* + ********************************************************************/ + +static uint32_t samr_get_server_role(void) +{ + uint32_t role = ROLE_DOMAIN_PDC; + + if (lp_server_role() == ROLE_DOMAIN_BDC) { + role = ROLE_DOMAIN_BDC; + } + + return role; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_1(TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t account_policy_temp; + time_t u_expire, u_min_age; + + become_root(); + + /* AS ROOT !!! */ + + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &account_policy_temp); + r->min_password_length = account_policy_temp; + + pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &account_policy_temp); + r->password_history_length = account_policy_temp; + + pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, + &r->password_properties); + + pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &account_policy_temp); + u_expire = account_policy_temp; + + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &account_policy_temp); + u_min_age = account_policy_temp; + + /* !AS ROOT */ + + unbecome_root(); + + unix_to_nt_time_abs((NTTIME *)&r->max_password_age, u_expire); + unix_to_nt_time_abs((NTTIME *)&r->min_password_age, u_min_age); + + if (lp_check_password_script(talloc_tos(), lp_sub) && *lp_check_password_script(talloc_tos(), lp_sub)){ + r->password_properties |= DOMAIN_PASSWORD_COMPLEX; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_2(TALLOC_CTX *mem_ctx, + struct samr_DomGeneralInformation *r, + struct samr_info *dinfo) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t u_logout; + time_t seq_num; + + become_root(); + + /* AS ROOT !!! */ + + r->num_users = count_sam_users(dinfo->disp_info, ACB_NORMAL); + r->num_groups = count_sam_groups(dinfo->disp_info); + r->num_aliases = count_sam_aliases(dinfo->disp_info); + + pdb_get_account_policy(PDB_POLICY_TIME_TO_LOGOUT, &u_logout); + + unix_to_nt_time_abs(&r->force_logoff_time, u_logout); + + if (!pdb_get_seq_num(&seq_num)) { + seq_num = time(NULL); + } + + /* !AS ROOT */ + + unbecome_root(); + + r->oem_information.string = lp_server_string(r, lp_sub); + r->domain_name.string = lp_workgroup(); + r->primary.string = lp_netbios_name(); + r->sequence_num = seq_num; + r->domain_server_state = DOMAIN_SERVER_ENABLED; + r->role = (enum samr_Role) samr_get_server_role(); + r->unknown3 = 1; + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_3(TALLOC_CTX *mem_ctx, + struct samr_DomInfo3 *r) +{ + uint32_t u_logout; + + become_root(); + + /* AS ROOT !!! */ + + { + uint32_t ul; + pdb_get_account_policy(PDB_POLICY_TIME_TO_LOGOUT, &ul); + u_logout = (time_t)ul; + } + + /* !AS ROOT */ + + unbecome_root(); + + unix_to_nt_time_abs(&r->force_logoff_time, u_logout); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_4(TALLOC_CTX *mem_ctx, + struct samr_DomOEMInformation *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + r->oem_information.string = lp_server_string(r, lp_sub); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_5(TALLOC_CTX *mem_ctx, + struct samr_DomInfo5 *r) +{ + r->domain_name.string = get_global_sam_name(); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_6(TALLOC_CTX *mem_ctx, + struct samr_DomInfo6 *r) +{ + /* NT returns its own name when a PDC. win2k and later + * only the name of the PDC if itself is a BDC (samba4 + * idl) */ + r->primary.string = lp_netbios_name(); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_7(TALLOC_CTX *mem_ctx, + struct samr_DomInfo7 *r) +{ + r->role = (enum samr_Role) samr_get_server_role(); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_8(TALLOC_CTX *mem_ctx, + struct samr_DomInfo8 *r) +{ + time_t seq_num; + + become_root(); + + /* AS ROOT !!! */ + + if (!pdb_get_seq_num(&seq_num)) { + seq_num = time(NULL); + } + + /* !AS ROOT */ + + unbecome_root(); + + r->sequence_num = seq_num; + r->domain_create_time = 0; + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_9(TALLOC_CTX *mem_ctx, + struct samr_DomInfo9 *r) +{ + r->domain_server_state = DOMAIN_SERVER_ENABLED; + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_11(TALLOC_CTX *mem_ctx, + struct samr_DomGeneralInformation2 *r, + struct samr_info *dinfo) +{ + NTSTATUS status; + uint32_t account_policy_temp; + time_t u_lock_duration, u_reset_time; + + status = query_dom_info_2(mem_ctx, &r->general, dinfo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* AS ROOT !!! */ + + become_root(); + + pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &account_policy_temp); + u_lock_duration = account_policy_temp; + if (u_lock_duration != -1) { + u_lock_duration *= 60; + } + + pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &account_policy_temp); + u_reset_time = account_policy_temp * 60; + + pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_temp); + r->lockout_threshold = account_policy_temp; + + /* !AS ROOT */ + + unbecome_root(); + + unix_to_nt_time_abs(&r->lockout_duration, u_lock_duration); + unix_to_nt_time_abs(&r->lockout_window, u_reset_time); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_12(TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *r) +{ + uint32_t account_policy_temp; + time_t u_lock_duration, u_reset_time; + + become_root(); + + /* AS ROOT !!! */ + + pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &account_policy_temp); + u_lock_duration = account_policy_temp; + if (u_lock_duration != -1) { + u_lock_duration *= 60; + } + + pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &account_policy_temp); + u_reset_time = account_policy_temp * 60; + + pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_temp); + r->lockout_threshold = account_policy_temp; + + /* !AS ROOT */ + + unbecome_root(); + + unix_to_nt_time_abs(&r->lockout_duration, u_lock_duration); + unix_to_nt_time_abs(&r->lockout_window, u_reset_time); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS query_dom_info_13(TALLOC_CTX *mem_ctx, + struct samr_DomInfo13 *r) +{ + time_t seq_num; + + become_root(); + + /* AS ROOT !!! */ + + if (!pdb_get_seq_num(&seq_num)) { + seq_num = time(NULL); + } + + /* !AS ROOT */ + + unbecome_root(); + + r->sequence_num = seq_num; + r->domain_create_time = 0; + r->modified_count_at_last_promotion = 0; + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_QueryDomainInfo + ********************************************************************/ + +NTSTATUS _samr_QueryDomainInfo(struct pipes_struct *p, + struct samr_QueryDomainInfo *r) +{ + NTSTATUS status = NT_STATUS_OK; + struct samr_info *dinfo; + union samr_DomainInfo *dom_info; + + uint32_t acc_required; + + DEBUG(5,("_samr_QueryDomainInfo: %d\n", __LINE__)); + + switch (r->in.level) { + case 1: /* DomainPasswordInformation */ + case 12: /* DomainLockoutInformation */ + /* DOMAIN_READ_PASSWORD_PARAMETERS */ + acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1; + break; + case 11: /* DomainGeneralInformation2 */ + /* DOMAIN_READ_PASSWORD_PARAMETERS | + * DOMAIN_READ_OTHER_PARAMETERS */ + acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 | + SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2; + break; + case 2: /* DomainGeneralInformation */ + case 3: /* DomainLogoffInformation */ + case 4: /* DomainOemInformation */ + case 5: /* DomainReplicationInformation */ + case 6: /* DomainReplicationInformation */ + case 7: /* DomainServerRoleInformation */ + case 8: /* DomainModifiedInformation */ + case 9: /* DomainStateInformation */ + case 10: /* DomainUasInformation */ + case 13: /* DomainModifiedInformation2 */ + /* DOMAIN_READ_OTHER_PARAMETERS */ + acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + acc_required, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + dom_info = talloc_zero(p->mem_ctx, union samr_DomainInfo); + if (!dom_info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: + status = query_dom_info_1(p->mem_ctx, &dom_info->info1); + break; + case 2: + status = query_dom_info_2(p->mem_ctx, &dom_info->general, dinfo); + break; + case 3: + status = query_dom_info_3(p->mem_ctx, &dom_info->info3); + break; + case 4: + status = query_dom_info_4(p->mem_ctx, &dom_info->oem); + break; + case 5: + status = query_dom_info_5(p->mem_ctx, &dom_info->info5); + break; + case 6: + status = query_dom_info_6(p->mem_ctx, &dom_info->info6); + break; + case 7: + status = query_dom_info_7(p->mem_ctx, &dom_info->info7); + break; + case 8: + status = query_dom_info_8(p->mem_ctx, &dom_info->info8); + break; + case 9: + status = query_dom_info_9(p->mem_ctx, &dom_info->info9); + break; + case 11: + status = query_dom_info_11(p->mem_ctx, &dom_info->general2, dinfo); + break; + case 12: + status = query_dom_info_12(p->mem_ctx, &dom_info->info12); + break; + case 13: + status = query_dom_info_13(p->mem_ctx, &dom_info->info13); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.info = dom_info; + + DEBUG(5,("_samr_QueryDomainInfo: %d\n", __LINE__)); + + return status; +} + +/* W2k3 seems to use the same check for all 3 objects that can be created via + * SAMR, if you try to create for example "Dialup" as an alias it says + * "NT_STATUS_USER_EXISTS". This is racy, but we can't really lock the user + * database. */ + +static NTSTATUS can_create(TALLOC_CTX *mem_ctx, const char *new_name) +{ + enum lsa_SidType type; + bool result; + + DEBUG(10, ("Checking whether [%s] can be created\n", new_name)); + + become_root(); + /* Lookup in our local databases (LOOKUP_NAME_REMOTE not set) + * whether the name already exists */ + result = lookup_name(mem_ctx, new_name, LOOKUP_NAME_LOCAL, + NULL, NULL, NULL, &type); + unbecome_root(); + + if (!result) { + DEBUG(10, ("%s does not exist, can create it\n", new_name)); + return NT_STATUS_OK; + } + + DEBUG(5, ("trying to create %s, exists as %s\n", + new_name, sid_type_lookup(type))); + + if (type == SID_NAME_DOM_GRP) { + return NT_STATUS_GROUP_EXISTS; + } + if (type == SID_NAME_ALIAS) { + return NT_STATUS_ALIAS_EXISTS; + } + + /* Yes, the default is NT_STATUS_USER_EXISTS */ + return NT_STATUS_USER_EXISTS; +} + +/******************************************************************* + _samr_CreateUser2 + ********************************************************************/ + +NTSTATUS _samr_CreateUser2(struct pipes_struct *p, + struct samr_CreateUser2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *account = NULL; + struct dom_sid sid; + uint32_t acb_info = r->in.acct_flags; + struct samr_info *dinfo; + NTSTATUS nt_status; + uint32_t acc_granted; + struct security_descriptor *psd; + size_t sd_size; + /* check this, when giving away 'add computer to domain' privs */ + uint32_t des_access = GENERIC_RIGHTS_USER_ALL_ACCESS; + bool can_add_account = False; + + /* Which privilege is needed to override the ACL? */ + enum sec_privilege needed_priv = SEC_PRIV_INVALID; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_CREATE_USER, + NULL, + &nt_status); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (sid_check_is_builtin(&dinfo->sid)) { + DEBUG(5,("_samr_CreateUser2: Refusing user create in BUILTIN\n")); + return NT_STATUS_ACCESS_DENIED; + } + + if (!(acb_info == ACB_NORMAL || acb_info == ACB_DOMTRUST || + acb_info == ACB_WSTRUST || acb_info == ACB_SVRTRUST)) { + /* Match Win2k, and return NT_STATUS_INVALID_PARAMETER if + this parameter is not an account type */ + return NT_STATUS_INVALID_PARAMETER; + } + + account = r->in.account_name->string; + if (account == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = can_create(p->mem_ctx, account); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + /* determine which user right we need to check based on the acb_info */ + + if (root_mode()) { + can_add_account = true; + } else if (acb_info & ACB_WSTRUST) { + needed_priv = SEC_PRIV_MACHINE_ACCOUNT; + can_add_account = security_token_has_privilege( + session_info->security_token, needed_priv); + } else if (acb_info & ACB_NORMAL && + (account[strlen(account)-1] != '$')) { + /* usrmgr.exe (and net rpc trustdom add) creates a normal user + account for domain trusts and changes the ACB flags later */ + needed_priv = SEC_PRIV_ADD_USERS; + can_add_account = security_token_has_privilege( + session_info->security_token, needed_priv); + } else if (lp_enable_privileges()) { + /* implicit assumption of a BDC or domain trust account here + * (we already check the flags earlier) */ + /* only Domain Admins can add a BDC or domain trust */ + can_add_account = nt_token_check_domain_rid( + session_info->security_token, + DOMAIN_RID_ADMINS ); + } + + DEBUG(5, ("_samr_CreateUser2: %s can add this account : %s\n", + uidtoname(session_info->unix_token->uid), + can_add_account ? "True":"False" )); + + if (!can_add_account) { + return NT_STATUS_ACCESS_DENIED; + } + + /********** BEGIN Admin BLOCK **********/ + + (void)winbind_off(); + become_root(); + nt_status = pdb_create_user(p->mem_ctx, account, acb_info, + r->out.rid); + unbecome_root(); + (void)winbind_on(); + + /********** END Admin BLOCK **********/ + + /* now check for failure */ + + if ( !NT_STATUS_IS_OK(nt_status) ) + return nt_status; + + /* Get the user's SID */ + + sid_compose(&sid, get_global_sam_sid(), *r->out.rid); + + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &usr_generic_mapping, + &sid, SAMR_USR_RIGHTS_WRITE_PW); + se_map_generic(&des_access, &usr_generic_mapping); + + /* + * JRA - TESTME. We just created this user so we + * had rights to create them. Do we need to check + * any further access on this object ? Can't we + * just assume we have all the rights we need ? + */ + + nt_status = access_check_object(psd, session_info->security_token, + needed_priv, SEC_PRIV_INVALID, + GENERIC_RIGHTS_USER_WRITE, des_access, + &acc_granted, "_samr_CreateUser2"); + + if ( !NT_STATUS_IS_OK(nt_status) ) { + return nt_status; + } + + nt_status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_USER, + acc_granted, + &sid, + NULL, + r->out.user_handle); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + /* After a "set" ensure we have no cached display info. */ + force_flush_samr_cache(&sid); + + *r->out.access_granted = acc_granted; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_CreateUser(struct pipes_struct *p, + struct samr_CreateUser *r) +{ + struct samr_CreateUser2 c; + uint32_t access_granted; + + c.in.domain_handle = r->in.domain_handle; + c.in.account_name = r->in.account_name; + c.in.acct_flags = ACB_NORMAL; + c.in.access_mask = r->in.access_mask; + c.out.user_handle = r->out.user_handle; + c.out.access_granted = &access_granted; + c.out.rid = r->out.rid; + + return _samr_CreateUser2(p, &c); +} + +/******************************************************************* + _samr_Connect + ********************************************************************/ + +NTSTATUS _samr_Connect(struct pipes_struct *p, + struct samr_Connect *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + NTSTATUS status; + + /* Access check */ + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to _samr_Connect\n")); + return NT_STATUS_ACCESS_DENIED; + } + + /* don't give away the farm but this is probably ok. The SAMR_ACCESS_ENUM_DOMAINS + was observed from a win98 client trying to enumerate users (when configured + user level access control on shares) --jerry */ + + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + se_map_generic( &des_access, &sam_generic_mapping ); + + acc_granted = des_access & (SAMR_ACCESS_ENUM_DOMAINS + |SAMR_ACCESS_LOOKUP_DOMAIN); + + /* set up the SAMR connect_anon response */ + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_CONNECT, + acc_granted, + NULL, + NULL, + r->out.connect_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_Connect2 + ********************************************************************/ + +NTSTATUS _samr_Connect2(struct pipes_struct *p, + struct samr_Connect2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *psd = NULL; + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + NTSTATUS nt_status; + size_t sd_size; + const char *fn = "_samr_Connect2"; + + switch (dce_call->pkt.u.request.opnum) { + case NDR_SAMR_CONNECT2: + fn = "_samr_Connect2"; + break; + case NDR_SAMR_CONNECT3: + fn = "_samr_Connect3"; + break; + case NDR_SAMR_CONNECT4: + fn = "_samr_Connect4"; + break; + case NDR_SAMR_CONNECT5: + fn = "_samr_Connect5"; + break; + } + + DEBUG(5,("%s: %d\n", fn, __LINE__)); + + /* Access check */ + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to %s\n", fn)); + return NT_STATUS_ACCESS_DENIED; + } + + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &sam_generic_mapping, NULL, 0); + se_map_generic(&des_access, &sam_generic_mapping); + + nt_status = access_check_object(psd, session_info->security_token, + SEC_PRIV_INVALID, SEC_PRIV_INVALID, + 0, des_access, &acc_granted, fn); + + if ( !NT_STATUS_IS_OK(nt_status) ) + return nt_status; + + nt_status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_CONNECT, + acc_granted, + NULL, + NULL, + r->out.connect_handle); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + DEBUG(5,("%s: %d\n", fn, __LINE__)); + + return NT_STATUS_OK; +} + +/**************************************************************** + _samr_Connect3 +****************************************************************/ + +NTSTATUS _samr_Connect3(struct pipes_struct *p, + struct samr_Connect3 *r) +{ + struct samr_Connect2 c; + + c.in.system_name = r->in.system_name; + c.in.access_mask = r->in.access_mask; + c.out.connect_handle = r->out.connect_handle; + + return _samr_Connect2(p, &c); +} + +/******************************************************************* + _samr_Connect4 + ********************************************************************/ + +NTSTATUS _samr_Connect4(struct pipes_struct *p, + struct samr_Connect4 *r) +{ + struct samr_Connect2 c; + + c.in.system_name = r->in.system_name; + c.in.access_mask = r->in.access_mask; + c.out.connect_handle = r->out.connect_handle; + + return _samr_Connect2(p, &c); +} + +/******************************************************************* + _samr_Connect5 + ********************************************************************/ + +NTSTATUS _samr_Connect5(struct pipes_struct *p, + struct samr_Connect5 *r) +{ + NTSTATUS status; + struct samr_Connect2 c; + struct samr_ConnectInfo1 info1; + + info1.client_version = SAMR_CONNECT_AFTER_W2K; + info1.supported_features = 0; + + c.in.system_name = r->in.system_name; + c.in.access_mask = r->in.access_mask; + c.out.connect_handle = r->out.connect_handle; + + *r->out.level_out = 1; + + status = _samr_Connect2(p, &c); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.info_out->info1 = info1; + + return NT_STATUS_OK; +} + +/********************************************************************** + _samr_LookupDomain + **********************************************************************/ + +NTSTATUS _samr_LookupDomain(struct pipes_struct *p, + struct samr_LookupDomain *r) +{ + NTSTATUS status; + const char *domain_name; + struct dom_sid *sid = NULL; + struct dom_sid_buf buf; + + /* win9x user manager likes to use SAMR_ACCESS_ENUM_DOMAINS here. + Reverted that change so we will work with RAS servers again */ + + (void)samr_policy_handle_find(p, + r->in.connect_handle, + SAMR_HANDLE_CONNECT, + SAMR_ACCESS_LOOKUP_DOMAIN, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_name = r->in.domain_name->string; + if (!domain_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + sid = talloc_zero(p->mem_ctx, struct dom_sid2); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + + if (strequal(domain_name, builtin_domain_name())) { + sid_copy(sid, &global_sid_Builtin); + } else { + if (!secrets_fetch_domain_sid(domain_name, sid)) { + status = NT_STATUS_NO_SUCH_DOMAIN; + } + } + + DEBUG(2,("Returning domain sid for domain %s -> %s\n", domain_name, + dom_sid_str_buf(sid, &buf))); + + *r->out.sid = sid; + + return status; +} + +/********************************************************************** + _samr_EnumDomains + **********************************************************************/ + +NTSTATUS _samr_EnumDomains(struct pipes_struct *p, + struct samr_EnumDomains *r) +{ + NTSTATUS status; + uint32_t num_entries = 2; + struct samr_SamEntry *entry_array = NULL; + struct samr_SamArray *sam; + + (void)samr_policy_handle_find(p, + r->in.connect_handle, + SAMR_HANDLE_CONNECT, + SAMR_ACCESS_ENUM_DOMAINS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + sam = talloc_zero(p->mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + + entry_array = talloc_zero_array(p->mem_ctx, + struct samr_SamEntry, + num_entries); + if (!entry_array) { + return NT_STATUS_NO_MEMORY; + } + + entry_array[0].idx = 0; + init_lsa_String(&entry_array[0].name, get_global_sam_name()); + + entry_array[1].idx = 1; + init_lsa_String(&entry_array[1].name, "Builtin"); + + sam->count = num_entries; + sam->entries = entry_array; + + *r->out.sam = sam; + *r->out.num_entries = num_entries; + + return status; +} + +/******************************************************************* + _samr_OpenAlias + ********************************************************************/ + +NTSTATUS _samr_OpenAlias(struct pipes_struct *p, + struct samr_OpenAlias *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dom_sid sid; + uint32_t alias_rid = r->in.rid; + struct samr_info *dinfo; + struct security_descriptor *psd = NULL; + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + size_t sd_size; + NTSTATUS status; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* append the alias' RID to it */ + + if (!sid_compose(&sid, &dinfo->sid, alias_rid)) + return NT_STATUS_NO_SUCH_ALIAS; + + /*check if access can be granted as requested by client. */ + + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &ali_generic_mapping, NULL, 0); + se_map_generic(&des_access,&ali_generic_mapping); + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_ADD_USERS, SEC_PRIV_INVALID, + GENERIC_RIGHTS_ALIAS_ALL_ACCESS, + des_access, &acc_granted, "_samr_OpenAlias"); + + if ( !NT_STATUS_IS_OK(status) ) + return status; + + { + /* Check we actually have the requested alias */ + enum lsa_SidType type; + bool result; + gid_t gid; + + become_root(); + result = lookup_sid(NULL, &sid, NULL, NULL, &type); + unbecome_root(); + + if (!result || (type != SID_NAME_ALIAS)) { + return NT_STATUS_NO_SUCH_ALIAS; + } + + /* make sure there is a mapping */ + + if ( !sid_to_gid( &sid, &gid ) ) { + return NT_STATUS_NO_SUCH_ALIAS; + } + + } + + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_ALIAS, + acc_granted, + &sid, + NULL, + r->out.alias_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + set_user_info_2 + ********************************************************************/ + +static NTSTATUS set_user_info_2(TALLOC_CTX *mem_ctx, + struct samr_UserInfo2 *id2, + struct samu *pwd) +{ + if (id2 == NULL) { + DEBUG(5,("set_user_info_2: NULL id2\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id2_to_sam_passwd(pwd, id2); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_4 + ********************************************************************/ + +static NTSTATUS set_user_info_4(TALLOC_CTX *mem_ctx, + struct samr_UserInfo4 *id4, + struct samu *pwd) +{ + if (id4 == NULL) { + DEBUG(5,("set_user_info_2: NULL id4\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id4_to_sam_passwd(pwd, id4); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_6 + ********************************************************************/ + +static NTSTATUS set_user_info_6(TALLOC_CTX *mem_ctx, + struct samr_UserInfo6 *id6, + struct samu *pwd) +{ + if (id6 == NULL) { + DEBUG(5,("set_user_info_6: NULL id6\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id6_to_sam_passwd(pwd, id6); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_7 + ********************************************************************/ + +static NTSTATUS set_user_info_7(TALLOC_CTX *mem_ctx, + struct samr_UserInfo7 *id7, + struct samu *pwd) +{ + NTSTATUS rc; + + if (id7 == NULL) { + DEBUG(5, ("set_user_info_7: NULL id7\n")); + return NT_STATUS_ACCESS_DENIED; + } + + if (!id7->account_name.string) { + DEBUG(5, ("set_user_info_7: failed to get new username\n")); + return NT_STATUS_ACCESS_DENIED; + } + + /* check to see if the new username already exists. Note: we can't + reliably lock all backends, so there is potentially the + possibility that a user can be created in between this check and + the rename. The rename should fail, but may not get the + exact same failure status code. I think this is small enough + of a window for this type of operation and the results are + simply that the rename fails with a slightly different status + code (like UNSUCCESSFUL instead of ALREADY_EXISTS). */ + + rc = can_create(mem_ctx, id7->account_name.string); + + /* when there is nothing to change, we're done here */ + if (NT_STATUS_EQUAL(rc, NT_STATUS_USER_EXISTS) && + strequal(id7->account_name.string, pdb_get_username(pwd))) { + return NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(rc)) { + return rc; + } + + rc = pdb_rename_sam_account(pwd, id7->account_name.string); + + return rc; +} + +/******************************************************************* + set_user_info_8 + ********************************************************************/ + +static NTSTATUS set_user_info_8(TALLOC_CTX *mem_ctx, + struct samr_UserInfo8 *id8, + struct samu *pwd) +{ + if (id8 == NULL) { + DEBUG(5,("set_user_info_8: NULL id8\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id8_to_sam_passwd(pwd, id8); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_10 + ********************************************************************/ + +static NTSTATUS set_user_info_10(TALLOC_CTX *mem_ctx, + struct samr_UserInfo10 *id10, + struct samu *pwd) +{ + if (id10 == NULL) { + DEBUG(5,("set_user_info_8: NULL id10\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id10_to_sam_passwd(pwd, id10); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_11 + ********************************************************************/ + +static NTSTATUS set_user_info_11(TALLOC_CTX *mem_ctx, + struct samr_UserInfo11 *id11, + struct samu *pwd) +{ + if (id11 == NULL) { + DEBUG(5,("set_user_info_11: NULL id11\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id11_to_sam_passwd(pwd, id11); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_12 + ********************************************************************/ + +static NTSTATUS set_user_info_12(TALLOC_CTX *mem_ctx, + struct samr_UserInfo12 *id12, + struct samu *pwd) +{ + if (id12 == NULL) { + DEBUG(5,("set_user_info_12: NULL id12\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id12_to_sam_passwd(pwd, id12); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_13 + ********************************************************************/ + +static NTSTATUS set_user_info_13(TALLOC_CTX *mem_ctx, + struct samr_UserInfo13 *id13, + struct samu *pwd) +{ + if (id13 == NULL) { + DEBUG(5,("set_user_info_13: NULL id13\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id13_to_sam_passwd(pwd, id13); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_14 + ********************************************************************/ + +static NTSTATUS set_user_info_14(TALLOC_CTX *mem_ctx, + struct samr_UserInfo14 *id14, + struct samu *pwd) +{ + if (id14 == NULL) { + DEBUG(5,("set_user_info_14: NULL id14\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id14_to_sam_passwd(pwd, id14); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_16 + ********************************************************************/ + +static NTSTATUS set_user_info_16(TALLOC_CTX *mem_ctx, + struct samr_UserInfo16 *id16, + struct samu *pwd) +{ + if (id16 == NULL) { + DEBUG(5,("set_user_info_16: NULL id16\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id16_to_sam_passwd(pwd, id16); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_17 + ********************************************************************/ + +static NTSTATUS set_user_info_17(TALLOC_CTX *mem_ctx, + struct samr_UserInfo17 *id17, + struct samu *pwd) +{ + if (id17 == NULL) { + DEBUG(5,("set_user_info_17: NULL id17\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id17_to_sam_passwd(pwd, id17); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_18 + ********************************************************************/ + +static NTSTATUS set_user_info_18(struct samr_UserInfo18 *id18, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + struct samu *pwd) +{ + int rc; + + if (id18 == NULL) { + DEBUG(2, ("set_user_info_18: id18 is NULL\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (id18->nt_pwd_active || id18->lm_pwd_active) { + if (!session_key->length) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + } + + if (id18->nt_pwd_active) { + DATA_BLOB in = data_blob_const(id18->nt_pwd.hash, 16); + uint8_t outbuf[16] = { 0, }; + DATA_BLOB out = data_blob_const(outbuf, sizeof(outbuf)); + + rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + if (!pdb_set_nt_passwd(pwd, out.data, PDB_CHANGED)) { + return NT_STATUS_ACCESS_DENIED; + } + + pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED); + } + + if (id18->lm_pwd_active) { + DATA_BLOB in = data_blob_const(id18->lm_pwd.hash, 16); + uint8_t outbuf[16] = { 0, }; + DATA_BLOB out = data_blob_const(outbuf, sizeof(outbuf)); + + rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + if (!pdb_set_lanman_passwd(pwd, out.data, PDB_CHANGED)) { + return NT_STATUS_ACCESS_DENIED; + } + + pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED); + } + + copy_id18_to_sam_passwd(pwd, id18); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_20 + ********************************************************************/ + +static NTSTATUS set_user_info_20(TALLOC_CTX *mem_ctx, + struct samr_UserInfo20 *id20, + struct samu *pwd) +{ + if (id20 == NULL) { + DEBUG(5,("set_user_info_20: NULL id20\n")); + return NT_STATUS_ACCESS_DENIED; + } + + copy_id20_to_sam_passwd(pwd, id20); + + return pdb_update_sam_account(pwd); +} + +/******************************************************************* + set_user_info_21 + ********************************************************************/ + +static NTSTATUS set_user_info_21(struct samr_UserInfo21 *id21, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key, + struct samu *pwd) +{ + NTSTATUS status; + int rc; + + if (id21 == NULL) { + DEBUG(5, ("set_user_info_21: NULL id21\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (id21->fields_present == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (id21->fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + return NT_STATUS_ACCESS_DENIED; + } + + if (id21->fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + if (id21->nt_password_set) { + DATA_BLOB in = data_blob_const( + id21->nt_owf_password.array, 16); + uint8_t outbuf[16] = { 0, }; + DATA_BLOB out = data_blob_const( + outbuf, sizeof(outbuf)); + + if ((id21->nt_owf_password.length != 16) || + (id21->nt_owf_password.size != 16)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!session_key->length) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + pdb_set_nt_passwd(pwd, out.data, PDB_CHANGED); + pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED); + } + } + + if (id21->fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + if (id21->lm_password_set) { + DATA_BLOB in = data_blob_const( + id21->lm_owf_password.array, 16); + uint8_t outbuf[16] = { 0, }; + DATA_BLOB out = data_blob_const( + outbuf, sizeof(outbuf)); + + if ((id21->lm_owf_password.length != 16) || + (id21->lm_owf_password.size != 16)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!session_key->length) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + pdb_set_lanman_passwd(pwd, out.data, PDB_CHANGED); + pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED); + } + } + + /* we need to separately check for an account rename first */ + + if (id21->account_name.string && + (!strequal(id21->account_name.string, pdb_get_username(pwd)))) + { + + /* check to see if the new username already exists. Note: we can't + reliably lock all backends, so there is potentially the + possibility that a user can be created in between this check and + the rename. The rename should fail, but may not get the + exact same failure status code. I think this is small enough + of a window for this type of operation and the results are + simply that the rename fails with a slightly different status + code (like UNSUCCESSFUL instead of ALREADY_EXISTS). */ + + status = can_create(mem_ctx, id21->account_name.string); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pdb_rename_sam_account(pwd, id21->account_name.string); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("set_user_info_21: failed to rename account: %s\n", + nt_errstr(status))); + return status; + } + + /* set the new username so that later + functions can work on the new account */ + pdb_set_username(pwd, id21->account_name.string, PDB_SET); + } + + copy_id21_to_sam_passwd("INFO_21", pwd, id21); + + /* + * The funny part about the previous two calls is + * that pwd still has the password hashes from the + * passdb entry. These have not been updated from + * id21. I don't know if they need to be set. --jerry + */ + + if ( IS_SAM_CHANGED(pwd, PDB_GROUPSID) ) { + status = pdb_set_unix_primary_group(mem_ctx, pwd); + if ( !NT_STATUS_IS_OK(status) ) { + return status; + } + } + + /* Don't worry about writing out the user account since the + primary group SID is generated solely from the user's Unix + primary group. */ + + /* write the change out */ + if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + set_user_info_23 + ********************************************************************/ + +static NTSTATUS set_user_info_23(TALLOC_CTX *mem_ctx, + struct samr_UserInfo23 *id23, + const char *rhost, + struct samu *pwd) +{ + char *plaintext_buf = NULL; + size_t len = 0; + uint32_t acct_ctrl; + NTSTATUS status; + + if (id23 == NULL) { + DEBUG(5, ("set_user_info_23: NULL id23\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (id23->info.fields_present == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (id23->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + return NT_STATUS_ACCESS_DENIED; + } + + if ((id23->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (id23->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) { + + DEBUG(5, ("Attempting administrator password change (level 23) for user %s\n", + pdb_get_username(pwd))); + + if (!decode_pw_buffer(mem_ctx, + id23->password.data, + &plaintext_buf, + &len, + CH_UTF16)) { + return NT_STATUS_WRONG_PASSWORD; + } + + if (!pdb_set_plaintext_passwd (pwd, plaintext_buf)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + copy_id23_to_sam_passwd(pwd, id23); + + acct_ctrl = pdb_get_acct_ctrl(pwd); + + /* if it's a trust account, don't update /etc/passwd */ + if ( ( (acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST ) || + ( (acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) || + ( (acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST) ) { + DEBUG(5, ("Changing trust account. Not updating /etc/passwd\n")); + } else if (plaintext_buf) { + /* update the UNIX password */ + if (lp_unix_password_sync() ) { + struct passwd *passwd; + if (pdb_get_username(pwd) == NULL) { + DEBUG(1, ("chgpasswd: User without name???\n")); + return NT_STATUS_ACCESS_DENIED; + } + + passwd = Get_Pwnam_alloc(pwd, pdb_get_username(pwd)); + if (passwd == NULL) { + DEBUG(1, ("chgpasswd: Username does not exist in system !?!\n")); + } + + if(!chgpasswd(pdb_get_username(pwd), rhost, + passwd, "", plaintext_buf, True)) { + return NT_STATUS_ACCESS_DENIED; + } + TALLOC_FREE(passwd); + } + } + + BURN_STR(plaintext_buf); + + if (IS_SAM_CHANGED(pwd, PDB_GROUPSID) && + (!NT_STATUS_IS_OK(status = pdb_set_unix_primary_group(mem_ctx, + pwd)))) { + return status; + } + + if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + set_user_info_pw + ********************************************************************/ + +static bool set_user_info_pw(uint8_t *pass, const char *rhost, struct samu *pwd) +{ + size_t len = 0; + char *plaintext_buf = NULL; + uint32_t acct_ctrl; + + DEBUG(5, ("Attempting administrator password change for user %s\n", + pdb_get_username(pwd))); + + acct_ctrl = pdb_get_acct_ctrl(pwd); + + if (!decode_pw_buffer(talloc_tos(), + pass, + &plaintext_buf, + &len, + CH_UTF16)) { + return False; + } + + if (!pdb_set_plaintext_passwd (pwd, plaintext_buf)) { + return False; + } + + /* if it's a trust account, don't update /etc/passwd */ + if ( ( (acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST ) || + ( (acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) || + ( (acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST) ) { + DEBUG(5, ("Changing trust account or non-unix-user password, not updating /etc/passwd\n")); + } else { + /* update the UNIX password */ + if (lp_unix_password_sync()) { + struct passwd *passwd; + + if (pdb_get_username(pwd) == NULL) { + DEBUG(1, ("chgpasswd: User without name???\n")); + return False; + } + + passwd = Get_Pwnam_alloc(pwd, pdb_get_username(pwd)); + if (passwd == NULL) { + DEBUG(1, ("chgpasswd: Username does not exist in system !?!\n")); + } + + if(!chgpasswd(pdb_get_username(pwd), rhost, passwd, + "", plaintext_buf, True)) { + return False; + } + TALLOC_FREE(passwd); + } + } + + BURN_STR(plaintext_buf); + + DEBUG(5,("set_user_info_pw: pdb_update_pwd()\n")); + + return True; +} + +static bool +set_user_info_pw_aes(DATA_BLOB *pw_data, const char *rhost, struct samu *pwd) +{ + uint32_t acct_ctrl; + DATA_BLOB new_password = { + .length = 0, + }; + bool ok; + + DBG_NOTICE("Attempting administrator password change for user %s\n", + pdb_get_username(pwd)); + + acct_ctrl = pdb_get_acct_ctrl(pwd); + + ok = decode_pwd_string_from_buffer514(talloc_tos(), + pw_data->data, + CH_UTF16, + &new_password); + if (!ok) { + return false; + } + + ok = pdb_set_plaintext_passwd(pwd, (char *)new_password.data); + if (!ok) { + return false; + } + + /* if it's a trust account, don't update /etc/passwd */ + if (((acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST) || + ((acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) || + ((acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST)) { + DBG_NOTICE("Changing trust account or non-unix-user password, " + "not updating /etc/passwd\n"); + } else { + /* update the UNIX password */ + if (lp_unix_password_sync()) { + struct passwd *passwd; + const char *username; + + username = pdb_get_username(pwd); + if (username == NULL) { + DBG_WARNING("User unknown\n"); + return false; + } + + passwd = Get_Pwnam_alloc(pwd, username); + if (passwd == NULL) { + DBG_WARNING("chgpasswd: Username does not " + "exist on system !?!\n"); + } + + ok = chgpasswd(username, + rhost, + passwd, + "", + (char *)new_password.data, + true); + if (!ok) { + return false; + } + TALLOC_FREE(passwd); + } + } + TALLOC_FREE(new_password.data); + + DBG_NOTICE("pdb_update_pwd()\n"); + + return true; +} + +/******************************************************************* + set_user_info_24 + ********************************************************************/ + +static NTSTATUS set_user_info_24(TALLOC_CTX *mem_ctx, + const char *rhost, + struct samr_UserInfo24 *id24, + struct samu *pwd) +{ + NTSTATUS status; + + if (id24 == NULL) { + DEBUG(5, ("set_user_info_24: NULL id24\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!set_user_info_pw(id24->password.data, rhost, pwd)) { + return NT_STATUS_WRONG_PASSWORD; + } + + copy_id24_to_sam_passwd(pwd, id24); + + status = pdb_update_sam_account(pwd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/******************************************************************* + set_user_info_25 + ********************************************************************/ + +static NTSTATUS set_user_info_25(TALLOC_CTX *mem_ctx, + const char *rhost, + struct samr_UserInfo25 *id25, + struct samu *pwd) +{ + NTSTATUS status; + + if (id25 == NULL) { + DEBUG(5, ("set_user_info_25: NULL id25\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (id25->info.fields_present == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (id25->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + return NT_STATUS_ACCESS_DENIED; + } + + if ((id25->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (id25->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) { + + if (!set_user_info_pw(id25->password.data, rhost, pwd)) { + return NT_STATUS_WRONG_PASSWORD; + } + } + + copy_id25_to_sam_passwd(pwd, id25); + + /* write the change out */ + if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) { + return status; + } + + /* + * We need to "pdb_update_sam_account" before the unix primary group + * is set, because the idealx scripts would also change the + * sambaPrimaryGroupSid using the ldap replace method. pdb_ldap uses + * the delete explicit / add explicit, which would then fail to find + * the previous primaryGroupSid value. + */ + + if ( IS_SAM_CHANGED(pwd, PDB_GROUPSID) ) { + status = pdb_set_unix_primary_group(mem_ctx, pwd); + if ( !NT_STATUS_IS_OK(status) ) { + return status; + } + } + + return NT_STATUS_OK; +} + +/******************************************************************* + set_user_info_26 + ********************************************************************/ + +static NTSTATUS set_user_info_26(TALLOC_CTX *mem_ctx, + const char *rhost, + struct samr_UserInfo26 *id26, + struct samu *pwd) +{ + NTSTATUS status; + + if (id26 == NULL) { + DEBUG(5, ("set_user_info_26: NULL id26\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!set_user_info_pw(id26->password.data, rhost, pwd)) { + return NT_STATUS_WRONG_PASSWORD; + } + + copy_pwd_expired_to_sam_passwd(pwd, id26->password_expired); + + status = pdb_update_sam_account(pwd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS set_user_info_31(TALLOC_CTX *mem_ctx, + const char *rhost, + DATA_BLOB *pw_data, + uint8_t password_expired, + struct samu *pwd) +{ + NTSTATUS status; + bool ok; + + if (pw_data->length == 0 || pw_data->length > 514) { + return NT_STATUS_WRONG_PASSWORD; + } + + ok = set_user_info_pw_aes(pw_data, rhost, pwd); + if (!ok) { + return NT_STATUS_WRONG_PASSWORD; + } + + copy_pwd_expired_to_sam_passwd(pwd, password_expired); + + status = pdb_update_sam_account(pwd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS set_user_info_32(TALLOC_CTX *mem_ctx, + const char *rhost, + DATA_BLOB *pw_data, + struct samr_UserInfo32 *id32, + struct samu *pwd) +{ + NTSTATUS status; + bool ok; + + if (pw_data->length == 0 || pw_data->length > 514) { + return NT_STATUS_WRONG_PASSWORD; + } + + if (id32 == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (id32->info.fields_present == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (id32->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + return NT_STATUS_ACCESS_DENIED; + } + + if ((id32->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (id32->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) { + ok = set_user_info_pw_aes(pw_data, rhost, pwd); + if (!ok) { + return NT_STATUS_WRONG_PASSWORD; + } + } + + copy_id32_to_sam_passwd(pwd, id32); + + status = pdb_update_sam_account(pwd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * We need to "pdb_update_sam_account" before the unix primary group + * is set, because the idealx scripts would also change the + * sambaPrimaryGroupSid using the ldap replace method. pdb_ldap uses + * the delete explicit / add explicit, which would then fail to find + * the previous primaryGroupSid value. + */ + if (IS_SAM_CHANGED(pwd, PDB_GROUPSID)) { + status = pdb_set_unix_primary_group(mem_ctx, pwd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +/************************************************************* +**************************************************************/ + +static uint32_t samr_set_user_info_map_fields_to_access_mask(uint32_t fields) +{ + uint32_t acc_required = 0; + + /* USER_ALL_USERNAME */ + if (fields & SAMR_FIELD_ACCOUNT_NAME) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_FULLNAME */ + if (fields & SAMR_FIELD_FULL_NAME) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_PRIMARYGROUPID */ + if (fields & SAMR_FIELD_PRIMARY_GID) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_HOMEDIRECTORY */ + if (fields & SAMR_FIELD_HOME_DIRECTORY) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_HOMEDIRECTORYDRIVE */ + if (fields & SAMR_FIELD_HOME_DRIVE) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_SCRIPTPATH */ + if (fields & SAMR_FIELD_LOGON_SCRIPT) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_PROFILEPATH */ + if (fields & SAMR_FIELD_PROFILE_PATH) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_ADMINCOMMENT */ + if (fields & SAMR_FIELD_COMMENT) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_WORKSTATIONS */ + if (fields & SAMR_FIELD_WORKSTATIONS) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_LOGONHOURS */ + if (fields & SAMR_FIELD_LOGON_HOURS) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_ACCOUNTEXPIRES */ + if (fields & SAMR_FIELD_ACCT_EXPIRY) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_USERACCOUNTCONTROL */ + if (fields & SAMR_FIELD_ACCT_FLAGS) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_PARAMETERS */ + if (fields & SAMR_FIELD_PARAMETERS) + acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES; + /* USER_ALL_USERCOMMENT */ + if (fields & SAMR_FIELD_COMMENT) + acc_required |= SAMR_USER_ACCESS_SET_LOC_COM; + /* USER_ALL_COUNTRYCODE */ + if (fields & SAMR_FIELD_COUNTRY_CODE) + acc_required |= SAMR_USER_ACCESS_SET_LOC_COM; + /* USER_ALL_CODEPAGE */ + if (fields & SAMR_FIELD_CODE_PAGE) + acc_required |= SAMR_USER_ACCESS_SET_LOC_COM; + /* USER_ALL_NTPASSWORDPRESENT */ + if (fields & SAMR_FIELD_NT_PASSWORD_PRESENT) + acc_required |= SAMR_USER_ACCESS_SET_PASSWORD; + /* USER_ALL_LMPASSWORDPRESENT */ + if (fields & SAMR_FIELD_LM_PASSWORD_PRESENT) + acc_required |= SAMR_USER_ACCESS_SET_PASSWORD; + /* USER_ALL_PASSWORDEXPIRED */ + if (fields & SAMR_FIELD_EXPIRED_FLAG) + acc_required |= SAMR_USER_ACCESS_SET_PASSWORD; + + return acc_required; +} + +static NTSTATUS arc4_decrypt_data(DATA_BLOB session_key, + uint8_t *data, + size_t data_size) +{ + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t my_session_key = { + .data = session_key.data, + .size = session_key.length, + }; + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + int rc; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &my_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + data, + data_size); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + status = NT_STATUS_OK; +out: + return status; +} + +/******************************************************************* + samr_SetUserInfo + ********************************************************************/ + +NTSTATUS _samr_SetUserInfo(struct pipes_struct *p, + struct samr_SetUserInfo *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct samr_info *uinfo; + NTSTATUS status; + struct samu *pwd = NULL; + union samr_UserInfo *info = r->in.info; + uint32_t acc_required = 0; + uint32_t fields = 0; + bool ret; + char *rhost; + DATA_BLOB session_key; + struct dom_sid_buf buf; + struct loadparm_context *lp_ctx = NULL; + bool encrypted; + + lp_ctx = loadparm_init_s3(p->mem_ctx, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* This is tricky. A WinXP domain join sets + (SAMR_USER_ACCESS_SET_PASSWORD|SAMR_USER_ACCESS_SET_ATTRIBUTES|SAMR_USER_ACCESS_GET_ATTRIBUTES) + The MMC lusrmgr plugin includes these perms and more in the SamrOpenUser(). But the + standard Win32 API calls just ask for SAMR_USER_ACCESS_SET_PASSWORD in the SamrOpenUser(). + This should be enough for levels 18, 24, 25,& 26. Info level 23 can set more so + we'll use the set from the WinXP join as the basis. */ + + switch (r->in.level) { + case 2: /* UserPreferencesInformation */ + /* USER_WRITE_ACCOUNT | USER_WRITE_PREFERENCES */ + acc_required = SAMR_USER_ACCESS_SET_ATTRIBUTES | SAMR_USER_ACCESS_SET_LOC_COM; + break; + case 4: /* UserLogonHoursInformation */ + case 6: /* UserNameInformation */ + case 7: /* UserAccountNameInformation */ + case 8: /* UserFullNameInformation */ + case 9: /* UserPrimaryGroupInformation */ + case 10: /* UserHomeInformation */ + case 11: /* UserScriptInformation */ + case 12: /* UserProfileInformation */ + case 13: /* UserAdminCommentInformation */ + case 14: /* UserWorkStationsInformation */ + case 16: /* UserControlInformation */ + case 17: /* UserExpiresInformation */ + case 20: /* UserParametersInformation */ + /* USER_WRITE_ACCOUNT */ + acc_required = SAMR_USER_ACCESS_SET_ATTRIBUTES; + break; + case 18: /* UserInternal1Information */ + /* FIXME: gd, this is a guess */ + acc_required = SAMR_USER_ACCESS_SET_PASSWORD; + break; + case 21: /* UserAllInformation */ + fields = info->info21.fields_present; + acc_required = samr_set_user_info_map_fields_to_access_mask(fields); + break; + case 23: /* UserInternal4Information */ + fields = info->info23.info.fields_present; + acc_required = samr_set_user_info_map_fields_to_access_mask(fields); + break; + case 25: /* UserInternal4InformationNew */ + fields = info->info25.info.fields_present; + acc_required = samr_set_user_info_map_fields_to_access_mask(fields); + break; + case 24: /* UserInternal5Information */ + case 26: /* UserInternal5InformationNew */ + case 31: /* UserInternal5InformationNew */ + acc_required = SAMR_USER_ACCESS_SET_PASSWORD; + break; + case 32: + fields = info->info32.info.fields_present; + acc_required = + samr_set_user_info_map_fields_to_access_mask(fields); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + uinfo = samr_policy_handle_find(p, + r->in.user_handle, + SAMR_HANDLE_USER, + acc_required, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5, ("_samr_SetUserInfo: sid:%s, level:%d\n", + dom_sid_str_buf(&uinfo->sid, &buf), + r->in.level)); + + if (info == NULL) { + DEBUG(5, ("_samr_SetUserInfo: NULL info level\n")); + return NT_STATUS_INVALID_INFO_CLASS; + } + + if (!(pwd = samu_new(NULL))) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = pdb_getsampwsid(pwd, &uinfo->sid); + unbecome_root(); + + if (!ret) { + TALLOC_FREE(pwd); + return NT_STATUS_NO_SUCH_USER; + } + + rhost = tsocket_address_inet_addr_string(remote_address, + talloc_tos()); + if (rhost == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* ================ BEGIN Privilege BLOCK ================ */ + + become_root(); + + /* ok! user info levels (lots: see MSDEV help), off we go... */ + + switch (r->in.level) { + + case 2: + status = set_user_info_2(p->mem_ctx, + &info->info2, pwd); + break; + + case 4: + status = set_user_info_4(p->mem_ctx, + &info->info4, pwd); + break; + + case 6: + status = set_user_info_6(p->mem_ctx, + &info->info6, pwd); + break; + + case 7: + status = set_user_info_7(p->mem_ctx, + &info->info7, pwd); + break; + + case 8: + status = set_user_info_8(p->mem_ctx, + &info->info8, pwd); + break; + + case 10: + status = set_user_info_10(p->mem_ctx, + &info->info10, pwd); + break; + + case 11: + status = set_user_info_11(p->mem_ctx, + &info->info11, pwd); + break; + + case 12: + status = set_user_info_12(p->mem_ctx, + &info->info12, pwd); + break; + + case 13: + status = set_user_info_13(p->mem_ctx, + &info->info13, pwd); + break; + + case 14: + status = set_user_info_14(p->mem_ctx, + &info->info14, pwd); + break; + + case 16: + status = set_user_info_16(p->mem_ctx, + &info->info16, pwd); + break; + + case 17: + status = set_user_info_17(p->mem_ctx, + &info->info17, pwd); + break; + + case 18: + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + /* Used by AS/U JRA. */ + status = set_user_info_18(&info->info18, + p->mem_ctx, + &session_key, + pwd); + break; + + case 20: + status = set_user_info_20(p->mem_ctx, + &info->info20, pwd); + break; + + case 21: + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + status = set_user_info_21(&info->info21, + p->mem_ctx, + &session_key, + pwd); + break; + + case 23: + encrypted = + dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + status = NT_STATUS_ACCESS_DENIED; + break; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + /* + * This can be allowed as it requires a session key + * which we only have if we have a SMB session. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + status = arc4_decrypt_data(session_key, + info->info23.password.data, + 516); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if(!NT_STATUS_IS_OK(status)) { + break; + } + +#ifdef DEBUG_PASSWORD + dump_data(100, info->info23.password.data, 516); +#endif + + status = set_user_info_23(p->mem_ctx, + &info->info23, + rhost, + pwd); + break; + + case 24: + encrypted = + dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + status = NT_STATUS_ACCESS_DENIED; + break; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + /* + * This can be allowed as it requires a session key + * which we only have if we have a SMB session. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + status = arc4_decrypt_data(session_key, + info->info24.password.data, + 516); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if(!NT_STATUS_IS_OK(status)) { + break; + } + +#ifdef DEBUG_PASSWORD + dump_data(100, info->info24.password.data, 516); +#endif + + status = set_user_info_24(p->mem_ctx, + rhost, + &info->info24, pwd); + break; + + case 25: + encrypted = + dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + status = NT_STATUS_ACCESS_DENIED; + break; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + /* + * This can be allowed as it requires a session key + * which we only have if we have a SMB session. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + status = decode_rc4_passwd_buffer(&session_key, + &info->info25.password); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (!NT_STATUS_IS_OK(status)) { + break; + } + +#ifdef DEBUG_PASSWORD + dump_data(100, info->info25.password.data, 532); +#endif + + status = set_user_info_25(p->mem_ctx, + rhost, + &info->info25, pwd); + break; + + case 26: + encrypted = + dcerpc_is_transport_encrypted(session_info); + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + status = NT_STATUS_ACCESS_DENIED; + break; + } + + status = session_extract_session_key( + session_info, &session_key, KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + break; + } + /* + * This can be allowed as it requires a session key + * which we only have if we have a SMB session. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + status = decode_rc4_passwd_buffer(&session_key, + &info->info26.password); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (!NT_STATUS_IS_OK(status)) { + break; + } + +#ifdef DEBUG_PASSWORD + dump_data(100, info->info26.password.data, 516); +#endif + + status = set_user_info_26(p->mem_ctx, + rhost, + &info->info26, pwd); + break; + case 31: { + DATA_BLOB new_password = data_blob_null; + const DATA_BLOB ciphertext = data_blob_const( + info->info31.password.cipher, + info->info31.password.cipher_len); + DATA_BLOB iv = data_blob_const( + info->info31.password.salt, + sizeof(info->info31.password.salt)); + + status = session_extract_session_key(session_info, + &session_key, + KEY_USE_16BYTES); + if (!NT_STATUS_IS_OK(status)) { + break; + } + + status = + samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt( + p->mem_ctx, + &ciphertext, + &session_key, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + info->info31.password.auth_data, + &new_password); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("samba_gnutls_aead_aes_256_cbc_hmac_" + "sha512_decrypt " + "failed with %s\n", + nt_errstr(status)); + status = NT_STATUS_WRONG_PASSWORD; + break; + } + + status = set_user_info_31(p->mem_ctx, + rhost, + &new_password, + info->info31.password_expired, + pwd); + data_blob_clear(&new_password); + + break; + } + case 32: { + DATA_BLOB new_password = data_blob_null; + const DATA_BLOB ciphertext = data_blob_const( + info->info32.password.cipher, + info->info32.password.cipher_len); + DATA_BLOB iv = data_blob_const( + info->info32.password.salt, + sizeof(info->info32.password.salt)); + + status = session_extract_session_key(session_info, + &session_key, + KEY_USE_16BYTES); + if (!NT_STATUS_IS_OK(status)) { + break; + } + + status = + samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt( + p->mem_ctx, + &ciphertext, + &session_key, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + info->info32.password.auth_data, + &new_password); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_WRONG_PASSWORD; + break; + } + + status = set_user_info_32(p->mem_ctx, + rhost, + &new_password, + &info->info32, + pwd); + data_blob_clear_free(&new_password); + + break; + } + default: + status = NT_STATUS_INVALID_INFO_CLASS; + } + + TALLOC_FREE(pwd); + + unbecome_root(); + + /* ================ END Privilege BLOCK ================ */ + + if (NT_STATUS_IS_OK(status)) { + force_flush_samr_cache(&uinfo->sid); + } + + return status; +} + +/******************************************************************* + _samr_SetUserInfo2 + ********************************************************************/ + +NTSTATUS _samr_SetUserInfo2(struct pipes_struct *p, + struct samr_SetUserInfo2 *r) +{ + struct samr_SetUserInfo q; + + q.in.user_handle = r->in.user_handle; + q.in.level = r->in.level; + q.in.info = r->in.info; + + return _samr_SetUserInfo(p, &q); +} + +/********************************************************************* + _samr_GetAliasMembership +*********************************************************************/ + +NTSTATUS _samr_GetAliasMembership(struct pipes_struct *p, + struct samr_GetAliasMembership *r) +{ + size_t num_alias_rids; + uint32_t *alias_rids; + struct samr_info *dinfo; + size_t i; + + NTSTATUS status; + + struct dom_sid *members; + + DEBUG(5,("_samr_GetAliasMembership: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS + | SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_check_is_our_sam(&dinfo->sid) && + !sid_check_is_builtin(&dinfo->sid)) + return NT_STATUS_OBJECT_TYPE_MISMATCH; + + if (r->in.sids->num_sids) { + members = talloc_array(p->mem_ctx, struct dom_sid, r->in.sids->num_sids); + + if (members == NULL) + return NT_STATUS_NO_MEMORY; + } else { + members = NULL; + } + + for (i=0; i<r->in.sids->num_sids; i++) + sid_copy(&members[i], r->in.sids->sids[i].sid); + + alias_rids = NULL; + num_alias_rids = 0; + + become_root(); + status = pdb_enum_alias_memberships(p->mem_ctx, &dinfo->sid, members, + r->in.sids->num_sids, + &alias_rids, &num_alias_rids); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.rids->count = num_alias_rids; + r->out.rids->ids = alias_rids; + + if (r->out.rids->ids == NULL) { + /* Windows domain clients don't accept a NULL ptr here */ + r->out.rids->ids = talloc_zero(p->mem_ctx, uint32_t); + } + if (r->out.rids->ids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_GetMembersInAlias +*********************************************************************/ + +NTSTATUS _samr_GetMembersInAlias(struct pipes_struct *p, + struct samr_GetMembersInAlias *r) +{ + struct samr_info *ainfo; + NTSTATUS status; + size_t i; + size_t num_sids = 0; + struct lsa_SidPtr *sids = NULL; + struct dom_sid *pdb_sids = NULL; + struct dom_sid_buf buf; + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SAMR_ALIAS_ACCESS_GET_MEMBERS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf))); + + become_root(); + status = pdb_enum_aliasmem(&ainfo->sid, talloc_tos(), &pdb_sids, + &num_sids); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_sids) { + sids = talloc_zero_array(p->mem_ctx, struct lsa_SidPtr, num_sids); + if (sids == NULL) { + TALLOC_FREE(pdb_sids); + return NT_STATUS_NO_MEMORY; + } + } + + for (i = 0; i < num_sids; i++) { + sids[i].sid = dom_sid_dup(p->mem_ctx, &pdb_sids[i]); + if (!sids[i].sid) { + TALLOC_FREE(pdb_sids); + return NT_STATUS_NO_MEMORY; + } + } + + r->out.sids->num_sids = num_sids; + r->out.sids->sids = sids; + + TALLOC_FREE(pdb_sids); + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_QueryGroupMember +*********************************************************************/ + +NTSTATUS _samr_QueryGroupMember(struct pipes_struct *p, + struct samr_QueryGroupMember *r) +{ + struct samr_info *ginfo; + size_t i, num_members; + + uint32_t *rid=NULL; + uint32_t *attr=NULL; + + NTSTATUS status; + struct samr_RidAttrArray *rids = NULL; + struct dom_sid_buf buf; + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SAMR_GROUP_ACCESS_GET_MEMBERS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + rids = talloc_zero(p->mem_ctx, struct samr_RidAttrArray); + if (!rids) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf))); + + if (!sid_check_is_in_our_sam(&ginfo->sid)) { + DEBUG(3, ("sid %s is not in our domain\n", + dom_sid_str_buf(&ginfo->sid, &buf))); + return NT_STATUS_NO_SUCH_GROUP; + } + + DEBUG(10, ("lookup on Domain SID\n")); + + become_root(); + status = pdb_enum_group_members(p->mem_ctx, &ginfo->sid, + &rid, &num_members); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) + return status; + + if (num_members) { + attr=talloc_zero_array(p->mem_ctx, uint32_t, num_members); + if (attr == NULL) { + return NT_STATUS_NO_MEMORY; + } + } else { + attr = NULL; + } + + for (i=0; i<num_members; i++) { + attr[i] = SE_GROUP_DEFAULT_FLAGS; + } + + rids->count = num_members; + rids->attributes = attr; + rids->rids = rid; + + *r->out.rids = rids; + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_AddAliasMember +*********************************************************************/ + +NTSTATUS _samr_AddAliasMember(struct pipes_struct *p, + struct samr_AddAliasMember *r) +{ + struct samr_info *ainfo; + struct dom_sid_buf buf; + NTSTATUS status; + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SAMR_ALIAS_ACCESS_ADD_MEMBER, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf))); + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_add_aliasmem(&ainfo->sid, r->in.sid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if (NT_STATUS_IS_OK(status)) { + force_flush_samr_cache(&ainfo->sid); + } + + return status; +} + +/********************************************************************* + _samr_DeleteAliasMember +*********************************************************************/ + +NTSTATUS _samr_DeleteAliasMember(struct pipes_struct *p, + struct samr_DeleteAliasMember *r) +{ + struct samr_info *ainfo; + struct dom_sid_buf buf; + NTSTATUS status; + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SAMR_ALIAS_ACCESS_REMOVE_MEMBER, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("_samr_del_aliasmem:sid is %s\n", + dom_sid_str_buf(&ainfo->sid, &buf))); + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_del_aliasmem(&ainfo->sid, r->in.sid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if (NT_STATUS_IS_OK(status)) { + force_flush_samr_cache(&ainfo->sid); + } + + return status; +} + +/********************************************************************* + _samr_AddGroupMember +*********************************************************************/ + +NTSTATUS _samr_AddGroupMember(struct pipes_struct *p, + struct samr_AddGroupMember *r) +{ + struct samr_info *ginfo; + struct dom_sid_buf buf; + NTSTATUS status; + uint32_t group_rid; + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SAMR_GROUP_ACCESS_ADD_MEMBER, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf))); + + if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid, + &group_rid)) { + return NT_STATUS_INVALID_HANDLE; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_add_groupmem(p->mem_ctx, group_rid, r->in.rid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + force_flush_samr_cache(&ginfo->sid); + + return status; +} + +/********************************************************************* + _samr_DeleteGroupMember +*********************************************************************/ + +NTSTATUS _samr_DeleteGroupMember(struct pipes_struct *p, + struct samr_DeleteGroupMember *r) + +{ + struct samr_info *ginfo; + NTSTATUS status; + uint32_t group_rid; + + /* + * delete the group member named r->in.rid + * who is a member of the sid associated with the handle + * the rid is a user's rid as the group is a domain group. + */ + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SAMR_GROUP_ACCESS_REMOVE_MEMBER, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid, + &group_rid)) { + return NT_STATUS_INVALID_HANDLE; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_del_groupmem(p->mem_ctx, group_rid, r->in.rid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + force_flush_samr_cache(&ginfo->sid); + + return status; +} + +/********************************************************************* + _samr_DeleteUser +*********************************************************************/ + +NTSTATUS _samr_DeleteUser(struct pipes_struct *p, + struct samr_DeleteUser *r) +{ + struct samr_info *uinfo; + NTSTATUS status; + struct samu *sam_pass=NULL; + bool ret; + + DEBUG(5, ("_samr_DeleteUser: %d\n", __LINE__)); + + uinfo = samr_policy_handle_find(p, + r->in.user_handle, + SAMR_HANDLE_USER, + SEC_STD_DELETE, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_check_is_in_our_sam(&uinfo->sid)) + return NT_STATUS_CANNOT_DELETE; + + /* check if the user exists before trying to delete */ + if ( !(sam_pass = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = pdb_getsampwsid(sam_pass, &uinfo->sid); + unbecome_root(); + + if(!ret) { + struct dom_sid_buf buf; + DEBUG(5,("_samr_DeleteUser: User %s doesn't exist.\n", + dom_sid_str_buf(&uinfo->sid, &buf))); + TALLOC_FREE(sam_pass); + return NT_STATUS_NO_SUCH_USER; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_delete_user(p->mem_ctx, sam_pass); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if ( !NT_STATUS_IS_OK(status) ) { + DEBUG(5,("_samr_DeleteUser: Failed to delete entry for " + "user %s: %s.\n", pdb_get_username(sam_pass), + nt_errstr(status))); + TALLOC_FREE(sam_pass); + return status; + } + + + TALLOC_FREE(sam_pass); + + force_flush_samr_cache(&uinfo->sid); + + if (!close_policy_hnd(p, r->in.user_handle)) + return NT_STATUS_OBJECT_NAME_INVALID; + + ZERO_STRUCTP(r->out.user_handle); + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_DeleteDomainGroup +*********************************************************************/ + +NTSTATUS _samr_DeleteDomainGroup(struct pipes_struct *p, + struct samr_DeleteDomainGroup *r) +{ + struct samr_info *ginfo; + struct dom_sid_buf buf; + NTSTATUS status; + uint32_t group_rid; + + DEBUG(5, ("samr_DeleteDomainGroup: %d\n", __LINE__)); + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SEC_STD_DELETE, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf))); + + if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid, + &group_rid)) { + return NT_STATUS_NO_SUCH_GROUP; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_delete_dom_group(p->mem_ctx, group_rid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if ( !NT_STATUS_IS_OK(status) ) { + DEBUG(5,("_samr_DeleteDomainGroup: Failed to delete mapping " + "entry for group %s: %s\n", + dom_sid_str_buf(&ginfo->sid, &buf), + nt_errstr(status))); + return status; + } + + force_flush_samr_cache(&ginfo->sid); + + if (!close_policy_hnd(p, r->in.group_handle)) + return NT_STATUS_OBJECT_NAME_INVALID; + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_DeleteDomAlias +*********************************************************************/ + +NTSTATUS _samr_DeleteDomAlias(struct pipes_struct *p, + struct samr_DeleteDomAlias *r) +{ + struct samr_info *ainfo; + struct dom_sid_buf buf; + NTSTATUS status; + + DEBUG(5, ("_samr_DeleteDomAlias: %d\n", __LINE__)); + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SEC_STD_DELETE, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf))); + + /* Don't let Windows delete builtin groups */ + + if ( sid_check_is_in_builtin( &ainfo->sid ) ) { + return NT_STATUS_SPECIAL_ACCOUNT; + } + + if (!sid_check_is_in_our_sam(&ainfo->sid)) + return NT_STATUS_NO_SUCH_ALIAS; + + DEBUG(10, ("lookup on Local SID\n")); + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + /* Have passdb delete the alias */ + status = pdb_delete_alias(&ainfo->sid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if ( !NT_STATUS_IS_OK(status)) + return status; + + force_flush_samr_cache(&ainfo->sid); + + if (!close_policy_hnd(p, r->in.alias_handle)) + return NT_STATUS_OBJECT_NAME_INVALID; + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_CreateDomainGroup +*********************************************************************/ + +NTSTATUS _samr_CreateDomainGroup(struct pipes_struct *p, + struct samr_CreateDomainGroup *r) + +{ + NTSTATUS status; + const char *name; + struct samr_info *dinfo; + struct dom_sid sid; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_CREATE_GROUP, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_check_is_our_sam(&dinfo->sid)) { + return NT_STATUS_ACCESS_DENIED; + } + + name = r->in.name->string; + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = can_create(p->mem_ctx, name); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + /* check that we successfully create the UNIX group */ + status = pdb_create_dom_group(p->mem_ctx, name, r->out.rid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + /* check if we should bail out here */ + + if ( !NT_STATUS_IS_OK(status) ) + return status; + + sid_compose(&sid, &dinfo->sid, *r->out.rid); + + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_GROUP, + GENERIC_RIGHTS_GROUP_ALL_ACCESS, + &sid, + NULL, + r->out.group_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + force_flush_samr_cache(&dinfo->sid); + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_CreateDomAlias +*********************************************************************/ + +NTSTATUS _samr_CreateDomAlias(struct pipes_struct *p, + struct samr_CreateDomAlias *r) +{ + struct dom_sid info_sid; + const char *name = NULL; + struct samr_info *dinfo; + gid_t gid; + NTSTATUS result; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_CREATE_ALIAS, + NULL, + &result); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + if (!sid_check_is_our_sam(&dinfo->sid)) { + return NT_STATUS_ACCESS_DENIED; + } + + name = r->in.alias_name->string; + + result = can_create(p->mem_ctx, name); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + /* Have passdb create the alias */ + result = pdb_create_alias(name, r->out.rid); + unbecome_root(); + + /******** END SeAddUsers BLOCK *********/ + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("pdb_create_alias failed: %s\n", + nt_errstr(result))); + return result; + } + + sid_compose(&info_sid, &dinfo->sid, *r->out.rid); + + if (!sid_to_gid(&info_sid, &gid)) { + DEBUG(10, ("Could not find alias just created\n")); + return NT_STATUS_ACCESS_DENIED; + } + + /* check if the group has been successfully created */ + if ( getgrgid(gid) == NULL ) { + DEBUG(1, ("getgrgid(%u) of just created alias failed\n", + (unsigned int)gid)); + return NT_STATUS_ACCESS_DENIED; + } + + result = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_ALIAS, + GENERIC_RIGHTS_ALIAS_ALL_ACCESS, + &info_sid, + NULL, + r->out.alias_handle); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + force_flush_samr_cache(&info_sid); + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_QueryGroupInfo +*********************************************************************/ + +NTSTATUS _samr_QueryGroupInfo(struct pipes_struct *p, + struct samr_QueryGroupInfo *r) +{ + struct samr_info *ginfo; + NTSTATUS status; + GROUP_MAP *map; + union samr_GroupInfo *info = NULL; + bool ret; + uint32_t attributes = SE_GROUP_DEFAULT_FLAGS; + const char *group_name = NULL; + const char *group_description = NULL; + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SAMR_GROUP_ACCESS_LOOKUP_INFO, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + map = talloc_zero(p->mem_ctx, GROUP_MAP); + if (!map) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = get_domain_group_from_sid(ginfo->sid, map); + unbecome_root(); + if (!ret) + return NT_STATUS_INVALID_HANDLE; + + group_name = talloc_move(r, &map->nt_name); + group_description = talloc_move(r, &map->comment); + + TALLOC_FREE(map); + + info = talloc_zero(p->mem_ctx, union samr_GroupInfo); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: { + uint32_t *members; + size_t num_members; + + become_root(); + status = pdb_enum_group_members( + p->mem_ctx, &ginfo->sid, &members, + &num_members); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info->all.name.string = group_name; + info->all.attributes = attributes; + info->all.num_members = num_members; + info->all.description.string = group_description; + break; + } + case 2: + info->name.string = group_name; + break; + case 3: + info->attributes.attributes = attributes; + break; + case 4: + info->description.string = group_description; + break; + case 5: { + /* + uint32_t *members; + size_t num_members; + */ + + /* + become_root(); + status = pdb_enum_group_members( + p->mem_ctx, &ginfo->sid, &members, + &num_members); + unbecome_root(); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + */ + info->all2.name.string = group_name; + info->all2.attributes = attributes; + info->all2.num_members = 0; /* num_members - in w2k3 this is always 0 */ + info->all2.description.string = group_description; + + break; + } + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_SetGroupInfo +*********************************************************************/ + +NTSTATUS _samr_SetGroupInfo(struct pipes_struct *p, + struct samr_SetGroupInfo *r) +{ + struct samr_info *ginfo; + GROUP_MAP *map; + NTSTATUS status; + bool ret; + + ginfo = samr_policy_handle_find(p, + r->in.group_handle, + SAMR_HANDLE_GROUP, + SAMR_GROUP_ACCESS_SET_INFO, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + map = talloc_zero(p->mem_ctx, GROUP_MAP); + if (!map) { + return NT_STATUS_NO_MEMORY; + } + + become_root(); + ret = get_domain_group_from_sid(ginfo->sid, map); + unbecome_root(); + if (!ret) + return NT_STATUS_NO_SUCH_GROUP; + + switch (r->in.level) { + case 2: + map->nt_name = talloc_strdup(map, + r->in.info->name.string); + if (!map->nt_name) { + return NT_STATUS_NO_MEMORY; + } + break; + case 3: + break; + case 4: + map->comment = talloc_strdup(map, + r->in.info->description.string); + if (!map->comment) { + return NT_STATUS_NO_MEMORY; + } + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_update_group_mapping_entry(map); + unbecome_root(); + + /******** End SeAddUsers BLOCK *********/ + + TALLOC_FREE(map); + + if (NT_STATUS_IS_OK(status)) { + force_flush_samr_cache(&ginfo->sid); + } + + return status; +} + +/********************************************************************* + _samr_SetAliasInfo +*********************************************************************/ + +NTSTATUS _samr_SetAliasInfo(struct pipes_struct *p, + struct samr_SetAliasInfo *r) +{ + struct samr_info *ainfo; + struct acct_info *info; + NTSTATUS status; + + ainfo = samr_policy_handle_find(p, + r->in.alias_handle, + SAMR_HANDLE_ALIAS, + SAMR_ALIAS_ACCESS_SET_INFO, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info = talloc_zero(p->mem_ctx, struct acct_info); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + /* get the current group information */ + + become_root(); + status = pdb_get_aliasinfo(&ainfo->sid, info); + unbecome_root(); + + if ( !NT_STATUS_IS_OK(status)) + return status; + + switch (r->in.level) { + case ALIASINFONAME: + { + char *group_name; + + /* We currently do not support renaming groups in the + the BUILTIN domain. Refer to util_builtin.c to understand + why. The eventually needs to be fixed to be like Windows + where you can rename builtin groups, just not delete them */ + + if ( sid_check_is_in_builtin( &ainfo->sid ) ) { + return NT_STATUS_SPECIAL_ACCOUNT; + } + + /* There has to be a valid name (and it has to be different) */ + + if ( !r->in.info->name.string ) + return NT_STATUS_INVALID_PARAMETER; + + /* If the name is the same just reply "ok". Yes this + doesn't allow you to change the case of a group name. */ + + if (strequal(r->in.info->name.string, info->acct_name)) { + return NT_STATUS_OK; + } + + talloc_free(info->acct_name); + info->acct_name = talloc_strdup(info, r->in.info->name.string); + if (!info->acct_name) { + return NT_STATUS_NO_MEMORY; + } + + /* make sure the name doesn't already exist as a user + or local group */ + + group_name = talloc_asprintf(p->mem_ctx, + "%s\\%s", + lp_netbios_name(), + info->acct_name); + if (group_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = can_create( p->mem_ctx, group_name ); + talloc_free(group_name); + if ( !NT_STATUS_IS_OK( status ) ) + return status; + break; + } + case ALIASINFODESCRIPTION: + TALLOC_FREE(info->acct_desc); + if (r->in.info->description.string) { + info->acct_desc = talloc_strdup(info, + r->in.info->description.string); + } else { + info->acct_desc = talloc_strdup(info, ""); + } + if (!info->acct_desc) { + return NT_STATUS_NO_MEMORY; + } + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /******** BEGIN SeAddUsers BLOCK *********/ + + become_root(); + status = pdb_set_aliasinfo(&ainfo->sid, info); + unbecome_root(); + + /******** End SeAddUsers BLOCK *********/ + + if (NT_STATUS_IS_OK(status)) + force_flush_samr_cache(&ainfo->sid); + + return status; +} + +/**************************************************************** + _samr_GetDomPwInfo +****************************************************************/ + +NTSTATUS _samr_GetDomPwInfo(struct pipes_struct *p, + struct samr_GetDomPwInfo *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t min_password_length = 0; + uint32_t password_properties = 0; + + /* Perform access check. Since this rpc does not require a + policy handle it will not be caught by the access checks on + SAMR_CONNECT or SAMR_CONNECT_ANON. */ + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to _samr_GetDomPwInfo\n")); + return NT_STATUS_ACCESS_DENIED; + } + + become_root(); + pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, + &min_password_length); + pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, + &password_properties); + unbecome_root(); + + if (lp_check_password_script(talloc_tos(), lp_sub) && *lp_check_password_script(talloc_tos(), lp_sub)) { + password_properties |= DOMAIN_PASSWORD_COMPLEX; + } + + r->out.info->min_password_length = min_password_length; + r->out.info->password_properties = password_properties; + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_OpenGroup +*********************************************************************/ + +NTSTATUS _samr_OpenGroup(struct pipes_struct *p, + struct samr_OpenGroup *r) + +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dom_sid info_sid; + struct dom_sid_buf buf; + GROUP_MAP *map; + struct samr_info *dinfo; + struct security_descriptor *psd = NULL; + uint32_t acc_granted; + uint32_t des_access = r->in.access_mask; + size_t sd_size; + NTSTATUS status; + bool ret; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /*check if access can be granted as requested by client. */ + map_max_allowed_access(session_info->security_token, + session_info->unix_token, + &des_access); + + make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &grp_generic_mapping, NULL, 0); + se_map_generic(&des_access,&grp_generic_mapping); + + status = access_check_object(psd, session_info->security_token, + SEC_PRIV_ADD_USERS, SEC_PRIV_INVALID, GENERIC_RIGHTS_GROUP_ALL_ACCESS, + des_access, &acc_granted, "_samr_OpenGroup"); + + if ( !NT_STATUS_IS_OK(status) ) + return status; + + /* this should not be hard-coded like this */ + + if (!sid_check_is_our_sam(&dinfo->sid)) { + return NT_STATUS_ACCESS_DENIED; + } + + sid_compose(&info_sid, &dinfo->sid, r->in.rid); + + DEBUG(10, ("_samr_OpenGroup:Opening SID: %s\n", + dom_sid_str_buf(&info_sid, &buf))); + + map = talloc_zero(p->mem_ctx, GROUP_MAP); + if (!map) { + return NT_STATUS_NO_MEMORY; + } + + /* check if that group really exists */ + become_root(); + ret = get_domain_group_from_sid(info_sid, map); + unbecome_root(); + if (!ret) + return NT_STATUS_NO_SUCH_GROUP; + + TALLOC_FREE(map); + + status = create_samr_policy_handle(p->mem_ctx, + p, + SAMR_HANDLE_GROUP, + acc_granted, + &info_sid, + NULL, + r->out.group_handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/********************************************************************* + _samr_RemoveMemberFromForeignDomain +*********************************************************************/ + +NTSTATUS _samr_RemoveMemberFromForeignDomain(struct pipes_struct *p, + struct samr_RemoveMemberFromForeignDomain *r) +{ + struct samr_info *dinfo; + struct dom_sid_buf buf; + NTSTATUS result; + + DEBUG(5,("_samr_RemoveMemberFromForeignDomain: removing SID [%s]\n", + dom_sid_str_buf(r->in.sid, &buf))); + + /* Find the policy handle. Open a policy on it. */ + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT, + NULL, + &result); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + DEBUG(8, ("_samr_RemoveMemberFromForeignDomain: sid is %s\n", + dom_sid_str_buf(&dinfo->sid, &buf))); + + /* we can only delete a user from a group since we don't have + nested groups anyways. So in the latter case, just say OK */ + + /* TODO: The above comment nowadays is bogus. Since we have nested + * groups now, and aliases members are never reported out of the unix + * group membership, the "just say OK" makes this call a no-op. For + * us. This needs fixing however. */ + + /* I've only ever seen this in the wild when deleting a user from + * usrmgr.exe. domain_sid is the builtin domain, and the sid to delete + * is the user about to be deleted. I very much suspect this is the + * only application of this call. To verify this, let people report + * other cases. */ + + if (!sid_check_is_builtin(&dinfo->sid)) { + struct dom_sid_buf buf2; + DBG_WARNING("domain_sid = %s, " + "global_sam_sid() = %s\n" + "please report to " + "samba-technical@lists.samba.org!\n", + dom_sid_str_buf(&dinfo->sid, &buf), + dom_sid_str_buf(get_global_sam_sid(), &buf2)); + return NT_STATUS_OK; + } + + force_flush_samr_cache(&dinfo->sid); + + result = NT_STATUS_OK; + + return result; +} + +/******************************************************************* + _samr_QueryDomainInfo2 + ********************************************************************/ + +NTSTATUS _samr_QueryDomainInfo2(struct pipes_struct *p, + struct samr_QueryDomainInfo2 *r) +{ + struct samr_QueryDomainInfo q; + + q.in.domain_handle = r->in.domain_handle; + q.in.level = r->in.level; + + q.out.info = r->out.info; + + return _samr_QueryDomainInfo(p, &q); +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS set_dom_info_1(TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *r) +{ + time_t u_expire, u_min_age; + + u_expire = nt_time_to_unix_abs((NTTIME *)&r->max_password_age); + u_min_age = nt_time_to_unix_abs((NTTIME *)&r->min_password_age); + + pdb_set_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, + (uint32_t)r->min_password_length); + pdb_set_account_policy(PDB_POLICY_PASSWORD_HISTORY, + (uint32_t)r->password_history_length); + pdb_set_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, + (uint32_t)r->password_properties); + pdb_set_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, (int)u_expire); + pdb_set_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, (int)u_min_age); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS set_dom_info_3(TALLOC_CTX *mem_ctx, + struct samr_DomInfo3 *r) +{ + time_t u_logout; + + u_logout = nt_time_to_unix_abs((NTTIME *)&r->force_logoff_time); + + pdb_set_account_policy(PDB_POLICY_TIME_TO_LOGOUT, (int)u_logout); + + return NT_STATUS_OK; +} + +/******************************************************************* + ********************************************************************/ + +static NTSTATUS set_dom_info_12(TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *r) +{ + time_t u_lock_duration, u_reset_time; + + /* + * It is not possible to set lockout_duration < lockout_window. + * (The test is the other way around since the negative numbers + * are stored...) + * + * This constraint is documented here for the samr rpc service: + * MS-SAMR 3.1.1.6 Attribute Constraints for Originating Updates + * http://msdn.microsoft.com/en-us/library/cc245667%28PROT.10%29.aspx + * + * And here for the ldap backend: + * MS-ADTS 3.1.1.5.3.2 Constraints + * http://msdn.microsoft.com/en-us/library/cc223462(PROT.10).aspx + */ + if (r->lockout_duration > r->lockout_window) { + return NT_STATUS_INVALID_PARAMETER; + } + + u_lock_duration = nt_time_to_unix_abs((NTTIME *)&r->lockout_duration); + if (u_lock_duration != -1) { + u_lock_duration /= 60; + } + + u_reset_time = nt_time_to_unix_abs((NTTIME *)&r->lockout_window)/60; + + pdb_set_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, (int)u_lock_duration); + pdb_set_account_policy(PDB_POLICY_RESET_COUNT_TIME, (int)u_reset_time); + pdb_set_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, + (uint32_t)r->lockout_threshold); + + return NT_STATUS_OK; +} + +/******************************************************************* + _samr_SetDomainInfo + ********************************************************************/ + +NTSTATUS _samr_SetDomainInfo(struct pipes_struct *p, + struct samr_SetDomainInfo *r) +{ + NTSTATUS status; + uint32_t acc_required = 0; + + DEBUG(5,("_samr_SetDomainInfo: %d\n", __LINE__)); + + switch (r->in.level) { + case 1: /* DomainPasswordInformation */ + case 12: /* DomainLockoutInformation */ + /* DOMAIN_WRITE_PASSWORD_PARAMETERS */ + acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_1; + break; + case 3: /* DomainLogoffInformation */ + case 4: /* DomainOemInformation */ + /* DOMAIN_WRITE_OTHER_PARAMETERS */ + acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_2; + break; + case 6: /* DomainReplicationInformation */ + case 9: /* DomainStateInformation */ + case 7: /* DomainServerRoleInformation */ + /* DOMAIN_ADMINISTER_SERVER */ + acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_3; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + (void)samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + acc_required, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("_samr_SetDomainInfo: level: %d\n", r->in.level)); + + switch (r->in.level) { + case 1: + status = set_dom_info_1(p->mem_ctx, &r->in.info->info1); + break; + case 3: + status = set_dom_info_3(p->mem_ctx, &r->in.info->info3); + break; + case 4: + break; + case 6: + break; + case 7: + break; + case 9: + break; + case 12: + status = set_dom_info_12(p->mem_ctx, &r->in.info->info12); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("_samr_SetDomainInfo: %d\n", __LINE__)); + + return NT_STATUS_OK; +} + +/**************************************************************** + _samr_GetDisplayEnumerationIndex +****************************************************************/ + +NTSTATUS _samr_GetDisplayEnumerationIndex(struct pipes_struct *p, + struct samr_GetDisplayEnumerationIndex *r) +{ + struct samr_info *dinfo; + uint32_t max_entries = (uint32_t) -1; + uint32_t enum_context = 0; + uint32_t i, num_account = 0; + struct samr_displayentry *entries = NULL; + NTSTATUS status; + + DEBUG(5,("_samr_GetDisplayEnumerationIndex: %d\n", __LINE__)); + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((r->in.level < 1) || (r->in.level > 3)) { + DEBUG(0,("_samr_GetDisplayEnumerationIndex: " + "Unknown info level (%u)\n", + r->in.level)); + return NT_STATUS_INVALID_INFO_CLASS; + } + + become_root(); + + /* The following done as ROOT. Don't return without unbecome_root(). */ + + switch (r->in.level) { + case 1: + if (dinfo->disp_info->users == NULL) { + dinfo->disp_info->users = pdb_search_users( + dinfo->disp_info, ACB_NORMAL); + if (dinfo->disp_info->users == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "starting user enumeration at index %u\n", + (unsigned int)enum_context)); + } else { + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "using cached user enumeration at index %u\n", + (unsigned int)enum_context)); + } + num_account = pdb_search_entries(dinfo->disp_info->users, + enum_context, max_entries, + &entries); + break; + case 2: + if (dinfo->disp_info->machines == NULL) { + dinfo->disp_info->machines = pdb_search_users( + dinfo->disp_info, ACB_WSTRUST|ACB_SVRTRUST); + if (dinfo->disp_info->machines == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "starting machine enumeration at index %u\n", + (unsigned int)enum_context)); + } else { + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "using cached machine enumeration at index %u\n", + (unsigned int)enum_context)); + } + num_account = pdb_search_entries(dinfo->disp_info->machines, + enum_context, max_entries, + &entries); + break; + case 3: + if (dinfo->disp_info->groups == NULL) { + dinfo->disp_info->groups = pdb_search_groups( + dinfo->disp_info); + if (dinfo->disp_info->groups == NULL) { + unbecome_root(); + return NT_STATUS_ACCESS_DENIED; + } + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "starting group enumeration at index %u\n", + (unsigned int)enum_context)); + } else { + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "using cached group enumeration at index %u\n", + (unsigned int)enum_context)); + } + num_account = pdb_search_entries(dinfo->disp_info->groups, + enum_context, max_entries, + &entries); + break; + default: + unbecome_root(); + smb_panic("info class changed"); + break; + } + + unbecome_root(); + + /* Ensure we cache this enumeration. */ + set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT); + + DEBUG(10,("_samr_GetDisplayEnumerationIndex: looking for :%s\n", + r->in.name->string)); + + for (i=0; i<num_account; i++) { + if (strequal(entries[i].account_name, r->in.name->string)) { + DEBUG(10,("_samr_GetDisplayEnumerationIndex: " + "found %s at idx %d\n", + r->in.name->string, i)); + *r->out.idx = i; + return NT_STATUS_OK; + } + } + + /* assuming account_name lives at the very end */ + *r->out.idx = num_account; + + return NT_STATUS_NO_MORE_ENTRIES; +} + +/**************************************************************** + _samr_GetDisplayEnumerationIndex2 +****************************************************************/ + +NTSTATUS _samr_GetDisplayEnumerationIndex2(struct pipes_struct *p, + struct samr_GetDisplayEnumerationIndex2 *r) +{ + struct samr_GetDisplayEnumerationIndex q; + + q.in.domain_handle = r->in.domain_handle; + q.in.level = r->in.level; + q.in.name = r->in.name; + + q.out.idx = r->out.idx; + + return _samr_GetDisplayEnumerationIndex(p, &q); +} + +/**************************************************************** + _samr_RidToSid +****************************************************************/ + +NTSTATUS _samr_RidToSid(struct pipes_struct *p, + struct samr_RidToSid *r) +{ + struct samr_info *dinfo; + NTSTATUS status; + struct dom_sid sid; + + dinfo = samr_policy_handle_find(p, + r->in.domain_handle, + SAMR_HANDLE_DOMAIN, + 0, + NULL, + &status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!sid_compose(&sid, &dinfo->sid, r->in.rid)) { + return NT_STATUS_NO_MEMORY; + } + + *r->out.sid = dom_sid_dup(p->mem_ctx, &sid); + if (!*r->out.sid) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +static enum samr_ValidationStatus samr_ValidatePassword_Change(TALLOC_CTX *mem_ctx, + const struct samr_PwInfo *dom_pw_info, + const struct samr_ValidatePasswordReq2 *req, + struct samr_ValidatePasswordRepCtr *rep) +{ + NTSTATUS status; + + if (req->password.string == NULL) { + return SAMR_VALIDATION_STATUS_SUCCESS; + } + if (strlen(req->password.string) < dom_pw_info->min_password_length) { + ZERO_STRUCT(rep->info); + return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT; + } + if (dom_pw_info->password_properties & DOMAIN_PASSWORD_COMPLEX) { + status = check_password_complexity(req->account.string, + NULL, /* full_name */ + req->password.string, + NULL); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCT(rep->info); + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + } + + return SAMR_VALIDATION_STATUS_SUCCESS; +} + +/**************************************************************** +****************************************************************/ + +static enum samr_ValidationStatus samr_ValidatePassword_Reset(TALLOC_CTX *mem_ctx, + const struct samr_PwInfo *dom_pw_info, + const struct samr_ValidatePasswordReq3 *req, + struct samr_ValidatePasswordRepCtr *rep) +{ + NTSTATUS status; + + if (req->password.string == NULL) { + return SAMR_VALIDATION_STATUS_SUCCESS; + } + if (strlen(req->password.string) < dom_pw_info->min_password_length) { + ZERO_STRUCT(rep->info); + return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT; + } + if (dom_pw_info->password_properties & DOMAIN_PASSWORD_COMPLEX) { + status = check_password_complexity(req->account.string, + NULL, /* full_name */ + req->password.string, + NULL); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCT(rep->info); + return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH; + } + } + + return SAMR_VALIDATION_STATUS_SUCCESS; +} + +/**************************************************************** + _samr_ValidatePassword +****************************************************************/ + +NTSTATUS _samr_ValidatePassword(struct pipes_struct *p, + struct samr_ValidatePassword *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + union samr_ValidatePasswordRep *rep; + NTSTATUS status; + struct samr_GetDomPwInfo pw; + struct samr_PwInfo dom_pw_info; + + if (p->transport != NCACN_IP_TCP && p->transport != NCALRPC) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + dcesrv_call_auth_info(dce_call, NULL, &auth_level); + + if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) { + p->fault_state = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + if (r->in.level < 1 || r->in.level > 3) { + return NT_STATUS_INVALID_INFO_CLASS; + } + + pw.in.domain_name = NULL; + pw.out.info = &dom_pw_info; + + status = _samr_GetDomPwInfo(p, &pw); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + rep = talloc_zero(p->mem_ctx, union samr_ValidatePasswordRep); + if (!rep) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: + status = NT_STATUS_NOT_SUPPORTED; + break; + case 2: + rep->ctr2.status = samr_ValidatePassword_Change(p->mem_ctx, + &dom_pw_info, + &r->in.req->req2, + &rep->ctr2); + break; + case 3: + rep->ctr3.status = samr_ValidatePassword_Reset(p->mem_ctx, + &dom_pw_info, + &r->in.req->req3, + &rep->ctr3); + break; + default: + status = NT_STATUS_INVALID_INFO_CLASS; + break; + } + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(rep); + return status; + } + + *r->out.rep = rep; + + return NT_STATUS_OK; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_Shutdown(struct pipes_struct *p, + struct samr_Shutdown *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_SetMemberAttributesOfGroup(struct pipes_struct *p, + struct samr_SetMemberAttributesOfGroup *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_TestPrivateFunctionsDomain(struct pipes_struct *p, + struct samr_TestPrivateFunctionsDomain *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_TestPrivateFunctionsUser(struct pipes_struct *p, + struct samr_TestPrivateFunctionsUser *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_AddMultipleMembersToAlias(struct pipes_struct *p, + struct samr_AddMultipleMembersToAlias *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_RemoveMultipleMembersFromAlias(struct pipes_struct *p, + struct samr_RemoveMultipleMembersFromAlias *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_SetBootKeyInformation(struct pipes_struct *p, + struct samr_SetBootKeyInformation *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_GetBootKeyInformation(struct pipes_struct *p, + struct samr_GetBootKeyInformation *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS _samr_SetDsrmPassword(struct pipes_struct *p, + struct samr_SetDsrmPassword *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NOT_IMPLEMENTED; +} + +void _samr_Opnum68NotUsedOnWire(struct pipes_struct *p, + struct samr_Opnum68NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _samr_Opnum69NotUsedOnWire(struct pipes_struct *p, + struct samr_Opnum69NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _samr_Opnum70NotUsedOnWire(struct pipes_struct *p, + struct samr_Opnum70NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _samr_Opnum71NotUsedOnWire(struct pipes_struct *p, + struct samr_Opnum71NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +void _samr_Opnum72NotUsedOnWire(struct pipes_struct *p, + struct samr_Opnum72NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + +NTSTATUS _samr_ChangePasswordUser4(struct pipes_struct *p, + struct samr_ChangePasswordUser4 *r) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + char *rhost = NULL; + struct samu *sampass = NULL; + char *username = NULL; + uint32_t acct_ctrl = 0; + const uint8_t *nt_pw = NULL; + gnutls_datum_t nt_key; + gnutls_datum_t salt = { + .data = r->in.password->salt, + .size = sizeof(r->in.password->salt), + }; + uint8_t cdk_data[16] = {0}; + DATA_BLOB cdk = { + .data = cdk_data, + .length = sizeof(cdk_data), + }; + char *new_passwd = NULL; + bool updated_badpw = false; + NTSTATUS update_login_attempts_status; + char *mutex_name_by_user = NULL; + struct named_mutex *mtx = NULL; + NTSTATUS status = NT_STATUS_WRONG_PASSWORD; + bool ok; + int rc; + + r->out.result = NT_STATUS_WRONG_PASSWORD; + + DBG_NOTICE("_samr_ChangePasswordUser4\n"); + + if (r->in.account->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + if (r->in.password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.password->PBKDF2Iterations < 5000 || + r->in.password->PBKDF2Iterations > 1000000) { + return NT_STATUS_INVALID_PARAMETER; + } + + (void)map_username(frame, r->in.account->string, &username); + if (username == NULL) { + return NT_STATUS_NO_MEMORY; + } + + rhost = tsocket_address_inet_addr_string(remote_address, frame); + if (rhost == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + sampass = samu_new(frame); + if (sampass == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + become_root(); + ok = pdb_getsampwnam(sampass, username); + unbecome_root(); + if (!ok) { + status = NT_STATUS_NO_SUCH_USER; + goto done; + } + + acct_ctrl = pdb_get_acct_ctrl(sampass); + if (acct_ctrl & ACB_AUTOLOCK) { + status = NT_STATUS_ACCOUNT_LOCKED_OUT; + goto done; + } + + nt_pw = pdb_get_nt_passwd(sampass); + nt_key = (gnutls_datum_t){ + .data = discard_const_p(uint8_t, nt_pw), + .size = NT_HASH_LEN, + }; + + rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512, + &nt_key, + &salt, + r->in.password->PBKDF2Iterations, + cdk.data, + cdk.length); + if (rc < 0) { + BURN_DATA(cdk_data); + status = NT_STATUS_WRONG_PASSWORD; + goto done; + } + + status = samr_set_password_aes(frame, + &cdk, + r->in.password, + &new_passwd); + BURN_DATA(cdk_data); + + /* + * We must re-load the sam account information under a mutex + * lock to ensure we don't miss any concurrent account lockout + * changes. + */ + + /* Clear out old sampass info. */ + TALLOC_FREE(sampass); + + sampass = samu_new(frame); + if (sampass == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + mutex_name_by_user = talloc_asprintf(frame, + "check_sam_security_mutex_%s", + username); + if (mutex_name_by_user == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* Grab the named mutex under root with 30 second timeout. */ + become_root(); + mtx = grab_named_mutex(frame, mutex_name_by_user, 30); + if (mtx != NULL) { + /* Re-load the account information if we got the mutex. */ + ok = pdb_getsampwnam(sampass, username); + } + unbecome_root(); + + /* Everything from here on until mtx is freed is done under the mutex.*/ + + if (mtx == NULL) { + DBG_ERR("Acquisition of mutex %s failed " + "for user %s\n", + mutex_name_by_user, + username); + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + if (!ok) { + /* + * Re-load of account failed. This could only happen if the + * user was deleted in the meantime. + */ + DBG_NOTICE("reload of user '%s' in passdb failed.\n", + username); + status = NT_STATUS_NO_SUCH_USER; + goto done; + } + + /* + * Check if the account is now locked out - now under the mutex. + * This can happen if the server is under + * a password guess attack and the ACB_AUTOLOCK is set by + * another process. + */ + if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) { + DBG_NOTICE("Account for user %s was locked out.\n", username); + status = NT_STATUS_ACCOUNT_LOCKED_OUT; + goto done; + } + + /* + * Notify passdb backend of login success/failure. If not + * NT_STATUS_OK the backend doesn't like the login + */ + update_login_attempts_status = pdb_update_login_attempts( + sampass, NT_STATUS_IS_OK(status)); + + if (!NT_STATUS_IS_OK(status)) { + bool increment_bad_pw_count = false; + + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD) && + (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) && + NT_STATUS_IS_OK(update_login_attempts_status)) + { + increment_bad_pw_count = true; + } + + if (increment_bad_pw_count) { + pdb_increment_bad_password_count(sampass); + updated_badpw = true; + } else { + pdb_update_bad_password_count(sampass, + &updated_badpw); + } + } else { + if ((pdb_get_acct_ctrl(sampass) & ACB_NORMAL) && + (pdb_get_bad_password_count(sampass) > 0)) + { + pdb_set_bad_password_count(sampass, 0, PDB_CHANGED); + pdb_set_bad_password_time(sampass, 0, PDB_CHANGED); + updated_badpw = true; + } + } + + if (updated_badpw) { + NTSTATUS update_status; + become_root(); + update_status = pdb_update_sam_account(sampass); + unbecome_root(); + + if (!NT_STATUS_IS_OK(update_status)) { + DEBUG(1, ("Failed to modify entry: %s\n", + nt_errstr(update_status))); + } + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + become_root(); + status = change_oem_password(sampass, + rhost, + NULL, + new_passwd, + true, + NULL); + unbecome_root(); + TALLOC_FREE(new_passwd); + +done: + TALLOC_FREE(frame); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return NT_STATUS_WRONG_PASSWORD; + } + + return status; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_samr_scompat.c" diff --git a/source3/rpc_server/samr/srv_samr_util.c b/source3/rpc_server/samr/srv_samr_util.c new file mode 100644 index 0000000..fa35ce6 --- /dev/null +++ b/source3/rpc_server/samr/srv_samr_util.c @@ -0,0 +1,756 @@ +/* + Unix SMB/CIFS implementation. + SAMR Pipe utility functions. + + Copyright (C) Luke Kenneth Casson Leighton 1996-1998 + Copyright (C) Gerald (Jerry) Carter 2000-2001 + Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Stefan (metze) Metzmacher 2002 + Copyright (C) Guenther Deschner 2008 + + 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 "../librpc/gen_ndr/samr.h" +#include "rpc_server/samr/srv_samr_util.h" +#include "passdb.h" +#include "lib/util/base64.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define STRING_CHANGED (old_string && !new_string) ||\ + (!old_string && new_string) ||\ + (old_string && new_string && (strcmp(old_string, new_string) != 0)) + +#define STRING_CHANGED_NC(s1,s2) ((s1) && !(s2)) ||\ + (!(s1) && (s2)) ||\ + ((s1) && (s2) && (strcmp((s1), (s2)) != 0)) + +/************************************************************* + Copies a struct samr_UserInfo2 to a struct samu +**************************************************************/ + +void copy_id2_to_sam_passwd(struct samu *to, + struct samr_UserInfo2 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_COMMENT | + SAMR_FIELD_COUNTRY_CODE | + SAMR_FIELD_CODE_PAGE; + i.comment = from->comment; + i.country_code = from->country_code; + i.code_page = from->code_page; + + copy_id21_to_sam_passwd("INFO_2", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo4 to a struct samu +**************************************************************/ + +void copy_id4_to_sam_passwd(struct samu *to, + struct samr_UserInfo4 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_LOGON_HOURS; + i.logon_hours = from->logon_hours; + + copy_id21_to_sam_passwd("INFO_4", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo6 to a struct samu +**************************************************************/ + +void copy_id6_to_sam_passwd(struct samu *to, + struct samr_UserInfo6 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_ACCOUNT_NAME | + SAMR_FIELD_FULL_NAME; + i.account_name = from->account_name; + i.full_name = from->full_name; + + copy_id21_to_sam_passwd("INFO_6", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo8 to a struct samu +**************************************************************/ + +void copy_id8_to_sam_passwd(struct samu *to, + struct samr_UserInfo8 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_FULL_NAME; + i.full_name = from->full_name; + + copy_id21_to_sam_passwd("INFO_8", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo10 to a struct samu +**************************************************************/ + +void copy_id10_to_sam_passwd(struct samu *to, + struct samr_UserInfo10 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_HOME_DIRECTORY | + SAMR_FIELD_HOME_DRIVE; + i.home_directory = from->home_directory; + i.home_drive = from->home_drive; + + copy_id21_to_sam_passwd("INFO_10", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo11 to a struct samu +**************************************************************/ + +void copy_id11_to_sam_passwd(struct samu *to, + struct samr_UserInfo11 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_LOGON_SCRIPT; + i.logon_script = from->logon_script; + + copy_id21_to_sam_passwd("INFO_11", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo12 to a struct samu +**************************************************************/ + +void copy_id12_to_sam_passwd(struct samu *to, + struct samr_UserInfo12 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_PROFILE_PATH; + i.profile_path = from->profile_path; + + copy_id21_to_sam_passwd("INFO_12", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo13 to a struct samu +**************************************************************/ + +void copy_id13_to_sam_passwd(struct samu *to, + struct samr_UserInfo13 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_DESCRIPTION; + i.description = from->description; + + copy_id21_to_sam_passwd("INFO_13", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo14 to a struct samu +**************************************************************/ + +void copy_id14_to_sam_passwd(struct samu *to, + struct samr_UserInfo14 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_WORKSTATIONS; + i.workstations = from->workstations; + + copy_id21_to_sam_passwd("INFO_14", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo16 to a struct samu +**************************************************************/ + +void copy_id16_to_sam_passwd(struct samu *to, + struct samr_UserInfo16 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_ACCT_FLAGS; + i.acct_flags = from->acct_flags; + + copy_id21_to_sam_passwd("INFO_16", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo17 to a struct samu +**************************************************************/ + +void copy_id17_to_sam_passwd(struct samu *to, + struct samr_UserInfo17 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_ACCT_EXPIRY; + i.acct_expiry = from->acct_expiry; + + copy_id21_to_sam_passwd("INFO_17", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo18 to a struct samu +**************************************************************/ + +void copy_id18_to_sam_passwd(struct samu *to, + struct samr_UserInfo18 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_EXPIRED_FLAG; + i.password_expired = from->password_expired; + + copy_id21_to_sam_passwd("INFO_18", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo20 to a struct samu +**************************************************************/ + +void copy_id20_to_sam_passwd(struct samu *to, + struct samr_UserInfo20 *from) +{ + DATA_BLOB mung; + + if (from == NULL || to == NULL) { + return; + } + + if (from->parameters.array) { + const char *old_string; + char *new_string = NULL; + old_string = pdb_get_munged_dial(to); + mung = data_blob_const(from->parameters.array, + from->parameters.length); + + if (mung.length != 0) { + new_string = base64_encode_data_blob(talloc_tos(), + mung); + SMB_ASSERT(new_string != NULL); + } + + DEBUG(10,("INFO_20 PARAMETERS: %s -> %s\n", + old_string, new_string)); + if (STRING_CHANGED_NC(old_string,new_string)) { + pdb_set_munged_dial(to, new_string, PDB_CHANGED); + } + + TALLOC_FREE(new_string); + } +} + +/************************************************************* + Copies a struct samr_UserInfo21 to a struct samu +**************************************************************/ + +void copy_id21_to_sam_passwd(const char *log_prefix, + struct samu *to, + struct samr_UserInfo21 *from) +{ + time_t unix_time, stored_time; + const char *old_string, *new_string; + const char *l; + + if (from == NULL || to == NULL) { + return; + } + + if (log_prefix) { + l = log_prefix; + } else { + l = "INFO_21"; + } + + if (from->fields_present & SAMR_FIELD_LAST_LOGON) { + unix_time = nt_time_to_unix(from->last_logon); + stored_time = pdb_get_logon_time(to); + DEBUG(10,("%s SAMR_FIELD_LAST_LOGON: %lu -> %lu\n", l, + (long unsigned int)stored_time, + (long unsigned int)unix_time)); + if (stored_time != unix_time) { + pdb_set_logon_time(to, unix_time, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_LAST_LOGOFF) { + unix_time = nt_time_to_unix(from->last_logoff); + stored_time = pdb_get_logoff_time(to); + DEBUG(10,("%s SAMR_FIELD_LAST_LOGOFF: %lu -> %lu\n", l, + (long unsigned int)stored_time, + (long unsigned int)unix_time)); + if (stored_time != unix_time) { + pdb_set_logoff_time(to, unix_time, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_ACCT_EXPIRY) { + unix_time = nt_time_to_unix(from->acct_expiry); + stored_time = pdb_get_kickoff_time(to); + DEBUG(10,("%s SAMR_FIELD_ACCT_EXPIRY: %lu -> %lu\n", l, + (long unsigned int)stored_time, + (long unsigned int)unix_time)); + if (stored_time != unix_time) { + pdb_set_kickoff_time(to, unix_time , PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + unix_time = nt_time_to_unix(from->last_password_change); + stored_time = pdb_get_pass_last_set_time(to); + DEBUG(10,("%s SAMR_FIELD_LAST_PWD_CHANGE: %lu -> %lu\n", l, + (long unsigned int)stored_time, + (long unsigned int)unix_time)); + if (stored_time != unix_time) { + pdb_set_pass_last_set_time(to, unix_time, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_ACCOUNT_NAME) && + (from->account_name.string)) { + old_string = pdb_get_username(to); + new_string = from->account_name.string; + DEBUG(10,("%s SAMR_FIELD_ACCOUNT_NAME: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_username(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_FULL_NAME) && + (from->full_name.string)) { + old_string = pdb_get_fullname(to); + new_string = from->full_name.string; + DEBUG(10,("%s SAMR_FIELD_FULL_NAME: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_fullname(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_HOME_DIRECTORY) && + (from->home_directory.string)) { + old_string = pdb_get_homedir(to); + new_string = from->home_directory.string; + DEBUG(10,("%s SAMR_FIELD_HOME_DIRECTORY: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_homedir(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_HOME_DRIVE) && + (from->home_drive.string)) { + old_string = pdb_get_dir_drive(to); + new_string = from->home_drive.string; + DEBUG(10,("%s SAMR_FIELD_HOME_DRIVE: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_dir_drive(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_LOGON_SCRIPT) && + (from->logon_script.string)) { + old_string = pdb_get_logon_script(to); + new_string = from->logon_script.string; + DEBUG(10,("%s SAMR_FIELD_LOGON_SCRIPT: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_logon_script(to , new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_PROFILE_PATH) && + (from->profile_path.string)) { + old_string = pdb_get_profile_path(to); + new_string = from->profile_path.string; + DEBUG(10,("%s SAMR_FIELD_PROFILE_PATH: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_profile_path(to , new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_DESCRIPTION) && + (from->description.string)) { + old_string = pdb_get_acct_desc(to); + new_string = from->description.string; + DEBUG(10,("%s SAMR_FIELD_DESCRIPTION: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_acct_desc(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_WORKSTATIONS) && + (from->workstations.string)) { + old_string = pdb_get_workstations(to); + new_string = from->workstations.string; + DEBUG(10,("%s SAMR_FIELD_WORKSTATIONS: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_workstations(to , new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_COMMENT) && + (from->comment.string)) { + old_string = pdb_get_comment(to); + new_string = from->comment.string; + DEBUG(10,("%s SAMR_FIELD_COMMENT: %s -> %s\n", l, + old_string, new_string)); + if (STRING_CHANGED) { + pdb_set_comment(to, new_string, PDB_CHANGED); + } + } + + if ((from->fields_present & SAMR_FIELD_PARAMETERS) && + (from->parameters.array)) { + char *newstr = NULL; + DATA_BLOB mung; + old_string = pdb_get_munged_dial(to); + + mung = data_blob_const(from->parameters.array, + from->parameters.length); + + if (mung.length != 0) { + newstr = base64_encode_data_blob(talloc_tos(), mung); + SMB_ASSERT(newstr != NULL); + } + DEBUG(10,("%s SAMR_FIELD_PARAMETERS: %s -> %s\n", l, + old_string, newstr)); + if (STRING_CHANGED_NC(old_string,newstr)) { + pdb_set_munged_dial(to, newstr, PDB_CHANGED); + } + + TALLOC_FREE(newstr); + } + + if (from->fields_present & SAMR_FIELD_RID) { + if (from->rid == 0) { + DEBUG(10,("%s: Asked to set User RID to 0 !? Skipping change!\n", l)); + } else if (from->rid != pdb_get_user_rid(to)) { + DEBUG(10,("%s SAMR_FIELD_RID: %u -> %u NOT UPDATED!\n", l, + pdb_get_user_rid(to), from->rid)); + } + } + + if (from->fields_present & SAMR_FIELD_PRIMARY_GID) { + if (from->primary_gid == 0) { + DEBUG(10,("%s: Asked to set Group RID to 0 !? Skipping change!\n", l)); + } else if (from->primary_gid != pdb_get_group_rid(to)) { + DEBUG(10,("%s SAMR_FIELD_PRIMARY_GID: %u -> %u\n", l, + pdb_get_group_rid(to), from->primary_gid)); + pdb_set_group_sid_from_rid(to, + from->primary_gid, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_ACCT_FLAGS) { + DEBUG(10,("%s SAMR_FIELD_ACCT_FLAGS: %08X -> %08X\n", l, + pdb_get_acct_ctrl(to), from->acct_flags)); + if (from->acct_flags != pdb_get_acct_ctrl(to)) { + + /* You cannot autolock an unlocked account via + * setuserinfo calls, so make sure to remove the + * ACB_AUTOLOCK bit here - gd */ + + if ((from->acct_flags & ACB_AUTOLOCK) && + !(pdb_get_acct_ctrl(to) & ACB_AUTOLOCK)) { + from->acct_flags &= ~ACB_AUTOLOCK; + } + + if (!(from->acct_flags & ACB_AUTOLOCK) && + (pdb_get_acct_ctrl(to) & ACB_AUTOLOCK)) { + /* We're unlocking a previously locked user. Reset bad password counts. + Patch from Jianliang Lu. <Jianliang.Lu@getronics.com> */ + pdb_set_bad_password_count(to, 0, PDB_CHANGED); + pdb_set_bad_password_time(to, 0, PDB_CHANGED); + } + pdb_set_acct_ctrl(to, from->acct_flags, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_LOGON_HOURS) { + char oldstr[44]; /* hours strings are 42 bytes. */ + char newstr[44]; + DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (units_per_week): %08X -> %08X\n", l, + pdb_get_logon_divs(to), from->logon_hours.units_per_week)); + if (from->logon_hours.units_per_week != pdb_get_logon_divs(to)) { + pdb_set_logon_divs(to, + from->logon_hours.units_per_week, PDB_CHANGED); + } + + DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (units_per_week/8): %08X -> %08X\n", l, + pdb_get_hours_len(to), + from->logon_hours.units_per_week/8)); + if (from->logon_hours.units_per_week/8 != pdb_get_hours_len(to)) { + pdb_set_hours_len(to, + from->logon_hours.units_per_week/8, PDB_CHANGED); + } + + DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (bits): %s -> %s\n", l, + pdb_get_hours(to), from->logon_hours.bits)); + pdb_sethexhours(oldstr, pdb_get_hours(to)); + pdb_sethexhours(newstr, from->logon_hours.bits); + if (!strequal(oldstr, newstr)) { + pdb_set_hours(to, from->logon_hours.bits, + from->logon_hours.units_per_week/8, + PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_BAD_PWD_COUNT) { + DEBUG(10,("%s SAMR_FIELD_BAD_PWD_COUNT: %08X -> %08X\n", l, + pdb_get_bad_password_count(to), from->bad_password_count)); + if (from->bad_password_count != pdb_get_bad_password_count(to)) { + pdb_set_bad_password_count(to, + from->bad_password_count, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_NUM_LOGONS) { + DEBUG(10,("%s SAMR_FIELD_NUM_LOGONS: %08X -> %08X\n", l, + pdb_get_logon_count(to), from->logon_count)); + if (from->logon_count != pdb_get_logon_count(to)) { + pdb_set_logon_count(to, from->logon_count, PDB_CHANGED); + } + } + + /* If the must change flag is set, the last set time goes to zero. + the must change and can change fields also do, but they are + calculated from policy, not set from the wire */ + + if (from->fields_present & SAMR_FIELD_EXPIRED_FLAG) { + DEBUG(10,("%s SAMR_FIELD_EXPIRED_FLAG: %02X\n", l, + from->password_expired)); + if (from->password_expired != 0) { + /* Only allow the set_time to zero (which means + "User Must Change Password on Next Login" + if the user object allows password change. */ + if (pdb_get_pass_can_change(to)) { + pdb_set_pass_last_set_time(to, 0, PDB_CHANGED); + } else { + DEBUG(10,("%s Disallowing set of 'User Must " + "Change Password on Next Login' as " + "user object disallows this.\n", l)); + } + } else { + /* A subtlety here: some windows commands will + clear the expired flag even though it's not + set, and we don't want to reset the time + in these caess. "net user /dom <user> /active:y" + for example, to clear an autolocked acct. + We must check to see if it's expired first. jmcd */ + + uint32_t pwd_max_age = 0; + time_t now = time(NULL); + + pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &pwd_max_age); + + if (pwd_max_age == (uint32_t)-1 || pwd_max_age == 0) { + pwd_max_age = get_time_t_max(); + } + + stored_time = pdb_get_pass_last_set_time(to); + + /* we will only *set* a pwdlastset date when + a) the last pwdlastset time was 0 (user was forced to + change password). + b) the users password has not expired. gd. */ + + if ((stored_time == 0) || + ((now - stored_time) > pwd_max_age)) { + pdb_set_pass_last_set_time(to, now, PDB_CHANGED); + } + } + } + + if (from->fields_present & SAMR_FIELD_COUNTRY_CODE) { + DEBUG(10,("%s SAMR_FIELD_COUNTRY_CODE: %08X -> %08X\n", l, + pdb_get_country_code(to), from->country_code)); + if (from->country_code != pdb_get_country_code(to)) { + pdb_set_country_code(to, + from->country_code, PDB_CHANGED); + } + } + + if (from->fields_present & SAMR_FIELD_CODE_PAGE) { + DEBUG(10,("%s SAMR_FIELD_CODE_PAGE: %08X -> %08X\n", l, + pdb_get_code_page(to), from->code_page)); + if (from->code_page != pdb_get_code_page(to)) { + pdb_set_code_page(to, + from->code_page, PDB_CHANGED); + } + } +} + + +/************************************************************* + Copies a struct samr_UserInfo23 to a struct samu +**************************************************************/ + +void copy_id23_to_sam_passwd(struct samu *to, + struct samr_UserInfo23 *from) +{ + if (from == NULL || to == NULL) { + return; + } + + copy_id21_to_sam_passwd("INFO 23", to, &from->info); +} + +/************************************************************* + Copies a struct samr_UserInfo24 to a struct samu +**************************************************************/ + +void copy_id24_to_sam_passwd(struct samu *to, + struct samr_UserInfo24 *from) +{ + struct samr_UserInfo21 i; + + if (from == NULL || to == NULL) { + return; + } + + ZERO_STRUCT(i); + + i.fields_present = SAMR_FIELD_EXPIRED_FLAG; + i.password_expired = from->password_expired; + + copy_id21_to_sam_passwd("INFO_24", to, &i); +} + +/************************************************************* + Copies a struct samr_UserInfo25 to a struct samu +**************************************************************/ + +void copy_id25_to_sam_passwd(struct samu *to, + struct samr_UserInfo25 *from) +{ + if (from == NULL || to == NULL) { + return; + } + + copy_id21_to_sam_passwd("INFO_25", to, &from->info); +} + +void copy_id32_to_sam_passwd(struct samu *to, struct samr_UserInfo32 *from) +{ + if (from == NULL || to == NULL) { + return; + } + + copy_id21_to_sam_passwd("INFO_32", to, &from->info); +} + +void copy_pwd_expired_to_sam_passwd(struct samu *to, + uint8_t password_expired) +{ + struct samr_UserInfo21 i = { + .fields_present = SAMR_FIELD_EXPIRED_FLAG, + .password_expired = password_expired, + }; + + if (to == NULL) { + return; + } + + copy_id21_to_sam_passwd("INFO_GENERIC", to, &i); +} diff --git a/source3/rpc_server/samr/srv_samr_util.h b/source3/rpc_server/samr/srv_samr_util.h new file mode 100644 index 0000000..5e839ac --- /dev/null +++ b/source3/rpc_server/samr/srv_samr_util.h @@ -0,0 +1,89 @@ +/* + Unix SMB/CIFS implementation. + SAMR Pipe utility functions. + + Copyright (C) Luke Kenneth Casson Leighton 1996-1998 + Copyright (C) Gerald (Jerry) Carter 2000-2001 + Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Stefan (metze) Metzmacher 2002 + Copyright (C) Guenther Deschner 2008 + + 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/>. +*/ + +/* The following definitions come from rpc_server/srv_samr_util.c */ + +struct samu; + +void copy_id2_to_sam_passwd(struct samu *to, + struct samr_UserInfo2 *from); +void copy_id4_to_sam_passwd(struct samu *to, + struct samr_UserInfo4 *from); +void copy_id6_to_sam_passwd(struct samu *to, + struct samr_UserInfo6 *from); +void copy_id8_to_sam_passwd(struct samu *to, + struct samr_UserInfo8 *from); +void copy_id10_to_sam_passwd(struct samu *to, + struct samr_UserInfo10 *from); +void copy_id11_to_sam_passwd(struct samu *to, + struct samr_UserInfo11 *from); +void copy_id12_to_sam_passwd(struct samu *to, + struct samr_UserInfo12 *from); +void copy_id13_to_sam_passwd(struct samu *to, + struct samr_UserInfo13 *from); +void copy_id14_to_sam_passwd(struct samu *to, + struct samr_UserInfo14 *from); +void copy_id16_to_sam_passwd(struct samu *to, + struct samr_UserInfo16 *from); +void copy_id17_to_sam_passwd(struct samu *to, + struct samr_UserInfo17 *from); +void copy_id18_to_sam_passwd(struct samu *to, + struct samr_UserInfo18 *from); +void copy_id20_to_sam_passwd(struct samu *to, + struct samr_UserInfo20 *from); +void copy_id21_to_sam_passwd(const char *log_prefix, + struct samu *to, + struct samr_UserInfo21 *from); +void copy_id23_to_sam_passwd(struct samu *to, + struct samr_UserInfo23 *from); +void copy_id24_to_sam_passwd(struct samu *to, + struct samr_UserInfo24 *from); +void copy_id25_to_sam_passwd(struct samu *to, + struct samr_UserInfo25 *from); +void copy_id32_to_sam_passwd(struct samu *to, struct samr_UserInfo32 *from); +void copy_pwd_expired_to_sam_passwd(struct samu *to, + uint8_t password_expired); + +/* The following definitions come from rpc_server/srv_samr_chgpasswd.c */ + +bool chgpasswd(const char *name, const char *rhost, const struct passwd *pass, + const char *oldpass, const char *newpass, bool as_root); +NTSTATUS change_oem_password(struct samu *hnd, const char *rhost, + char *old_passwd, char *new_passwd, + bool as_root, + enum samPwdChangeReason *samr_reject_reason); +NTSTATUS pass_oem_change(char *user, const char *rhost, + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + enum samPwdChangeReason *reject_reason); +NTSTATUS check_password_complexity(const char *username, + const char *fullname, + const char *password, + enum samPwdChangeReason *samr_reject_reason); +NTSTATUS samr_set_password_aes(TALLOC_CTX *mem_ctx, + const DATA_BLOB *cdk, + struct samr_EncryptedPasswordAES *pwbuf, + char **new_password_str); diff --git a/source3/rpc_server/spoolss/iremotewinspool_util.c b/source3/rpc_server/spoolss/iremotewinspool_util.c new file mode 100644 index 0000000..6b9c726 --- /dev/null +++ b/source3/rpc_server/spoolss/iremotewinspool_util.c @@ -0,0 +1,156 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Guenther Deschner 2016 + + 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 "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "rpc_server/spoolss/iremotewinspool_util.h" + +#define _PAR_MAPPING(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME } +#define _PAR_MAPPING_EX(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME## EX } +#define _PAR_MAPPING_2(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME## 2 } + +struct { + int iremotewinspool_opcode; + int spoolss_opcode; +} proxy_table[] = { + + /* 3.1.4.1. Printer Management Methods */ + + _PAR_MAPPING_EX(OPENPRINTER), + _PAR_MAPPING_EX(ADDPRINTER), + _PAR_MAPPING(DELETEPRINTER), + _PAR_MAPPING(SETPRINTER), + _PAR_MAPPING(GETPRINTER), + _PAR_MAPPING(GETPRINTERDATA), + _PAR_MAPPING(GETPRINTERDATAEX), + _PAR_MAPPING(SETPRINTERDATA), + _PAR_MAPPING(SETPRINTERDATAEX), + _PAR_MAPPING(CLOSEPRINTER), + _PAR_MAPPING(ENUMPRINTERDATA), + _PAR_MAPPING(ENUMPRINTERDATAEX), + _PAR_MAPPING(ENUMPRINTERKEY), + _PAR_MAPPING(DELETEPRINTERDATA), + _PAR_MAPPING(DELETEPRINTERDATAEX), + _PAR_MAPPING(DELETEPRINTERKEY), + _PAR_MAPPING(SENDRECVBIDIDATA), + _PAR_MAPPING(CREATEPRINTERIC), + _PAR_MAPPING(PLAYGDISCRIPTONPRINTERIC), + _PAR_MAPPING(DELETEPRINTERIC), + _PAR_MAPPING(ENUMPRINTERS), + _PAR_MAPPING(ADDPERMACHINECONNECTION), + _PAR_MAPPING(DELETEPERMACHINECONNECTION), + _PAR_MAPPING(ENUMPERMACHINECONNECTIONS), + _PAR_MAPPING(RESETPRINTER), + + /* 3.1.4.2. Printer Driver Management Methods */ + + _PAR_MAPPING_2(GETPRINTERDRIVER), + _PAR_MAPPING_EX(ADDPRINTERDRIVER), + _PAR_MAPPING(ENUMPRINTERDRIVERS), + _PAR_MAPPING(GETPRINTERDRIVERDIRECTORY), + _PAR_MAPPING(DELETEPRINTERDRIVER), + _PAR_MAPPING(DELETEPRINTERDRIVEREX), + /* No mapping for: RpcAsyncInstallPrinterDriverFromPackage */ + /* No mapping for: RpcAsyncUploadPrinterDriverPackage */ + _PAR_MAPPING(GETCOREPRINTERDRIVERS), + /* No mapping for: RpcAsyncCorePrinterDriverInstalled */ + _PAR_MAPPING(GETPRINTERDRIVERPACKAGEPATH), + /* No mapping for: RpcAsyncDeletePrinterDriverPackage */ + + /* 3.1.4.3. Printer Port Management Methods */ + + _PAR_MAPPING(XCVDATA), + _PAR_MAPPING(ENUMPORTS), + _PAR_MAPPING_EX(ADDPORT), + _PAR_MAPPING(SETPORT), + + /* 3.1.4.4. Printer Processor Management Methods */ + + _PAR_MAPPING(ADDPRINTPROCESSOR), + _PAR_MAPPING(ENUMPRINTPROCESSORS), + _PAR_MAPPING(GETPRINTPROCESSORDIRECTORY), + _PAR_MAPPING(DELETEPRINTPROCESSOR), + _PAR_MAPPING(ENUMPRINTPROCESSORDATATYPES), + + /* 3.1.4.5. Port Monitor Management Methods */ + + _PAR_MAPPING(ENUMMONITORS), + _PAR_MAPPING(ADDMONITOR), + _PAR_MAPPING(DELETEMONITOR), + + /* 3.1.4.6. Form Management Methods */ + + _PAR_MAPPING(ADDFORM), + _PAR_MAPPING(DELETEFORM), + _PAR_MAPPING(GETFORM), + _PAR_MAPPING(SETFORM), + _PAR_MAPPING(ENUMFORMS), + + /* 3.1.4.7. Job Management Methods */ + + _PAR_MAPPING(SETJOB), + _PAR_MAPPING(GETJOB), + _PAR_MAPPING(ENUMJOBS), + _PAR_MAPPING(ADDJOB), + _PAR_MAPPING(SCHEDULEJOB), + + /* 3.1.4.8. Job Printing Methods */ + + _PAR_MAPPING(STARTDOCPRINTER), + _PAR_MAPPING(STARTPAGEPRINTER), + _PAR_MAPPING(WRITEPRINTER), + _PAR_MAPPING(ENDPAGEPRINTER), + _PAR_MAPPING(ENDDOCPRINTER), + _PAR_MAPPING(ABORTPRINTER), + _PAR_MAPPING(READPRINTER), + + /* 3.1.4.9. Printing Related Notification Methods */ + + /* No mapping for: RpcSyncRegisterForRemoteNotifications */ + /* No mapping for: RpcSyncUnRegisterForRemoteNotifications */ + /* No mapping for: RpcSyncRefreshRemoteNotifications */ + /* No mapping for: RpcAsyncGetRemoteNotifications */ + + /* 3.1.4.10. Job Named Property Management Methods */ + + _PAR_MAPPING(GETJOBNAMEDPROPERTYVALUE), + _PAR_MAPPING(SETJOBNAMEDPROPERTY), + _PAR_MAPPING(DELETEJOBNAMEDPROPERTY), + _PAR_MAPPING(ENUMJOBNAMEDPROPERTIES), + + /* 3.1.4.11. Branch Office Remote Logging Methods */ + + _PAR_MAPPING(LOGJOBINFOFORBRANCHOFFICE), +}; + +bool iremotewinspool_map_opcode(uint16_t opcode, + uint16_t *proxy_opcode) +{ + int i; + + for (i = 0; i <ARRAY_SIZE(proxy_table); i++) { + if (proxy_table[i].iremotewinspool_opcode == opcode) { + *proxy_opcode = proxy_table[i].spoolss_opcode; + return true; + } + } + + return false; +} diff --git a/source3/rpc_server/spoolss/iremotewinspool_util.h b/source3/rpc_server/spoolss/iremotewinspool_util.h new file mode 100644 index 0000000..e2de82a --- /dev/null +++ b/source3/rpc_server/spoolss/iremotewinspool_util.h @@ -0,0 +1,21 @@ +/* + Unix SMB/CIFS implementation. + + Copyright (C) Guenther Deschner 2016 + + 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/>. +*/ + +bool iremotewinspool_map_opcode(uint16_t opcode, + uint16_t *proxy_opcode); diff --git a/source3/rpc_server/spoolss/srv_iremotewinspool.c b/source3/rpc_server/spoolss/srv_iremotewinspool.c new file mode 100644 index 0000000..5bc4228 --- /dev/null +++ b/source3/rpc_server/spoolss/srv_iremotewinspool.c @@ -0,0 +1,2382 @@ +/* + * Unix SMB/CIFS implementation. + * server auto-generated by pidl. DO NOT MODIFY! + */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_winspool_scompat.h" +#include "librpc/gen_ndr/ndr_spoolss_scompat.h" +#include "rpc_server/rpc_config.h" +#include "rpc_server/rpc_server.h" +#include "rpc_server/spoolss/iremotewinspool_util.h" + +static bool forward_opnum_to_spoolss(uint16_t opnum) { + switch (opnum) { + case 58: /* winspool_SyncRegisterForRemoteNotifications */ + case 59: /* winspool_SyncUnRegisterForRemoteNotifications */ + case 60: /* winspool_SyncRefreshRemoteNotifications */ + case 61: /* winspool_AsyncGetRemoteNotifications */ + case 62: /* winspool_AsyncInstallPrinterDriverFromPackage */ + case 63: /* winspool_AsyncUploadPrinterDriverPackage */ + case 65: /* winspool_AsyncCorePrinterDriverInstalled */ + case 67: /* winspool_AsyncDeletePrinterDriverPackage */ + return false; + default: + break; + } + return true; +} + +/* iremotewinspool - dcerpc server boilerplate generated by pidl */ +static NTSTATUS iremotewinspool__op_bind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface) +{ +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_BIND + return DCESRV_INTERFACE_IREMOTEWINSPOOL_BIND(context,iface); +#else + return NT_STATUS_OK; +#endif +} + +static void iremotewinspool__op_unbind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface) +{ +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_UNBIND + DCESRV_INTERFACE_IREMOTEWINSPOOL_UNBIND(context, iface); +#else + return; +#endif +} + +NTSTATUS iremotewinspool__op_ndr_pull(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_pull *pull, void **r) +{ + enum ndr_err_code ndr_err; + uint16_t opnum = dce_call->pkt.u.request.opnum; + uint16_t mapped_opnum; + + dce_call->fault_code = 0; + + if (forward_opnum_to_spoolss(opnum)) { + bool ok; + ok = iremotewinspool_map_opcode(opnum, &mapped_opnum); + if (ok) { + dce_call->pkt.u.request.opnum = mapped_opnum; + } + return spoolss__op_ndr_pull(dce_call, mem_ctx, pull, r); + } + + if (opnum >= ndr_table_iremotewinspool.num_calls) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + + *r = talloc_named(mem_ctx, ndr_table_iremotewinspool.calls[opnum].struct_size, "struct %s", ndr_table_iremotewinspool.calls[opnum].name); + NT_STATUS_HAVE_NO_MEMORY(*r); + + /* unravel the NDR for the packet */ + ndr_err = ndr_table_iremotewinspool.calls[opnum].ndr_pull(pull, NDR_IN, *r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +static NTSTATUS iremotewinspool__op_dispatch_internal(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r, bool rpcint_call) +{ + uint16_t opnum = dce_call->pkt.u.request.opnum; + struct pipes_struct *p = NULL; + NTSTATUS status = NT_STATUS_OK; + bool impersonated = false; + bool ok; + struct GUID object_uuid; + + ok = dce_call->pkt.pfc_flags & DCERPC_PFC_FLAG_OBJECT_UUID; + if (!ok) { + dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE; + return NT_STATUS_NET_WRITE_FAULT; + } + + status = GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &object_uuid); + if (!NT_STATUS_IS_OK(status)) { + dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE; + return NT_STATUS_NET_WRITE_FAULT; + } + + if (!GUID_equal(&dce_call->pkt.u.request.object.object, &object_uuid)) { + dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE; + return NT_STATUS_NET_WRITE_FAULT; + } + + if (forward_opnum_to_spoolss(opnum)) { + return spoolss__op_dispatch(dce_call, mem_ctx, r); + } + + /* Retrieve pipes struct */ + p = dcesrv_get_pipes_struct(dce_call->conn); + p->dce_call = dce_call; + p->mem_ctx = mem_ctx; + /* Reset pipes struct fault state */ + p->fault_state = 0; + + /* Impersonate */ + if (!rpcint_call) { + impersonated = become_authenticated_pipe_user( + dce_call->auth_state->session_info); + if (!impersonated) { + dce_call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + status = NT_STATUS_NET_WRITE_FAULT; + goto fail; + } + } + + switch (opnum) { + case 0: { /* winspool_AsyncOpenPrinter */ + struct winspool_AsyncOpenPrinter *r2 = (struct winspool_AsyncOpenPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncOpenPrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pHandle = talloc_zero(r2, struct policy_handle); + if (r2->out.pHandle == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncOpenPrinter(p, r2); + break; + } + case 1: { /* winspool_AsyncAddPrinter */ + struct winspool_AsyncAddPrinter *r2 = (struct winspool_AsyncAddPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pHandle = talloc_zero(r2, struct policy_handle); + if (r2->out.pHandle == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncAddPrinter(p, r2); + break; + } + case 2: { /* winspool_AsyncSetJob */ + struct winspool_AsyncSetJob *r2 = (struct winspool_AsyncSetJob *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJob, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetJob(p, r2); + break; + } + case 3: { /* winspool_AsyncGetJob */ + struct winspool_AsyncGetJob *r2 = (struct winspool_AsyncGetJob *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJob, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pJob = r2->in.pJob; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetJob(p, r2); + break; + } + case 4: { /* winspool_AsyncEnumJobs */ + struct winspool_AsyncEnumJobs *r2 = (struct winspool_AsyncEnumJobs *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobs, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pJob = r2->in.pJob; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumJobs(p, r2); + break; + } + case 5: { /* winspool_AsyncAddJob */ + struct winspool_AsyncAddJob *r2 = (struct winspool_AsyncAddJob *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddJob, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pAddJob = r2->in.pAddJob; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncAddJob(p, r2); + break; + } + case 6: { /* winspool_AsyncScheduleJob */ + struct winspool_AsyncScheduleJob *r2 = (struct winspool_AsyncScheduleJob *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncScheduleJob, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncScheduleJob(p, r2); + break; + } + case 7: { /* winspool_AsyncDeletePrinter */ + struct winspool_AsyncDeletePrinter *r2 = (struct winspool_AsyncDeletePrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinter(p, r2); + break; + } + case 8: { /* winspool_AsyncSetPrinter */ + struct winspool_AsyncSetPrinter *r2 = (struct winspool_AsyncSetPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetPrinter(p, r2); + break; + } + case 9: { /* winspool_AsyncGetPrinter */ + struct winspool_AsyncGetPrinter *r2 = (struct winspool_AsyncGetPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPrinter = r2->in.pPrinter; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinter(p, r2); + break; + } + case 10: { /* winspool_AsyncStartDocPrinter */ + struct winspool_AsyncStartDocPrinter *r2 = (struct winspool_AsyncStartDocPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartDocPrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pJobId = talloc_zero(r2, uint32_t); + if (r2->out.pJobId == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncStartDocPrinter(p, r2); + break; + } + case 11: { /* winspool_AsyncStartPagePrinter */ + struct winspool_AsyncStartPagePrinter *r2 = (struct winspool_AsyncStartPagePrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartPagePrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncStartPagePrinter(p, r2); + break; + } + case 12: { /* winspool_AsyncWritePrinter */ + struct winspool_AsyncWritePrinter *r2 = (struct winspool_AsyncWritePrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncWritePrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pcWritten = talloc_zero(r2, uint32_t); + if (r2->out.pcWritten == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncWritePrinter(p, r2); + break; + } + case 13: { /* winspool_AsyncEndPagePrinter */ + struct winspool_AsyncEndPagePrinter *r2 = (struct winspool_AsyncEndPagePrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndPagePrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncEndPagePrinter(p, r2); + break; + } + case 14: { /* winspool_AsyncEndDocPrinter */ + struct winspool_AsyncEndDocPrinter *r2 = (struct winspool_AsyncEndDocPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndDocPrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncEndDocPrinter(p, r2); + break; + } + case 15: { /* winspool_AsyncAbortPrinter */ + struct winspool_AsyncAbortPrinter *r2 = (struct winspool_AsyncAbortPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAbortPrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAbortPrinter(p, r2); + break; + } + case 16: { /* winspool_AsyncGetPrinterData */ + struct winspool_AsyncGetPrinterData *r2 = (struct winspool_AsyncGetPrinterData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterData, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pType = talloc_zero(r2, uint32_t); + if (r2->out.pType == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.nSize); + if (r2->out.pData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinterData(p, r2); + break; + } + case 17: { /* winspool_AsyncGetPrinterDataEx */ + struct winspool_AsyncGetPrinterDataEx *r2 = (struct winspool_AsyncGetPrinterDataEx *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDataEx, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pType = talloc_zero(r2, uint32_t); + if (r2->out.pType == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.nSize); + if (r2->out.pData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinterDataEx(p, r2); + break; + } + case 18: { /* winspool_AsyncSetPrinterData */ + struct winspool_AsyncSetPrinterData *r2 = (struct winspool_AsyncSetPrinterData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterData, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetPrinterData(p, r2); + break; + } + case 19: { /* winspool_AsyncSetPrinterDataEx */ + struct winspool_AsyncSetPrinterDataEx *r2 = (struct winspool_AsyncSetPrinterDataEx *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterDataEx, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetPrinterDataEx(p, r2); + break; + } + case 20: { /* winspool_AsyncClosePrinter */ + struct winspool_AsyncClosePrinter *r2 = (struct winspool_AsyncClosePrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncClosePrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.phPrinter = r2->in.phPrinter; + r2->out.result = _winspool_AsyncClosePrinter(p, r2); + break; + } + case 21: { /* winspool_AsyncAddForm */ + struct winspool_AsyncAddForm *r2 = (struct winspool_AsyncAddForm *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddForm, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddForm(p, r2); + break; + } + case 22: { /* winspool_AsyncDeleteForm */ + struct winspool_AsyncDeleteForm *r2 = (struct winspool_AsyncDeleteForm *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteForm, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeleteForm(p, r2); + break; + } + case 23: { /* winspool_AsyncGetForm */ + struct winspool_AsyncGetForm *r2 = (struct winspool_AsyncGetForm *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetForm, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pForm = r2->in.pForm; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetForm(p, r2); + break; + } + case 24: { /* winspool_AsyncSetForm */ + struct winspool_AsyncSetForm *r2 = (struct winspool_AsyncSetForm *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetForm, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetForm(p, r2); + break; + } + case 25: { /* winspool_AsyncEnumForms */ + struct winspool_AsyncEnumForms *r2 = (struct winspool_AsyncEnumForms *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumForms, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pForm = r2->in.pForm; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumForms(p, r2); + break; + } + case 26: { /* winspool_AsyncGetPrinterDriver */ + struct winspool_AsyncGetPrinterDriver *r2 = (struct winspool_AsyncGetPrinterDriver *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriver, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pDriver = r2->in.pDriver; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pdwServerMaxVersion = talloc_zero(r2, uint32_t); + if (r2->out.pdwServerMaxVersion == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pdwServerMinVersion = talloc_zero(r2, uint32_t); + if (r2->out.pdwServerMinVersion == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinterDriver(p, r2); + break; + } + case 27: { /* winspool_AsyncEnumPrinterData */ + struct winspool_AsyncEnumPrinterData *r2 = (struct winspool_AsyncEnumPrinterData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterData, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pValueName = talloc_zero_array(r2, uint16_t, r2->in.cbValueName / 2); + if (r2->out.pValueName == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbValueName = talloc_zero(r2, uint32_t); + if (r2->out.pcbValueName == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pType = talloc_zero(r2, uint32_t); + if (r2->out.pType == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.cbData); + if (r2->out.pData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbData = talloc_zero(r2, uint32_t); + if (r2->out.pcbData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrinterData(p, r2); + break; + } + case 28: { /* winspool_AsyncEnumPrinterDataEx */ + struct winspool_AsyncEnumPrinterDataEx *r2 = (struct winspool_AsyncEnumPrinterDataEx *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDataEx, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pEnumValues = talloc_zero_array(r2, uint8_t, r2->in.cbEnumValues); + if (r2->out.pEnumValues == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbEnumValues = talloc_zero(r2, uint32_t); + if (r2->out.pcbEnumValues == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pnEnumValues = talloc_zero(r2, uint32_t); + if (r2->out.pnEnumValues == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrinterDataEx(p, r2); + break; + } + case 29: { /* winspool_AsyncEnumPrinterKey */ + struct winspool_AsyncEnumPrinterKey *r2 = (struct winspool_AsyncEnumPrinterKey *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterKey, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pSubkey = talloc_zero_array(r2, uint16_t, r2->in.cbSubkey / 2); + if (r2->out.pSubkey == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbSubkey = talloc_zero(r2, uint32_t); + if (r2->out.pcbSubkey == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrinterKey(p, r2); + break; + } + case 30: { /* winspool_AsyncDeletePrinterData */ + struct winspool_AsyncDeletePrinterData *r2 = (struct winspool_AsyncDeletePrinterData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterData, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterData(p, r2); + break; + } + case 31: { /* winspool_AsyncDeletePrinterDataEx */ + struct winspool_AsyncDeletePrinterDataEx *r2 = (struct winspool_AsyncDeletePrinterDataEx *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDataEx, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterDataEx(p, r2); + break; + } + case 32: { /* winspool_AsyncDeletePrinterKey */ + struct winspool_AsyncDeletePrinterKey *r2 = (struct winspool_AsyncDeletePrinterKey *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterKey, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterKey(p, r2); + break; + } + case 33: { /* winspool_AsyncXcvData */ + struct winspool_AsyncXcvData *r2 = (struct winspool_AsyncXcvData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncXcvData, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pdwStatus = r2->in.pdwStatus; + r2->out.pOutputData = talloc_zero_array(r2, uint8_t, r2->in.cbOutputData); + if (r2->out.pOutputData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcbOutputNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbOutputNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncXcvData(p, r2); + break; + } + case 34: { /* winspool_AsyncSendRecvBidiData */ + struct winspool_AsyncSendRecvBidiData *r2 = (struct winspool_AsyncSendRecvBidiData *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSendRecvBidiData, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.ppRespData = talloc_zero(r2, struct RPC_BIDI_RESPONSE_CONTAINER *); + if (r2->out.ppRespData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncSendRecvBidiData(p, r2); + break; + } + case 35: { /* winspool_AsyncCreatePrinterIC */ + struct winspool_AsyncCreatePrinterIC *r2 = (struct winspool_AsyncCreatePrinterIC *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCreatePrinterIC, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pHandle = talloc_zero(r2, struct policy_handle); + if (r2->out.pHandle == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncCreatePrinterIC(p, r2); + break; + } + case 36: { /* winspool_AsyncPlayGdiScriptOnPrinterIC */ + struct winspool_AsyncPlayGdiScriptOnPrinterIC *r2 = (struct winspool_AsyncPlayGdiScriptOnPrinterIC *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncPlayGdiScriptOnPrinterIC, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pOut = talloc_zero_array(r2, uint8_t, r2->in.cOut); + if (r2->out.pOut == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncPlayGdiScriptOnPrinterIC(p, r2); + break; + } + case 37: { /* winspool_AsyncDeletePrinterIC */ + struct winspool_AsyncDeletePrinterIC *r2 = (struct winspool_AsyncDeletePrinterIC *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterIC, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.phPrinterIC = r2->in.phPrinterIC; + r2->out.result = _winspool_AsyncDeletePrinterIC(p, r2); + break; + } + case 38: { /* winspool_AsyncEnumPrinters */ + struct winspool_AsyncEnumPrinters *r2 = (struct winspool_AsyncEnumPrinters *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinters, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPrinterEnum = r2->in.pPrinterEnum; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrinters(p, r2); + break; + } + case 39: { /* winspool_AsyncAddPrinterDriver */ + struct winspool_AsyncAddPrinterDriver *r2 = (struct winspool_AsyncAddPrinterDriver *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinterDriver, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddPrinterDriver(p, r2); + break; + } + case 40: { /* winspool_AsyncEnumPrinterDrivers */ + struct winspool_AsyncEnumPrinterDrivers *r2 = (struct winspool_AsyncEnumPrinterDrivers *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDrivers, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pDrivers = r2->in.pDrivers; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrinterDrivers(p, r2); + break; + } + case 41: { /* winspool_AsyncGetPrinterDriverDirectory */ + struct winspool_AsyncGetPrinterDriverDirectory *r2 = (struct winspool_AsyncGetPrinterDriverDirectory *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverDirectory, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pDriverDirectory = r2->in.pDriverDirectory; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinterDriverDirectory(p, r2); + break; + } + case 42: { /* winspool_AsyncDeletePrinterDriver */ + struct winspool_AsyncDeletePrinterDriver *r2 = (struct winspool_AsyncDeletePrinterDriver *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriver, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterDriver(p, r2); + break; + } + case 43: { /* winspool_AsyncDeletePrinterDriverEx */ + struct winspool_AsyncDeletePrinterDriverEx *r2 = (struct winspool_AsyncDeletePrinterDriverEx *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverEx, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterDriverEx(p, r2); + break; + } + case 44: { /* winspool_AsyncAddPrintProcessor */ + struct winspool_AsyncAddPrintProcessor *r2 = (struct winspool_AsyncAddPrintProcessor *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrintProcessor, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddPrintProcessor(p, r2); + break; + } + case 45: { /* winspool_AsyncEnumPrintProcessors */ + struct winspool_AsyncEnumPrintProcessors *r2 = (struct winspool_AsyncEnumPrintProcessors *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessors, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPrintProcessorInfo = r2->in.pPrintProcessorInfo; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrintProcessors(p, r2); + break; + } + case 46: { /* winspool_AsyncGetPrintProcessorDirectory */ + struct winspool_AsyncGetPrintProcessorDirectory *r2 = (struct winspool_AsyncGetPrintProcessorDirectory *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrintProcessorDirectory, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPrintProcessorDirectory = r2->in.pPrintProcessorDirectory; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrintProcessorDirectory(p, r2); + break; + } + case 47: { /* winspool_AsyncEnumPorts */ + struct winspool_AsyncEnumPorts *r2 = (struct winspool_AsyncEnumPorts *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPorts, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPort = r2->in.pPort; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPorts(p, r2); + break; + } + case 48: { /* winspool_AsyncEnumMonitors */ + struct winspool_AsyncEnumMonitors *r2 = (struct winspool_AsyncEnumMonitors *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumMonitors, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pMonitor = r2->in.pMonitor; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumMonitors(p, r2); + break; + } + case 49: { /* winspool_AsyncAddPort */ + struct winspool_AsyncAddPort *r2 = (struct winspool_AsyncAddPort *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPort, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddPort(p, r2); + break; + } + case 50: { /* winspool_AsyncSetPort */ + struct winspool_AsyncSetPort *r2 = (struct winspool_AsyncSetPort *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPort, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetPort(p, r2); + break; + } + case 51: { /* winspool_AsyncAddMonitor */ + struct winspool_AsyncAddMonitor *r2 = (struct winspool_AsyncAddMonitor *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddMonitor, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddMonitor(p, r2); + break; + } + case 52: { /* winspool_AsyncDeleteMonitor */ + struct winspool_AsyncDeleteMonitor *r2 = (struct winspool_AsyncDeleteMonitor *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteMonitor, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeleteMonitor(p, r2); + break; + } + case 53: { /* winspool_AsyncDeletePrintProcessor */ + struct winspool_AsyncDeletePrintProcessor *r2 = (struct winspool_AsyncDeletePrintProcessor *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrintProcessor, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrintProcessor(p, r2); + break; + } + case 54: { /* winspool_AsyncEnumPrintProcessorDatatypes */ + struct winspool_AsyncEnumPrintProcessorDatatypes *r2 = (struct winspool_AsyncEnumPrintProcessorDatatypes *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessorDatatypes, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pDatatypes = r2->in.pDatatypes; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPrintProcessorDatatypes(p, r2); + break; + } + case 55: { /* winspool_AsyncAddPerMachineConnection */ + struct winspool_AsyncAddPerMachineConnection *r2 = (struct winspool_AsyncAddPerMachineConnection *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPerMachineConnection, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncAddPerMachineConnection(p, r2); + break; + } + case 56: { /* winspool_AsyncDeletePerMachineConnection */ + struct winspool_AsyncDeletePerMachineConnection *r2 = (struct winspool_AsyncDeletePerMachineConnection *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePerMachineConnection, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePerMachineConnection(p, r2); + break; + } + case 57: { /* winspool_AsyncEnumPerMachineConnections */ + struct winspool_AsyncEnumPerMachineConnections *r2 = (struct winspool_AsyncEnumPerMachineConnections *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPerMachineConnections, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pPrinterEnum = r2->in.pPrinterEnum; + r2->out.pcbNeeded = talloc_zero(r2, uint32_t); + if (r2->out.pcbNeeded == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcReturned = talloc_zero(r2, uint32_t); + if (r2->out.pcReturned == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumPerMachineConnections(p, r2); + break; + } + case 58: { /* winspool_SyncRegisterForRemoteNotifications */ + struct winspool_SyncRegisterForRemoteNotifications *r2 = (struct winspool_SyncRegisterForRemoteNotifications *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRegisterForRemoteNotifications, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.phRpcHandle = talloc_zero(r2, struct policy_handle); + if (r2->out.phRpcHandle == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_SyncRegisterForRemoteNotifications(p, r2); + break; + } + case 59: { /* winspool_SyncUnRegisterForRemoteNotifications */ + struct winspool_SyncUnRegisterForRemoteNotifications *r2 = (struct winspool_SyncUnRegisterForRemoteNotifications *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncUnRegisterForRemoteNotifications, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.phRpcHandle = r2->in.phRpcHandle; + r2->out.result = _winspool_SyncUnRegisterForRemoteNotifications(p, r2); + break; + } + case 60: { /* winspool_SyncRefreshRemoteNotifications */ + struct winspool_SyncRefreshRemoteNotifications *r2 = (struct winspool_SyncRefreshRemoteNotifications *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRefreshRemoteNotifications, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.ppNotifyData = talloc_zero(r2, struct winspool_PrintPropertiesCollection *); + if (r2->out.ppNotifyData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_SyncRefreshRemoteNotifications(p, r2); + break; + } + case 61: { /* winspool_AsyncGetRemoteNotifications */ + struct winspool_AsyncGetRemoteNotifications *r2 = (struct winspool_AsyncGetRemoteNotifications *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetRemoteNotifications, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.ppNotifyData = talloc_zero(r2, struct winspool_PrintPropertiesCollection *); + if (r2->out.ppNotifyData == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetRemoteNotifications(p, r2); + break; + } + case 62: { /* winspool_AsyncInstallPrinterDriverFromPackage */ + struct winspool_AsyncInstallPrinterDriverFromPackage *r2 = (struct winspool_AsyncInstallPrinterDriverFromPackage *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncInstallPrinterDriverFromPackage, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncInstallPrinterDriverFromPackage(p, r2); + break; + } + case 63: { /* winspool_AsyncUploadPrinterDriverPackage */ + struct winspool_AsyncUploadPrinterDriverPackage *r2 = (struct winspool_AsyncUploadPrinterDriverPackage *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncUploadPrinterDriverPackage, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pszDestInfPath = r2->in.pszDestInfPath; + r2->out.pcchDestInfPath = r2->in.pcchDestInfPath; + r2->out.result = _winspool_AsyncUploadPrinterDriverPackage(p, r2); + break; + } + case 64: { /* winspool_AsyncGetCorePrinterDrivers */ + struct winspool_AsyncGetCorePrinterDrivers *r2 = (struct winspool_AsyncGetCorePrinterDrivers *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetCorePrinterDrivers, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pCorePrinterDrivers = talloc_zero_array(r2, struct spoolss_CorePrinterDriver, r2->in.cCorePrinterDrivers); + if (r2->out.pCorePrinterDrivers == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetCorePrinterDrivers(p, r2); + break; + } + case 65: { /* winspool_AsyncCorePrinterDriverInstalled */ + struct winspool_AsyncCorePrinterDriverInstalled *r2 = (struct winspool_AsyncCorePrinterDriverInstalled *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCorePrinterDriverInstalled, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pbDriverInstalled = talloc_zero(r2, int32_t); + if (r2->out.pbDriverInstalled == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncCorePrinterDriverInstalled(p, r2); + break; + } + case 66: { /* winspool_AsyncGetPrinterDriverPackagePath */ + struct winspool_AsyncGetPrinterDriverPackagePath *r2 = (struct winspool_AsyncGetPrinterDriverPackagePath *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverPackagePath, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pszDriverPackageCab = r2->in.pszDriverPackageCab; + r2->out.pcchRequiredSize = talloc_zero(r2, uint32_t); + if (r2->out.pcchRequiredSize == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetPrinterDriverPackagePath(p, r2); + break; + } + case 67: { /* winspool_AsyncDeletePrinterDriverPackage */ + struct winspool_AsyncDeletePrinterDriverPackage *r2 = (struct winspool_AsyncDeletePrinterDriverPackage *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverPackage, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeletePrinterDriverPackage(p, r2); + break; + } + case 68: { /* winspool_AsyncReadPrinter */ + struct winspool_AsyncReadPrinter *r2 = (struct winspool_AsyncReadPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncReadPrinter, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pBuf = talloc_zero_array(r2, uint8_t, r2->in.cbBuf); + if (r2->out.pBuf == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.pcNoBytesRead = talloc_zero(r2, uint32_t); + if (r2->out.pcNoBytesRead == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncReadPrinter(p, r2); + break; + } + case 69: { /* winspool_AsyncResetPrinter */ + struct winspool_AsyncResetPrinter *r2 = (struct winspool_AsyncResetPrinter *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncResetPrinter, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncResetPrinter(p, r2); + break; + } + case 70: { /* winspool_AsyncGetJobNamedPropertyValue */ + struct winspool_AsyncGetJobNamedPropertyValue *r2 = (struct winspool_AsyncGetJobNamedPropertyValue *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJobNamedPropertyValue, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pValue = talloc_zero(r2, struct spoolss_PrintPropertyValue); + if (r2->out.pValue == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncGetJobNamedPropertyValue(p, r2); + break; + } + case 71: { /* winspool_AsyncSetJobNamedProperty */ + struct winspool_AsyncSetJobNamedProperty *r2 = (struct winspool_AsyncSetJobNamedProperty *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJobNamedProperty, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncSetJobNamedProperty(p, r2); + break; + } + case 72: { /* winspool_AsyncDeleteJobNamedProperty */ + struct winspool_AsyncDeleteJobNamedProperty *r2 = (struct winspool_AsyncDeleteJobNamedProperty *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteJobNamedProperty, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncDeleteJobNamedProperty(p, r2); + break; + } + case 73: { /* winspool_AsyncEnumJobNamedProperties */ + struct winspool_AsyncEnumJobNamedProperties *r2 = (struct winspool_AsyncEnumJobNamedProperties *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobNamedProperties, NDR_IN, r2); + } + NDR_ZERO_STRUCT(r2->out); + r2->out.pcProperties = talloc_zero(r2, uint32_t); + if (r2->out.pcProperties == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.ppProperties = talloc_zero(r2, struct spoolss_PrintNamedProperty *); + if (r2->out.ppProperties == NULL) { + status = NT_STATUS_NO_MEMORY; + p->fault_state = DCERPC_FAULT_CANT_PERFORM; + goto fail; + } + + r2->out.result = _winspool_AsyncEnumJobNamedProperties(p, r2); + break; + } + case 74: { /* winspool_AsyncLogJobInfoForBranchOffice */ + struct winspool_AsyncLogJobInfoForBranchOffice *r2 = (struct winspool_AsyncLogJobInfoForBranchOffice *)r; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncLogJobInfoForBranchOffice, NDR_IN, r2); + } + r2->out.result = _winspool_AsyncLogJobInfoForBranchOffice(p, r2); + break; + } + default: + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + break; + } + +fail: + /* Unimpersonate */ + if (impersonated) { + unbecome_authenticated_pipe_user(); + } + + p->dce_call = NULL; + p->mem_ctx = NULL; + /* Check pipes struct fault state */ + if (p->fault_state != 0) { + dce_call->fault_code = p->fault_state; + } + if (dce_call->fault_code != 0) { + status = NT_STATUS_NET_WRITE_FAULT; + } + + return status; +} + +NTSTATUS iremotewinspool__op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + return iremotewinspool__op_dispatch_internal(dce_call, mem_ctx, r, false); +} + +NTSTATUS iremotewinspool__op_reply(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + uint16_t opnum = dce_call->pkt.u.request.opnum; + + if (forward_opnum_to_spoolss(opnum)) { + return spoolss__op_reply(dce_call, mem_ctx, r); + } + + switch (opnum) { + case 0: { /* winspool_AsyncOpenPrinter */ + struct winspool_AsyncOpenPrinter *r2 = (struct winspool_AsyncOpenPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncOpenPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncOpenPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncOpenPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 1: { /* winspool_AsyncAddPrinter */ + struct winspool_AsyncAddPrinter *r2 = (struct winspool_AsyncAddPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 2: { /* winspool_AsyncSetJob */ + struct winspool_AsyncSetJob *r2 = (struct winspool_AsyncSetJob *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetJob replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJob, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 3: { /* winspool_AsyncGetJob */ + struct winspool_AsyncGetJob *r2 = (struct winspool_AsyncGetJob *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetJob replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJob, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 4: { /* winspool_AsyncEnumJobs */ + struct winspool_AsyncEnumJobs *r2 = (struct winspool_AsyncEnumJobs *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumJobs replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobs, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumJobs\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 5: { /* winspool_AsyncAddJob */ + struct winspool_AsyncAddJob *r2 = (struct winspool_AsyncAddJob *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddJob replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddJob, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 6: { /* winspool_AsyncScheduleJob */ + struct winspool_AsyncScheduleJob *r2 = (struct winspool_AsyncScheduleJob *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncScheduleJob replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncScheduleJob, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncScheduleJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 7: { /* winspool_AsyncDeletePrinter */ + struct winspool_AsyncDeletePrinter *r2 = (struct winspool_AsyncDeletePrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 8: { /* winspool_AsyncSetPrinter */ + struct winspool_AsyncSetPrinter *r2 = (struct winspool_AsyncSetPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 9: { /* winspool_AsyncGetPrinter */ + struct winspool_AsyncGetPrinter *r2 = (struct winspool_AsyncGetPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 10: { /* winspool_AsyncStartDocPrinter */ + struct winspool_AsyncStartDocPrinter *r2 = (struct winspool_AsyncStartDocPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncStartDocPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartDocPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncStartDocPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 11: { /* winspool_AsyncStartPagePrinter */ + struct winspool_AsyncStartPagePrinter *r2 = (struct winspool_AsyncStartPagePrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncStartPagePrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartPagePrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncStartPagePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 12: { /* winspool_AsyncWritePrinter */ + struct winspool_AsyncWritePrinter *r2 = (struct winspool_AsyncWritePrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncWritePrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncWritePrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncWritePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 13: { /* winspool_AsyncEndPagePrinter */ + struct winspool_AsyncEndPagePrinter *r2 = (struct winspool_AsyncEndPagePrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEndPagePrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndPagePrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEndPagePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 14: { /* winspool_AsyncEndDocPrinter */ + struct winspool_AsyncEndDocPrinter *r2 = (struct winspool_AsyncEndDocPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEndDocPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndDocPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEndDocPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 15: { /* winspool_AsyncAbortPrinter */ + struct winspool_AsyncAbortPrinter *r2 = (struct winspool_AsyncAbortPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAbortPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAbortPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAbortPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 16: { /* winspool_AsyncGetPrinterData */ + struct winspool_AsyncGetPrinterData *r2 = (struct winspool_AsyncGetPrinterData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinterData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 17: { /* winspool_AsyncGetPrinterDataEx */ + struct winspool_AsyncGetPrinterDataEx *r2 = (struct winspool_AsyncGetPrinterDataEx *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinterDataEx replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 18: { /* winspool_AsyncSetPrinterData */ + struct winspool_AsyncSetPrinterData *r2 = (struct winspool_AsyncSetPrinterData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetPrinterData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 19: { /* winspool_AsyncSetPrinterDataEx */ + struct winspool_AsyncSetPrinterDataEx *r2 = (struct winspool_AsyncSetPrinterDataEx *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetPrinterDataEx replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 20: { /* winspool_AsyncClosePrinter */ + struct winspool_AsyncClosePrinter *r2 = (struct winspool_AsyncClosePrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncClosePrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncClosePrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncClosePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 21: { /* winspool_AsyncAddForm */ + struct winspool_AsyncAddForm *r2 = (struct winspool_AsyncAddForm *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddForm replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddForm, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 22: { /* winspool_AsyncDeleteForm */ + struct winspool_AsyncDeleteForm *r2 = (struct winspool_AsyncDeleteForm *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeleteForm replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteForm, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 23: { /* winspool_AsyncGetForm */ + struct winspool_AsyncGetForm *r2 = (struct winspool_AsyncGetForm *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetForm replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetForm, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 24: { /* winspool_AsyncSetForm */ + struct winspool_AsyncSetForm *r2 = (struct winspool_AsyncSetForm *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetForm replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetForm, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 25: { /* winspool_AsyncEnumForms */ + struct winspool_AsyncEnumForms *r2 = (struct winspool_AsyncEnumForms *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumForms replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumForms, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumForms\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 26: { /* winspool_AsyncGetPrinterDriver */ + struct winspool_AsyncGetPrinterDriver *r2 = (struct winspool_AsyncGetPrinterDriver *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinterDriver replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriver, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 27: { /* winspool_AsyncEnumPrinterData */ + struct winspool_AsyncEnumPrinterData *r2 = (struct winspool_AsyncEnumPrinterData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrinterData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 28: { /* winspool_AsyncEnumPrinterDataEx */ + struct winspool_AsyncEnumPrinterDataEx *r2 = (struct winspool_AsyncEnumPrinterDataEx *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrinterDataEx replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 29: { /* winspool_AsyncEnumPrinterKey */ + struct winspool_AsyncEnumPrinterKey *r2 = (struct winspool_AsyncEnumPrinterKey *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrinterKey replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterKey, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterKey\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 30: { /* winspool_AsyncDeletePrinterData */ + struct winspool_AsyncDeletePrinterData *r2 = (struct winspool_AsyncDeletePrinterData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 31: { /* winspool_AsyncDeletePrinterDataEx */ + struct winspool_AsyncDeletePrinterDataEx *r2 = (struct winspool_AsyncDeletePrinterDataEx *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterDataEx replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 32: { /* winspool_AsyncDeletePrinterKey */ + struct winspool_AsyncDeletePrinterKey *r2 = (struct winspool_AsyncDeletePrinterKey *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterKey replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterKey, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterKey\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 33: { /* winspool_AsyncXcvData */ + struct winspool_AsyncXcvData *r2 = (struct winspool_AsyncXcvData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncXcvData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncXcvData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncXcvData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 34: { /* winspool_AsyncSendRecvBidiData */ + struct winspool_AsyncSendRecvBidiData *r2 = (struct winspool_AsyncSendRecvBidiData *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSendRecvBidiData replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSendRecvBidiData, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSendRecvBidiData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 35: { /* winspool_AsyncCreatePrinterIC */ + struct winspool_AsyncCreatePrinterIC *r2 = (struct winspool_AsyncCreatePrinterIC *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncCreatePrinterIC replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCreatePrinterIC, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncCreatePrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 36: { /* winspool_AsyncPlayGdiScriptOnPrinterIC */ + struct winspool_AsyncPlayGdiScriptOnPrinterIC *r2 = (struct winspool_AsyncPlayGdiScriptOnPrinterIC *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncPlayGdiScriptOnPrinterIC replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncPlayGdiScriptOnPrinterIC, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncPlayGdiScriptOnPrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 37: { /* winspool_AsyncDeletePrinterIC */ + struct winspool_AsyncDeletePrinterIC *r2 = (struct winspool_AsyncDeletePrinterIC *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterIC replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterIC, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 38: { /* winspool_AsyncEnumPrinters */ + struct winspool_AsyncEnumPrinters *r2 = (struct winspool_AsyncEnumPrinters *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrinters replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinters, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinters\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 39: { /* winspool_AsyncAddPrinterDriver */ + struct winspool_AsyncAddPrinterDriver *r2 = (struct winspool_AsyncAddPrinterDriver *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddPrinterDriver replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinterDriver, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 40: { /* winspool_AsyncEnumPrinterDrivers */ + struct winspool_AsyncEnumPrinterDrivers *r2 = (struct winspool_AsyncEnumPrinterDrivers *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrinterDrivers replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDrivers, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterDrivers\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 41: { /* winspool_AsyncGetPrinterDriverDirectory */ + struct winspool_AsyncGetPrinterDriverDirectory *r2 = (struct winspool_AsyncGetPrinterDriverDirectory *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinterDriverDirectory replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverDirectory, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriverDirectory\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 42: { /* winspool_AsyncDeletePrinterDriver */ + struct winspool_AsyncDeletePrinterDriver *r2 = (struct winspool_AsyncDeletePrinterDriver *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterDriver replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriver, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 43: { /* winspool_AsyncDeletePrinterDriverEx */ + struct winspool_AsyncDeletePrinterDriverEx *r2 = (struct winspool_AsyncDeletePrinterDriverEx *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterDriverEx replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverEx, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriverEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 44: { /* winspool_AsyncAddPrintProcessor */ + struct winspool_AsyncAddPrintProcessor *r2 = (struct winspool_AsyncAddPrintProcessor *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddPrintProcessor replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrintProcessor, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrintProcessor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 45: { /* winspool_AsyncEnumPrintProcessors */ + struct winspool_AsyncEnumPrintProcessors *r2 = (struct winspool_AsyncEnumPrintProcessors *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrintProcessors replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessors, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrintProcessors\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 46: { /* winspool_AsyncGetPrintProcessorDirectory */ + struct winspool_AsyncGetPrintProcessorDirectory *r2 = (struct winspool_AsyncGetPrintProcessorDirectory *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrintProcessorDirectory replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrintProcessorDirectory, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrintProcessorDirectory\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 47: { /* winspool_AsyncEnumPorts */ + struct winspool_AsyncEnumPorts *r2 = (struct winspool_AsyncEnumPorts *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPorts replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPorts, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPorts\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 48: { /* winspool_AsyncEnumMonitors */ + struct winspool_AsyncEnumMonitors *r2 = (struct winspool_AsyncEnumMonitors *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumMonitors replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumMonitors, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumMonitors\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 49: { /* winspool_AsyncAddPort */ + struct winspool_AsyncAddPort *r2 = (struct winspool_AsyncAddPort *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddPort replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPort, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPort\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 50: { /* winspool_AsyncSetPort */ + struct winspool_AsyncSetPort *r2 = (struct winspool_AsyncSetPort *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetPort replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPort, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPort\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 51: { /* winspool_AsyncAddMonitor */ + struct winspool_AsyncAddMonitor *r2 = (struct winspool_AsyncAddMonitor *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddMonitor replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddMonitor, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddMonitor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 52: { /* winspool_AsyncDeleteMonitor */ + struct winspool_AsyncDeleteMonitor *r2 = (struct winspool_AsyncDeleteMonitor *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeleteMonitor replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteMonitor, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteMonitor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 53: { /* winspool_AsyncDeletePrintProcessor */ + struct winspool_AsyncDeletePrintProcessor *r2 = (struct winspool_AsyncDeletePrintProcessor *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrintProcessor replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrintProcessor, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrintProcessor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 54: { /* winspool_AsyncEnumPrintProcessorDatatypes */ + struct winspool_AsyncEnumPrintProcessorDatatypes *r2 = (struct winspool_AsyncEnumPrintProcessorDatatypes *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPrintProcessorDatatypes replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessorDatatypes, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrintProcessorDatatypes\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 55: { /* winspool_AsyncAddPerMachineConnection */ + struct winspool_AsyncAddPerMachineConnection *r2 = (struct winspool_AsyncAddPerMachineConnection *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncAddPerMachineConnection replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPerMachineConnection, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPerMachineConnection\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 56: { /* winspool_AsyncDeletePerMachineConnection */ + struct winspool_AsyncDeletePerMachineConnection *r2 = (struct winspool_AsyncDeletePerMachineConnection *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePerMachineConnection replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePerMachineConnection, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePerMachineConnection\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 57: { /* winspool_AsyncEnumPerMachineConnections */ + struct winspool_AsyncEnumPerMachineConnections *r2 = (struct winspool_AsyncEnumPerMachineConnections *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumPerMachineConnections replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPerMachineConnections, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPerMachineConnections\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 58: { /* winspool_SyncRegisterForRemoteNotifications */ + struct winspool_SyncRegisterForRemoteNotifications *r2 = (struct winspool_SyncRegisterForRemoteNotifications *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_SyncRegisterForRemoteNotifications replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRegisterForRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_SyncRegisterForRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 59: { /* winspool_SyncUnRegisterForRemoteNotifications */ + struct winspool_SyncUnRegisterForRemoteNotifications *r2 = (struct winspool_SyncUnRegisterForRemoteNotifications *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_SyncUnRegisterForRemoteNotifications replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncUnRegisterForRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_SyncUnRegisterForRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 60: { /* winspool_SyncRefreshRemoteNotifications */ + struct winspool_SyncRefreshRemoteNotifications *r2 = (struct winspool_SyncRefreshRemoteNotifications *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_SyncRefreshRemoteNotifications replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRefreshRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_SyncRefreshRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 61: { /* winspool_AsyncGetRemoteNotifications */ + struct winspool_AsyncGetRemoteNotifications *r2 = (struct winspool_AsyncGetRemoteNotifications *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetRemoteNotifications replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 62: { /* winspool_AsyncInstallPrinterDriverFromPackage */ + struct winspool_AsyncInstallPrinterDriverFromPackage *r2 = (struct winspool_AsyncInstallPrinterDriverFromPackage *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncInstallPrinterDriverFromPackage replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncInstallPrinterDriverFromPackage, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncInstallPrinterDriverFromPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 63: { /* winspool_AsyncUploadPrinterDriverPackage */ + struct winspool_AsyncUploadPrinterDriverPackage *r2 = (struct winspool_AsyncUploadPrinterDriverPackage *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncUploadPrinterDriverPackage replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncUploadPrinterDriverPackage, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncUploadPrinterDriverPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 64: { /* winspool_AsyncGetCorePrinterDrivers */ + struct winspool_AsyncGetCorePrinterDrivers *r2 = (struct winspool_AsyncGetCorePrinterDrivers *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetCorePrinterDrivers replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetCorePrinterDrivers, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetCorePrinterDrivers\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 65: { /* winspool_AsyncCorePrinterDriverInstalled */ + struct winspool_AsyncCorePrinterDriverInstalled *r2 = (struct winspool_AsyncCorePrinterDriverInstalled *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncCorePrinterDriverInstalled replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCorePrinterDriverInstalled, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncCorePrinterDriverInstalled\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 66: { /* winspool_AsyncGetPrinterDriverPackagePath */ + struct winspool_AsyncGetPrinterDriverPackagePath *r2 = (struct winspool_AsyncGetPrinterDriverPackagePath *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetPrinterDriverPackagePath replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverPackagePath, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriverPackagePath\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 67: { /* winspool_AsyncDeletePrinterDriverPackage */ + struct winspool_AsyncDeletePrinterDriverPackage *r2 = (struct winspool_AsyncDeletePrinterDriverPackage *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeletePrinterDriverPackage replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverPackage, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriverPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 68: { /* winspool_AsyncReadPrinter */ + struct winspool_AsyncReadPrinter *r2 = (struct winspool_AsyncReadPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncReadPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncReadPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncReadPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 69: { /* winspool_AsyncResetPrinter */ + struct winspool_AsyncResetPrinter *r2 = (struct winspool_AsyncResetPrinter *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncResetPrinter replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncResetPrinter, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncResetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 70: { /* winspool_AsyncGetJobNamedPropertyValue */ + struct winspool_AsyncGetJobNamedPropertyValue *r2 = (struct winspool_AsyncGetJobNamedPropertyValue *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncGetJobNamedPropertyValue replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJobNamedPropertyValue, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetJobNamedPropertyValue\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 71: { /* winspool_AsyncSetJobNamedProperty */ + struct winspool_AsyncSetJobNamedProperty *r2 = (struct winspool_AsyncSetJobNamedProperty *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncSetJobNamedProperty replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJobNamedProperty, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetJobNamedProperty\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 72: { /* winspool_AsyncDeleteJobNamedProperty */ + struct winspool_AsyncDeleteJobNamedProperty *r2 = (struct winspool_AsyncDeleteJobNamedProperty *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncDeleteJobNamedProperty replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteJobNamedProperty, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteJobNamedProperty\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 73: { /* winspool_AsyncEnumJobNamedProperties */ + struct winspool_AsyncEnumJobNamedProperties *r2 = (struct winspool_AsyncEnumJobNamedProperties *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncEnumJobNamedProperties replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobNamedProperties, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumJobNamedProperties\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + case 74: { /* winspool_AsyncLogJobInfoForBranchOffice */ + struct winspool_AsyncLogJobInfoForBranchOffice *r2 = (struct winspool_AsyncLogJobInfoForBranchOffice *)r; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + DEBUG(5,("function winspool_AsyncLogJobInfoForBranchOffice replied async\n")); + } + if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) { + NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncLogJobInfoForBranchOffice, NDR_OUT | NDR_SET_VALUES, r2); + } + if (dce_call->fault_code != 0) { + DBG_WARNING("dcerpc_fault %s in winspool_AsyncLogJobInfoForBranchOffice\n", dcerpc_errstr(mem_ctx, dce_call->fault_code)); + } + break; + } + default: + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + break; + } + + if (dce_call->fault_code != 0) { + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +NTSTATUS iremotewinspool__op_ndr_push(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_push *push, const void *r) +{ + enum ndr_err_code ndr_err; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + if (forward_opnum_to_spoolss(opnum)) { + return spoolss__op_ndr_push(dce_call, mem_ctx, push, r); + } + + ndr_err = ndr_table_iremotewinspool.calls[opnum].ndr_push(push, NDR_OUT, r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +NTSTATUS iremotewinspool__op_local(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + return iremotewinspool__op_dispatch_internal(dce_call, mem_ctx, r, true); +} + +static const struct dcesrv_interface dcesrv_iremotewinspool_interface = { + .name = "iremotewinspool", + .syntax_id = {{0x76f03f96,0xcdfd,0x44fc,{0xa2,0x2c},{0x64,0x95,0x0a,0x00,0x12,0x09}},1.0}, + .bind = iremotewinspool__op_bind, + .unbind = iremotewinspool__op_unbind, + .ndr_pull = iremotewinspool__op_ndr_pull, + .dispatch = iremotewinspool__op_dispatch, + .reply = iremotewinspool__op_reply, + .ndr_push = iremotewinspool__op_ndr_push, + .local = iremotewinspool__op_local, +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_FLAGS + .flags = DCESRV_INTERFACE_IREMOTEWINSPOOL_FLAGS +#else + .flags = 0 +#endif +}; + +static NTSTATUS iremotewinspool__op_init_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server) +{ + int i; + NTSTATUS ret; + +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_NCACN_NP_SECONDARY_ENDPOINT + const char *ncacn_np_secondary_endpoint = DCESRV_INTERFACE_IREMOTEWINSPOOL_NCACN_NP_SECONDARY_ENDPOINT; +#else + const char *ncacn_np_secondary_endpoint = NULL; +#endif + + for (i=0;i<ndr_table_iremotewinspool.endpoints->count;i++) { + const char *name = ndr_table_iremotewinspool.endpoints->names[i]; + + ret = dcesrv_interface_register(dce_ctx, name, ncacn_np_secondary_endpoint, &dcesrv_iremotewinspool_interface, NULL); + if (!NT_STATUS_IS_OK(ret)) { + DBG_ERR("Failed to register endpoint '%s'\n",name); + return ret; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS iremotewinspool__op_shutdown_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server) +{ + return NT_STATUS_OK; +} + +static bool iremotewinspool__op_interface_by_uuid(struct dcesrv_interface *iface, const struct GUID *uuid, uint32_t if_version) +{ + if (dcesrv_iremotewinspool_interface.syntax_id.if_version == if_version && GUID_equal(&dcesrv_iremotewinspool_interface.syntax_id.uuid, uuid)) { + memcpy(iface,&dcesrv_iremotewinspool_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static bool iremotewinspool__op_interface_by_name(struct dcesrv_interface *iface, const char *name) +{ + if (strcmp(dcesrv_iremotewinspool_interface.name, name)==0) { + memcpy(iface, &dcesrv_iremotewinspool_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static const struct dcesrv_endpoint_server iremotewinspool_ep_server = { + /* fill in our name */ + .name = "iremotewinspool", + + /* Initialization flag */ + .initialized = false, + + /* fill in all the operations */ +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_INIT_SERVER + .init_server = DCESRV_INTERFACE_IREMOTEWINSPOOL_INIT_SERVER, +#else + .init_server = iremotewinspool__op_init_server, +#endif +#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_SHUTDOWN_SERVER + .shutdown_server = DCESRV_INTERFACE_IREMOTEWINSPOOL_SHUTDOWN_SERVER, +#else + .shutdown_server = iremotewinspool__op_shutdown_server, +#endif + .interface_by_uuid = iremotewinspool__op_interface_by_uuid, + .interface_by_name = iremotewinspool__op_interface_by_name +}; + +const struct dcesrv_endpoint_server *iremotewinspool_get_ep_server(void) +{ + return &iremotewinspool_ep_server; +} diff --git a/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c b/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c new file mode 100644 index 0000000..c437192 --- /dev/null +++ b/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c @@ -0,0 +1,924 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the iremotewinspool pipe + + Copyright (C) YOUR NAME HERE YEAR + + 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 "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_winspool_scompat.h" + +/**************************************************************** + _winspool_AsyncOpenPrinter +****************************************************************/ + +WERROR _winspool_AsyncOpenPrinter(struct pipes_struct *p, + struct winspool_AsyncOpenPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddPrinter +****************************************************************/ + +WERROR _winspool_AsyncAddPrinter(struct pipes_struct *p, + struct winspool_AsyncAddPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetJob +****************************************************************/ + +WERROR _winspool_AsyncSetJob(struct pipes_struct *p, + struct winspool_AsyncSetJob *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetJob +****************************************************************/ + +WERROR _winspool_AsyncGetJob(struct pipes_struct *p, + struct winspool_AsyncGetJob *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumJobs +****************************************************************/ + +WERROR _winspool_AsyncEnumJobs(struct pipes_struct *p, + struct winspool_AsyncEnumJobs *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddJob +****************************************************************/ + +WERROR _winspool_AsyncAddJob(struct pipes_struct *p, + struct winspool_AsyncAddJob *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncScheduleJob +****************************************************************/ + +WERROR _winspool_AsyncScheduleJob(struct pipes_struct *p, + struct winspool_AsyncScheduleJob *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinter +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinter(struct pipes_struct *p, + struct winspool_AsyncDeletePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetPrinter +****************************************************************/ + +WERROR _winspool_AsyncSetPrinter(struct pipes_struct *p, + struct winspool_AsyncSetPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinter +****************************************************************/ + +WERROR _winspool_AsyncGetPrinter(struct pipes_struct *p, + struct winspool_AsyncGetPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncStartDocPrinter +****************************************************************/ + +WERROR _winspool_AsyncStartDocPrinter(struct pipes_struct *p, + struct winspool_AsyncStartDocPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncStartPagePrinter +****************************************************************/ + +WERROR _winspool_AsyncStartPagePrinter(struct pipes_struct *p, + struct winspool_AsyncStartPagePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncWritePrinter +****************************************************************/ + +WERROR _winspool_AsyncWritePrinter(struct pipes_struct *p, + struct winspool_AsyncWritePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEndPagePrinter +****************************************************************/ + +WERROR _winspool_AsyncEndPagePrinter(struct pipes_struct *p, + struct winspool_AsyncEndPagePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEndDocPrinter +****************************************************************/ + +WERROR _winspool_AsyncEndDocPrinter(struct pipes_struct *p, + struct winspool_AsyncEndDocPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAbortPrinter +****************************************************************/ + +WERROR _winspool_AsyncAbortPrinter(struct pipes_struct *p, + struct winspool_AsyncAbortPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinterData +****************************************************************/ + +WERROR _winspool_AsyncGetPrinterData(struct pipes_struct *p, + struct winspool_AsyncGetPrinterData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinterDataEx +****************************************************************/ + +WERROR _winspool_AsyncGetPrinterDataEx(struct pipes_struct *p, + struct winspool_AsyncGetPrinterDataEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetPrinterData +****************************************************************/ + +WERROR _winspool_AsyncSetPrinterData(struct pipes_struct *p, + struct winspool_AsyncSetPrinterData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetPrinterDataEx +****************************************************************/ + +WERROR _winspool_AsyncSetPrinterDataEx(struct pipes_struct *p, + struct winspool_AsyncSetPrinterDataEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncClosePrinter +****************************************************************/ + +WERROR _winspool_AsyncClosePrinter(struct pipes_struct *p, + struct winspool_AsyncClosePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddForm +****************************************************************/ + +WERROR _winspool_AsyncAddForm(struct pipes_struct *p, + struct winspool_AsyncAddForm *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeleteForm +****************************************************************/ + +WERROR _winspool_AsyncDeleteForm(struct pipes_struct *p, + struct winspool_AsyncDeleteForm *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetForm +****************************************************************/ + +WERROR _winspool_AsyncGetForm(struct pipes_struct *p, + struct winspool_AsyncGetForm *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetForm +****************************************************************/ + +WERROR _winspool_AsyncSetForm(struct pipes_struct *p, + struct winspool_AsyncSetForm *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumForms +****************************************************************/ + +WERROR _winspool_AsyncEnumForms(struct pipes_struct *p, + struct winspool_AsyncEnumForms *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinterDriver +****************************************************************/ + +WERROR _winspool_AsyncGetPrinterDriver(struct pipes_struct *p, + struct winspool_AsyncGetPrinterDriver *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrinterData +****************************************************************/ + +WERROR _winspool_AsyncEnumPrinterData(struct pipes_struct *p, + struct winspool_AsyncEnumPrinterData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrinterDataEx +****************************************************************/ + +WERROR _winspool_AsyncEnumPrinterDataEx(struct pipes_struct *p, + struct winspool_AsyncEnumPrinterDataEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrinterKey +****************************************************************/ + +WERROR _winspool_AsyncEnumPrinterKey(struct pipes_struct *p, + struct winspool_AsyncEnumPrinterKey *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterData +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterData(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterDataEx +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterDataEx(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterDataEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterKey +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterKey(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterKey *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncXcvData +****************************************************************/ + +WERROR _winspool_AsyncXcvData(struct pipes_struct *p, + struct winspool_AsyncXcvData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSendRecvBidiData +****************************************************************/ + +WERROR _winspool_AsyncSendRecvBidiData(struct pipes_struct *p, + struct winspool_AsyncSendRecvBidiData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncCreatePrinterIC +****************************************************************/ + +WERROR _winspool_AsyncCreatePrinterIC(struct pipes_struct *p, + struct winspool_AsyncCreatePrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncPlayGdiScriptOnPrinterIC +****************************************************************/ + +WERROR _winspool_AsyncPlayGdiScriptOnPrinterIC(struct pipes_struct *p, + struct winspool_AsyncPlayGdiScriptOnPrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterIC +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterIC(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrinters +****************************************************************/ + +WERROR _winspool_AsyncEnumPrinters(struct pipes_struct *p, + struct winspool_AsyncEnumPrinters *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddPrinterDriver +****************************************************************/ + +WERROR _winspool_AsyncAddPrinterDriver(struct pipes_struct *p, + struct winspool_AsyncAddPrinterDriver *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrinterDrivers +****************************************************************/ + +WERROR _winspool_AsyncEnumPrinterDrivers(struct pipes_struct *p, + struct winspool_AsyncEnumPrinterDrivers *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinterDriverDirectory +****************************************************************/ + +WERROR _winspool_AsyncGetPrinterDriverDirectory(struct pipes_struct *p, + struct winspool_AsyncGetPrinterDriverDirectory *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterDriver +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterDriver(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterDriver *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterDriverEx +****************************************************************/ + +WERROR _winspool_AsyncDeletePrinterDriverEx(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterDriverEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddPrintProcessor +****************************************************************/ + +WERROR _winspool_AsyncAddPrintProcessor(struct pipes_struct *p, + struct winspool_AsyncAddPrintProcessor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrintProcessors +****************************************************************/ + +WERROR _winspool_AsyncEnumPrintProcessors(struct pipes_struct *p, + struct winspool_AsyncEnumPrintProcessors *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrintProcessorDirectory +****************************************************************/ + +WERROR _winspool_AsyncGetPrintProcessorDirectory(struct pipes_struct *p, + struct winspool_AsyncGetPrintProcessorDirectory *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPorts +****************************************************************/ + +WERROR _winspool_AsyncEnumPorts(struct pipes_struct *p, + struct winspool_AsyncEnumPorts *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumMonitors +****************************************************************/ + +WERROR _winspool_AsyncEnumMonitors(struct pipes_struct *p, + struct winspool_AsyncEnumMonitors *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddPort +****************************************************************/ + +WERROR _winspool_AsyncAddPort(struct pipes_struct *p, + struct winspool_AsyncAddPort *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetPort +****************************************************************/ + +WERROR _winspool_AsyncSetPort(struct pipes_struct *p, + struct winspool_AsyncSetPort *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddMonitor +****************************************************************/ + +WERROR _winspool_AsyncAddMonitor(struct pipes_struct *p, + struct winspool_AsyncAddMonitor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeleteMonitor +****************************************************************/ + +WERROR _winspool_AsyncDeleteMonitor(struct pipes_struct *p, + struct winspool_AsyncDeleteMonitor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrintProcessor +****************************************************************/ + +WERROR _winspool_AsyncDeletePrintProcessor(struct pipes_struct *p, + struct winspool_AsyncDeletePrintProcessor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPrintProcessorDatatypes +****************************************************************/ + +WERROR _winspool_AsyncEnumPrintProcessorDatatypes(struct pipes_struct *p, + struct winspool_AsyncEnumPrintProcessorDatatypes *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncAddPerMachineConnection +****************************************************************/ + +WERROR _winspool_AsyncAddPerMachineConnection(struct pipes_struct *p, + struct winspool_AsyncAddPerMachineConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePerMachineConnection +****************************************************************/ + +WERROR _winspool_AsyncDeletePerMachineConnection(struct pipes_struct *p, + struct winspool_AsyncDeletePerMachineConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumPerMachineConnections +****************************************************************/ + +WERROR _winspool_AsyncEnumPerMachineConnections(struct pipes_struct *p, + struct winspool_AsyncEnumPerMachineConnections *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_SyncRegisterForRemoteNotifications +****************************************************************/ + +HRESULT _winspool_SyncRegisterForRemoteNotifications(struct pipes_struct *p, + struct winspool_SyncRegisterForRemoteNotifications *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_SyncUnRegisterForRemoteNotifications +****************************************************************/ + +HRESULT _winspool_SyncUnRegisterForRemoteNotifications(struct pipes_struct *p, + struct winspool_SyncUnRegisterForRemoteNotifications *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_SyncRefreshRemoteNotifications +****************************************************************/ + +HRESULT _winspool_SyncRefreshRemoteNotifications(struct pipes_struct *p, + struct winspool_SyncRefreshRemoteNotifications *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetRemoteNotifications +****************************************************************/ + +HRESULT _winspool_AsyncGetRemoteNotifications(struct pipes_struct *p, + struct winspool_AsyncGetRemoteNotifications *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncInstallPrinterDriverFromPackage +****************************************************************/ + +HRESULT _winspool_AsyncInstallPrinterDriverFromPackage(struct pipes_struct *p, + struct winspool_AsyncInstallPrinterDriverFromPackage *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncUploadPrinterDriverPackage +****************************************************************/ + +HRESULT _winspool_AsyncUploadPrinterDriverPackage(struct pipes_struct *p, + struct winspool_AsyncUploadPrinterDriverPackage *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetCorePrinterDrivers +****************************************************************/ + +HRESULT _winspool_AsyncGetCorePrinterDrivers(struct pipes_struct *p, + struct winspool_AsyncGetCorePrinterDrivers *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncCorePrinterDriverInstalled +****************************************************************/ + +HRESULT _winspool_AsyncCorePrinterDriverInstalled(struct pipes_struct *p, + struct winspool_AsyncCorePrinterDriverInstalled *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetPrinterDriverPackagePath +****************************************************************/ + +HRESULT _winspool_AsyncGetPrinterDriverPackagePath(struct pipes_struct *p, + struct winspool_AsyncGetPrinterDriverPackagePath *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeletePrinterDriverPackage +****************************************************************/ + +HRESULT _winspool_AsyncDeletePrinterDriverPackage(struct pipes_struct *p, + struct winspool_AsyncDeletePrinterDriverPackage *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncReadPrinter +****************************************************************/ + +WERROR _winspool_AsyncReadPrinter(struct pipes_struct *p, + struct winspool_AsyncReadPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncResetPrinter +****************************************************************/ + +WERROR _winspool_AsyncResetPrinter(struct pipes_struct *p, + struct winspool_AsyncResetPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncGetJobNamedPropertyValue +****************************************************************/ + +WERROR _winspool_AsyncGetJobNamedPropertyValue(struct pipes_struct *p, + struct winspool_AsyncGetJobNamedPropertyValue *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncSetJobNamedProperty +****************************************************************/ + +WERROR _winspool_AsyncSetJobNamedProperty(struct pipes_struct *p, + struct winspool_AsyncSetJobNamedProperty *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncDeleteJobNamedProperty +****************************************************************/ + +WERROR _winspool_AsyncDeleteJobNamedProperty(struct pipes_struct *p, + struct winspool_AsyncDeleteJobNamedProperty *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncEnumJobNamedProperties +****************************************************************/ + +WERROR _winspool_AsyncEnumJobNamedProperties(struct pipes_struct *p, + struct winspool_AsyncEnumJobNamedProperties *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _winspool_AsyncLogJobInfoForBranchOffice +****************************************************************/ + +WERROR _winspool_AsyncLogJobInfoForBranchOffice(struct pipes_struct *p, + struct winspool_AsyncLogJobInfoForBranchOffice *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} diff --git a/source3/rpc_server/spoolss/srv_spoolss_handle.h b/source3/rpc_server/spoolss/srv_spoolss_handle.h new file mode 100644 index 0000000..e84037c --- /dev/null +++ b/source3/rpc_server/spoolss/srv_spoolss_handle.h @@ -0,0 +1,77 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-2000, + * Copyright (C) Luke Kenneth Casson Leighton 1996-2000, + * Copyright (C) Jean François Micouleau 1998-2000, + * Copyright (C) Jeremy Allison 2001-2002, + * Copyright (C) Gerald Carter 2000-2004, + * Copyright (C) Tim Potter 2001-2002. + * Copyright (C) Guenther Deschner 2009-2010. + * Copyright (C) Andreas Schneider 2010. + * + * 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/>. + */ + +/* Since the SPOOLSS rpc routines are basically DOS 16-bit calls wrapped + up, all the errors returned are DOS errors, not NT status codes. */ + +#include "includes.h" +#include "../librpc/gen_ndr/spoolss.h" + +struct notify_back_channel; + +#define SPLHND_PRINTER 1 +#define SPLHND_SERVER 2 +#define SPLHND_PORTMON_TCP 3 +#define SPLHND_PORTMON_LOCAL 4 + +/* structure to store the printer handles */ +/* and a reference to what it's pointing to */ +/* and the notify info asked about */ +/* that's the central struct */ +struct printer_handle { + struct printer_handle *prev, *next; + bool document_started; + bool page_started; + uint32_t jobid; /* jobid in printing backend */ + int printer_type; + const char *servername; + fstring sharename; + uint32_t access_granted; + struct { + uint32_t flags; + uint32_t options; + fstring localmachine; + uint32_t printerlocal; + struct spoolss_NotifyOption *option; + struct policy_handle cli_hnd; + struct notify_back_channel *cli_chan; + uint32_t change; + /* are we in a FindNextPrinterChangeNotify() call? */ + bool fnpcn; + struct messaging_context *msg_ctx; + } notify; + struct { + fstring machine; + fstring user; + } client; + + /* devmode sent in the OpenPrinter() call */ + struct spoolss_DeviceMode *devmode; + + /* TODO cache the printer info2 structure */ + struct spoolss_PrinterInfo2 *info2; + +}; diff --git a/source3/rpc_server/spoolss/srv_spoolss_nt.c b/source3/rpc_server/spoolss/srv_spoolss_nt.c new file mode 100644 index 0000000..16b8b41 --- /dev/null +++ b/source3/rpc_server/spoolss/srv_spoolss_nt.c @@ -0,0 +1,11622 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-2000, + * Copyright (C) Luke Kenneth Casson Leighton 1996-2000, + * Copyright (C) Jean François Micouleau 1998-2000, + * Copyright (C) Jeremy Allison 2001-2002, + * Copyright (C) Gerald Carter 2000-2004, + * Copyright (C) Tim Potter 2001-2002. + * Copyright (C) Guenther Deschner 2009-2010. + * Copyright (C) Andreas Schneider 2010. + * + * 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/>. + */ + +/* Since the SPOOLSS rpc routines are basically DOS 16-bit calls wrapped + up, all the errors returned are DOS errors, not NT status codes. */ + +#include "includes.h" +#include "libsmb/namequery.h" +#include "ntdomain.h" +#include "nt_printing.h" +#include "srv_spoolss_util.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "librpc/gen_ndr/ndr_spoolss_scompat.h" +#include "../librpc/gen_ndr/ndr_spoolss_c.h" +#include "rpc_client/init_spoolss.h" +#include "rpc_client/cli_pipe.h" +#include "../libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "registry.h" +#include "include/printing.h" +#include "secrets.h" +#include "../librpc/gen_ndr/netlogon.h" +#include "rpc_misc.h" +#include "printing/notify.h" +#include "serverid.h" +#include "../libcli/registry/util_reg.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "auth.h" +#include "messages.h" +#include "rpc_server/spoolss/srv_spoolss_nt.h" +#include "util_tdb.h" +#include "libsmb/libsmb.h" +#include "printing/printer_list.h" +#include "../lib/tsocket/tsocket.h" +#include "rpc_client/cli_winreg_spoolss.h" +#include "../libcli/smb/smbXcli_base.h" +#include "rpc_server/spoolss/srv_spoolss_handle.h" +#include "lib/gencache.h" +#include "rpc_server/rpc_server.h" +#include "librpc/rpc/dcesrv_core.h" +#include "printing/nt_printing_migrate_internal.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" + +/* macros stolen from s4 spoolss server */ +#define SPOOLSS_BUFFER_UNION(fn,info,level) \ + ((info)?ndr_size_##fn(info, level, 0):0) + +#define SPOOLSS_BUFFER_UNION_ARRAY(mem_ctx,fn,info,level,count) \ + ((info)?ndr_size_##fn##_info(mem_ctx, level, count, info):0) + +#define SPOOLSS_BUFFER_ARRAY(mem_ctx,fn,info,count) \ + ((info)?ndr_size_##fn##_info(mem_ctx, count, info):0) + +#define SPOOLSS_BUFFER_OK(val_true,val_false) ((r->in.offered >= *r->out.needed)?val_true:val_false) + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#ifndef MAX_OPEN_PRINTER_EXS +#define MAX_OPEN_PRINTER_EXS 50 +#endif + +#define GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT 5 +#define GLOBAL_SPOOLSS_OS_MINOR_DEFAULT 2 +#define GLOBAL_SPOOLSS_OS_BUILD_DEFAULT 3790 +#define GLOBAL_SPOOLSS_ARCHITECTURE SPOOLSS_ARCHITECTURE_x64 + +static struct printer_handle *printers_list; + +struct printer_session_counter { + struct printer_session_counter *next; + struct printer_session_counter *prev; + + int snum; + uint32_t counter; +}; + +static struct printer_session_counter *counter_list; + +struct notify_back_channel { + struct notify_back_channel *prev, *next; + + /* associated client */ + struct sockaddr_storage client_address; + + /* print notify back-channel pipe handle*/ + struct rpc_pipe_client *cli_pipe; + struct cli_state *cli; + uint32_t active_connections; +}; + +static struct notify_back_channel *back_channels; + +/* Map generic permissions to printer object specific permissions */ + +const struct standard_mapping printer_std_mapping = { + PRINTER_READ, + PRINTER_WRITE, + PRINTER_EXECUTE, + PRINTER_ALL_ACCESS +}; + +/* Map generic permissions to print server object specific permissions */ + +const struct standard_mapping printserver_std_mapping = { + SERVER_READ, + SERVER_WRITE, + SERVER_EXECUTE, + SERVER_ALL_ACCESS +}; + +/* API table for Xcv Monitor functions */ + +struct xcv_api_table { + const char *name; + WERROR(*fn) (TALLOC_CTX *mem_ctx, struct security_token *token, DATA_BLOB *in, DATA_BLOB *out, uint32_t *needed); +}; + +static void prune_printername_cache(void); + +/******************************************************************** + * Canonicalize servername. + ********************************************************************/ + +static const char *canon_servername(const char *servername) +{ + const char *pservername = servername; + + if (servername == NULL) { + return ""; + } + + while (*pservername == '\\') { + pservername++; + } + return pservername; +} + +/* translate between internal status numbers and NT status numbers */ +static int nt_printj_status(int v) +{ + switch (v) { + case LPQ_QUEUED: + return 0; + case LPQ_PAUSED: + return JOB_STATUS_PAUSED; + case LPQ_SPOOLING: + return JOB_STATUS_SPOOLING; + case LPQ_PRINTING: + return JOB_STATUS_PRINTING; + case LPQ_ERROR: + return JOB_STATUS_ERROR; + case LPQ_DELETING: + return JOB_STATUS_DELETING; + case LPQ_OFFLINE: + return JOB_STATUS_OFFLINE; + case LPQ_PAPEROUT: + return JOB_STATUS_PAPEROUT; + case LPQ_PRINTED: + return JOB_STATUS_PRINTED; + case LPQ_DELETED: + return JOB_STATUS_DELETED; + case LPQ_BLOCKED: + return JOB_STATUS_BLOCKED_DEVQ; + case LPQ_USER_INTERVENTION: + return JOB_STATUS_USER_INTERVENTION; + } + return 0; +} + +static int nt_printq_status(int v) +{ + switch (v) { + case LPQ_PAUSED: + return PRINTER_STATUS_PAUSED; + case LPQ_QUEUED: + case LPQ_SPOOLING: + case LPQ_PRINTING: + return 0; + } + return 0; +} + +/*************************************************************************** + Disconnect from the client +****************************************************************************/ + +static void srv_spoolss_replycloseprinter(int snum, + struct printer_handle *prn_hnd) +{ + WERROR result; + NTSTATUS status; + + /* + * Tell the specific printing tdb we no longer want messages for this printer + * by deregistering our PID. + */ + + if (!print_notify_deregister_pid(snum)) { + DEBUG(0, ("Failed to register our pid for printer %s\n", + lp_const_servicename(snum))); + } + + /* weird if the test succeeds !!! */ + if (prn_hnd->notify.cli_chan == NULL || + prn_hnd->notify.cli_chan->cli_pipe == NULL || + prn_hnd->notify.cli_chan->cli_pipe->binding_handle == NULL || + prn_hnd->notify.cli_chan->active_connections == 0) { + DEBUG(0, ("Trying to close unexisting backchannel!\n")); + DLIST_REMOVE(back_channels, prn_hnd->notify.cli_chan); + TALLOC_FREE(prn_hnd->notify.cli_chan); + return; + } + + status = dcerpc_spoolss_ReplyClosePrinter( + prn_hnd->notify.cli_chan->cli_pipe->binding_handle, + talloc_tos(), + &prn_hnd->notify.cli_hnd, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("dcerpc_spoolss_ReplyClosePrinter failed [%s].\n", + nt_errstr(status))); + result = ntstatus_to_werror(status); + } else if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("reply_close_printer failed [%s].\n", + win_errstr(result))); + } + + /* if it's the last connection, deconnect the IPC$ share */ + if (prn_hnd->notify.cli_chan->active_connections == 1) { + + cli_shutdown(prn_hnd->notify.cli_chan->cli); + DLIST_REMOVE(back_channels, prn_hnd->notify.cli_chan); + TALLOC_FREE(prn_hnd->notify.cli_chan); + + if (prn_hnd->notify.msg_ctx != NULL) { + messaging_deregister(prn_hnd->notify.msg_ctx, + MSG_PRINTER_NOTIFY2, NULL); + } + } + + if (prn_hnd->notify.cli_chan) { + prn_hnd->notify.cli_chan->active_connections--; + prn_hnd->notify.cli_chan = NULL; + } +} + +/**************************************************************************** + Functions to free a printer entry datastruct. +****************************************************************************/ + +static int printer_entry_destructor(struct printer_handle *Printer) +{ + if (Printer->notify.cli_chan != NULL && + Printer->notify.cli_chan->active_connections > 0) { + int snum = -1; + + switch(Printer->printer_type) { + case SPLHND_SERVER: + srv_spoolss_replycloseprinter(snum, Printer); + break; + + case SPLHND_PRINTER: + snum = print_queue_snum(Printer->sharename); + if (snum != -1) { + srv_spoolss_replycloseprinter(snum, Printer); + } + break; + default: + break; + } + } + + Printer->notify.flags=0; + Printer->notify.options=0; + Printer->notify.localmachine[0]='\0'; + Printer->notify.printerlocal=0; + TALLOC_FREE(Printer->notify.option); + TALLOC_FREE(Printer->devmode); + + /* Remove from the internal list. */ + DLIST_REMOVE(printers_list, Printer); + return 0; +} + +/**************************************************************************** + find printer index by handle +****************************************************************************/ + +static struct printer_handle *find_printer_index_by_hnd(struct pipes_struct *p, + struct policy_handle *hnd) +{ + struct printer_handle *find_printer = NULL; + NTSTATUS status; + + find_printer = find_policy_by_hnd(p, + hnd, + DCESRV_HANDLE_ANY, + struct printer_handle, + &status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2,("find_printer_index_by_hnd: Printer handle not found: %s\n", + nt_errstr(status))); + return NULL; + } + + return find_printer; +} + +/**************************************************************************** + Close printer index by handle. +****************************************************************************/ + +static bool close_printer_handle(struct pipes_struct *p, struct policy_handle *hnd) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd); + + if (!Printer) { + DEBUG(2,("close_printer_handle: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(hnd))); + return false; + } + + close_policy_hnd(p, hnd); + + return true; +} + +/**************************************************************************** + Delete a printer given a handle. +****************************************************************************/ + +static WERROR delete_printer_hook(TALLOC_CTX *ctx, struct security_token *token, + const char *sharename, + struct messaging_context *msg_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *cmd = lp_deleteprinter_command(talloc_tos(), lp_sub); + char *command = NULL; + int ret; + bool is_print_op = false; + + /* can't fail if we don't try */ + + if ( !*cmd ) + return WERR_OK; + + command = talloc_asprintf(ctx, + "%s \"%s\"", + cmd, sharename); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + if ( token ) + is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR); + + DEBUG(10,("Running [%s]\n", command)); + + /********** BEGIN SePrintOperatorPrivlege BLOCK **********/ + + if ( is_print_op ) + become_root(); + + ret = smbrun(command, NULL, NULL); + if (ret == 0) { + /* Tell everyone we updated smb.conf. */ + messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + } + + if ( is_print_op ) + unbecome_root(); + + /********** END SePrintOperatorPrivlege BLOCK **********/ + + DEBUGADD(10,("returned [%d]\n", ret)); + + TALLOC_FREE(command); + + if (ret != 0) + return WERR_INVALID_HANDLE; /* What to return here? */ + + return WERR_OK; +} + +/**************************************************************************** + Delete a printer given a handle. +****************************************************************************/ + +static WERROR delete_printer_handle(struct pipes_struct *p, struct policy_handle *hnd) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd); + WERROR result; + + if (!Printer) { + DEBUG(2,("delete_printer_handle: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(hnd))); + return WERR_INVALID_HANDLE; + } + + /* + * It turns out that Windows allows delete printer on a handle + * opened by an admin user, then used on a pipe handle created + * by an anonymous user..... but they're working on security.... riiight ! + * JRA. + */ + + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("delete_printer_handle: denied by handle\n")); + return WERR_ACCESS_DENIED; + } + + /* this does not need a become root since the access check has been + done on the handle already */ + + result = winreg_delete_printer_key_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + Printer->sharename, + ""); + if (!W_ERROR_IS_OK(result)) { + DEBUG(3,("Error deleting printer %s\n", Printer->sharename)); + return WERR_INVALID_HANDLE; + } + + result = delete_printer_hook(p->mem_ctx, session_info->security_token, + Printer->sharename, p->msg_ctx); + if (!W_ERROR_IS_OK(result)) { + return result; + } + prune_printername_cache(); + return WERR_OK; +} + +/**************************************************************************** + Return the snum of a printer corresponding to an handle. +****************************************************************************/ + +static bool get_printer_snum(struct pipes_struct *p, struct policy_handle *hnd, + int *number, struct share_params **params) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd); + + if (!Printer) { + DEBUG(2,("get_printer_snum: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(hnd))); + return false; + } + + switch (Printer->printer_type) { + case SPLHND_PRINTER: + DEBUG(4,("short name:%s\n", Printer->sharename)); + *number = print_queue_snum(Printer->sharename); + return (*number != -1); + case SPLHND_SERVER: + return false; + default: + return false; + } +} + +/**************************************************************************** + Set printer handle type. + Check if it's \\server or \\server\printer +****************************************************************************/ + +static bool set_printer_hnd_printertype(struct printer_handle *Printer, const char *handlename) +{ + DEBUG(3,("Setting printer type=%s\n", handlename)); + + /* it's a print server */ + if (handlename && *handlename=='\\' && *(handlename+1)=='\\' && !strchr_m(handlename+2, '\\')) { + DEBUGADD(4,("Printer is a print server\n")); + Printer->printer_type = SPLHND_SERVER; + } + /* it's a printer (set_printer_hnd_name() will handle port monitors */ + else { + DEBUGADD(4,("Printer is a printer\n")); + Printer->printer_type = SPLHND_PRINTER; + } + + return true; +} + +static void prune_printername_cache_fn(const char *key, const char *value, + time_t timeout, void *private_data) +{ + gencache_del(key); +} + +static void prune_printername_cache(void) +{ + gencache_iterate(prune_printername_cache_fn, NULL, "PRINTERNAME/*"); +} + +/**************************************************************************** + Set printer handle name.. Accept names like \\server, \\server\printer, + \\server\SHARE, & "\\server\,XcvMonitor Standard TCP/IP Port" See + the MSDN docs regarding OpenPrinter() for details on the XcvData() and + XcvDataPort() interface. +****************************************************************************/ + +static WERROR set_printer_hnd_name(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct printer_handle *Printer, + const char *handlename) +{ + int snum; + int n_services=lp_numservices(); + char *aprinter; + const char *printername; + const char *servername = NULL; + fstring sname; + bool found = false; + struct spoolss_PrinterInfo2 *info2 = NULL; + WERROR result; + char *p; + + /* + * Hopefully nobody names his printers like this. Maybe \ or , + * are illegal in printer names even? + */ + const char printer_not_found[] = "Printer \\, !@#$%^&*( not found"; + char *cache_key; + char *tmp; + + DEBUG(4,("Setting printer name=%s (len=%lu)\n", handlename, + (unsigned long)strlen(handlename))); + + aprinter = discard_const_p(char, handlename); + if ( *handlename == '\\' ) { + servername = canon_servername(handlename); + if ( (aprinter = strchr_m( servername, '\\' )) != NULL ) { + *aprinter = '\0'; + aprinter++; + } + if (!is_myname_or_ipaddr(servername)) { + return WERR_INVALID_PRINTER_NAME; + } + Printer->servername = talloc_asprintf(Printer, "\\\\%s", servername); + if (Printer->servername == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + if (Printer->printer_type == SPLHND_SERVER) { + return WERR_OK; + } + + if (Printer->printer_type != SPLHND_PRINTER) { + return WERR_INVALID_HANDLE; + } + + DEBUGADD(5, ("searching for [%s]\n", aprinter)); + + p = strchr(aprinter, ','); + if (p != NULL) { + char *p2 = p; + p++; + if (*p == ' ') { + p++; + } + if (strncmp(p, "DrvConvert", strlen("DrvConvert")) == 0) { + *p2 = '\0'; + } else if (strncmp(p, "LocalOnly", strlen("LocalOnly")) == 0) { + *p2 = '\0'; + } + } + + if (p) { + DEBUGADD(5, ("stripped handlename: [%s]\n", aprinter)); + } + + /* check for the Port Monitor Interface */ + if ( strequal( aprinter, SPL_XCV_MONITOR_TCPMON ) ) { + Printer->printer_type = SPLHND_PORTMON_TCP; + fstrcpy(sname, SPL_XCV_MONITOR_TCPMON); + found = true; + } + else if ( strequal( aprinter, SPL_XCV_MONITOR_LOCALMON ) ) { + Printer->printer_type = SPLHND_PORTMON_LOCAL; + fstrcpy(sname, SPL_XCV_MONITOR_LOCALMON); + found = true; + } + + cache_key = talloc_asprintf(talloc_tos(), "PRINTERNAME/%s", aprinter); + if (cache_key == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* + * With hundreds of printers, the "for" loop iterating all + * shares can be quite expensive, as it is done on every + * OpenPrinter. The loop maps "aprinter" to "sname", the + * result of which we cache in gencache. + */ + if (gencache_get(cache_key, talloc_tos(), &tmp, NULL)) { + found = (strcmp(tmp, printer_not_found) != 0); + if (!found) { + DEBUG(4, ("Printer %s not found\n", aprinter)); + TALLOC_FREE(tmp); + return WERR_INVALID_PRINTER_NAME; + } + fstrcpy(sname, tmp); + TALLOC_FREE(tmp); + } + + /* Search all sharenames first as this is easier than pulling + the printer_info_2 off of disk. Don't use find_service() since + that calls out to map_username() */ + + /* do another loop to look for printernames */ + for (snum = 0; !found && snum < n_services; snum++) { + const char *printer = lp_const_servicename(snum); + + /* no point going on if this is not a printer */ + if (!(lp_snum_ok(snum) && lp_printable(snum))) { + continue; + } + + /* ignore [printers] share */ + if (strequal(printer, "printers")) { + continue; + } + + fstrcpy(sname, printer); + if (strequal(aprinter, printer)) { + found = true; + break; + } + + /* no point looking up the printer object if + we aren't allowing printername != sharename */ + if (lp_force_printername(snum)) { + continue; + } + + result = winreg_get_printer_internal(mem_ctx, + session_info, + msg_ctx, + sname, + &info2); + if ( !W_ERROR_IS_OK(result) ) { + DEBUG(2,("set_printer_hnd_name: failed to lookup printer [%s] -- result [%s]\n", + sname, win_errstr(result))); + continue; + } + + printername = strrchr(info2->printername, '\\'); + if (printername == NULL) { + printername = info2->printername; + } else { + printername++; + } + + if (strequal(printername, aprinter)) { + found = true; + break; + } + + DEBUGADD(10, ("printername: %s\n", printername)); + + TALLOC_FREE(info2); + } + + if (!found) { + gencache_set(cache_key, printer_not_found, + time(NULL) + 300); + TALLOC_FREE(cache_key); + DEBUGADD(4,("Printer not found\n")); + return WERR_INVALID_PRINTER_NAME; + } + + gencache_set(cache_key, sname, time(NULL) + 300); + TALLOC_FREE(cache_key); + + DEBUGADD(4,("set_printer_hnd_name: Printer found: %s -> %s\n", aprinter, sname)); + + strlcpy(Printer->sharename, sname, sizeof(Printer->sharename)); + + return WERR_OK; +} + +/**************************************************************************** + Find first available printer slot. creates a printer handle for you. + ****************************************************************************/ + +static WERROR open_printer_hnd(struct pipes_struct *p, + struct policy_handle *hnd, + const char *name, + uint32_t access_granted) +{ + struct printer_handle *new_printer; + WERROR result; + + DEBUG(10,("open_printer_hnd: name [%s]\n", name)); + + new_printer = talloc_zero(p->mem_ctx, struct printer_handle); + if (new_printer == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + talloc_set_destructor(new_printer, printer_entry_destructor); + + /* This also steals the printer_handle on the policy_handle */ + if (!create_policy_hnd(p, hnd, 0, new_printer)) { + TALLOC_FREE(new_printer); + return WERR_INVALID_HANDLE; + } + + /* Add to the internal list. */ + DLIST_ADD(printers_list, new_printer); + + new_printer->notify.option=NULL; + + if (!set_printer_hnd_printertype(new_printer, name)) { + close_printer_handle(p, hnd); + return WERR_INVALID_HANDLE; + } + + result = set_printer_hnd_name(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + new_printer, name); + if (!W_ERROR_IS_OK(result)) { + close_printer_handle(p, hnd); + return result; + } + + new_printer->access_granted = access_granted; + + DBG_INFO("%d printer handles active\n", (int)num_pipe_handles()); + + return WERR_OK; +} + +/*************************************************************************** + check to see if the client notify handle is monitoring the notification + given by (notify_type, notify_field). + **************************************************************************/ + +static bool is_monitoring_event_flags(uint32_t flags, uint16_t notify_type, + uint16_t notify_field) +{ + return true; +} + +static bool is_monitoring_event(struct printer_handle *p, uint16_t notify_type, + uint16_t notify_field) +{ + struct spoolss_NotifyOption *option = p->notify.option; + uint32_t i, j; + + /* + * Flags should always be zero when the change notify + * is registered by the client's spooler. A user Win32 app + * might use the flags though instead of the NOTIFY_OPTION_INFO + * --jerry + */ + + if (!option) { + return false; + } + + if (p->notify.flags) + return is_monitoring_event_flags( + p->notify.flags, notify_type, notify_field); + + for (i = 0; i < option->count; i++) { + + /* Check match for notify_type */ + + if (option->types[i].type != notify_type) + continue; + + /* Check match for field */ + + for (j = 0; j < option->types[i].count; j++) { + if (option->types[i].fields[j].field == notify_field) { + return true; + } + } + } + + DEBUG(10, ("Open handle for \\\\%s\\%s is not monitoring 0x%02x/0x%02x\n", + p->servername, p->sharename, notify_type, notify_field)); + + return false; +} + +#define SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(_data, _integer) \ + _data->data.integer[0] = _integer; \ + _data->data.integer[1] = 0; + + +#define SETUP_SPOOLSS_NOTIFY_DATA_STRING(_data, _p) \ + _data->data.string.string = talloc_strdup(mem_ctx, _p); \ + if (!_data->data.string.string) {\ + _data->data.string.size = 0; \ + } \ + _data->data.string.size = strlen_m_term(_p) * 2; + +#define SETUP_SPOOLSS_NOTIFY_DATA_DEVMODE(_data, _devmode) \ + _data->data.devmode.devmode = _devmode; + +static void init_systemtime_buffer(TALLOC_CTX *mem_ctx, + struct tm *t, + const char **pp, + uint32_t *plen) +{ + struct spoolss_Time st; + uint32_t len = 16; + char *p; + + if (!init_systemtime(&st, t)) { + return; + } + + p = talloc_array(mem_ctx, char, len); + if (!p) { + return; + } + + /* + * Systemtime must be linearized as a set of UINT16's. + * Fix from Benjamin (Bj) Kuit bj@it.uts.edu.au + */ + + SSVAL(p, 0, st.year); + SSVAL(p, 2, st.month); + SSVAL(p, 4, st.day_of_week); + SSVAL(p, 6, st.day); + SSVAL(p, 8, st.hour); + SSVAL(p, 10, st.minute); + SSVAL(p, 12, st.second); + SSVAL(p, 14, st.millisecond); + + *pp = p; + *plen = len; +} + +/* Convert a notification message to a struct spoolss_Notify */ + +static void notify_one_value(struct spoolss_notify_msg *msg, + struct spoolss_Notify *data, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, msg->notify.value[0]); +} + +static void notify_string(struct spoolss_notify_msg *msg, + struct spoolss_Notify *data, + TALLOC_CTX *mem_ctx) +{ + /* The length of the message includes the trailing \0 */ + + data->data.string.size = msg->len * 2; + data->data.string.string = talloc_strdup(mem_ctx, msg->notify.data); + if (!data->data.string.string) { + data->data.string.size = 0; + return; + } +} + +static void notify_system_time(struct spoolss_notify_msg *msg, + struct spoolss_Notify *data, + TALLOC_CTX *mem_ctx) +{ + data->data.string.string = NULL; + data->data.string.size = 0; + + if (msg->len != sizeof(time_t)) { + DEBUG(5, ("notify_system_time: received wrong sized message (%d)\n", + msg->len)); + return; + } + + init_systemtime_buffer(mem_ctx, gmtime((time_t *)msg->notify.data), + &data->data.string.string, + &data->data.string.size); +} + +struct notify2_message_table { + const char *name; + void (*fn)(struct spoolss_notify_msg *msg, + struct spoolss_Notify *data, TALLOC_CTX *mem_ctx); +}; + +static struct notify2_message_table printer_notify_table[] = { + /* 0x00 */ { "PRINTER_NOTIFY_FIELD_SERVER_NAME", notify_string }, + /* 0x01 */ { "PRINTER_NOTIFY_FIELD_PRINTER_NAME", notify_string }, + /* 0x02 */ { "PRINTER_NOTIFY_FIELD_SHARE_NAME", notify_string }, + /* 0x03 */ { "PRINTER_NOTIFY_FIELD_PORT_NAME", notify_string }, + /* 0x04 */ { "PRINTER_NOTIFY_FIELD_DRIVER_NAME", notify_string }, + /* 0x05 */ { "PRINTER_NOTIFY_FIELD_COMMENT", notify_string }, + /* 0x06 */ { "PRINTER_NOTIFY_FIELD_LOCATION", notify_string }, + /* 0x07 */ { "PRINTER_NOTIFY_FIELD_DEVMODE", NULL }, + /* 0x08 */ { "PRINTER_NOTIFY_FIELD_SEPFILE", notify_string }, + /* 0x09 */ { "PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR", notify_string }, + /* 0x0a */ { "PRINTER_NOTIFY_FIELD_PARAMETERS", NULL }, + /* 0x0b */ { "PRINTER_NOTIFY_FIELD_DATATYPE", notify_string }, + /* 0x0c */ { "PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NULL }, + /* 0x0d */ { "PRINTER_NOTIFY_FIELD_ATTRIBUTES", notify_one_value }, + /* 0x0e */ { "PRINTER_NOTIFY_FIELD_PRIORITY", notify_one_value }, + /* 0x0f */ { "PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY", NULL }, + /* 0x10 */ { "PRINTER_NOTIFY_FIELD_START_TIME", NULL }, + /* 0x11 */ { "PRINTER_NOTIFY_FIELD_UNTIL_TIME", NULL }, + /* 0x12 */ { "PRINTER_NOTIFY_FIELD_STATUS", notify_one_value }, +}; + +static struct notify2_message_table job_notify_table[] = { + /* 0x00 */ { "JOB_NOTIFY_FIELD_PRINTER_NAME", NULL }, + /* 0x01 */ { "JOB_NOTIFY_FIELD_MACHINE_NAME", NULL }, + /* 0x02 */ { "JOB_NOTIFY_FIELD_PORT_NAME", NULL }, + /* 0x03 */ { "JOB_NOTIFY_FIELD_USER_NAME", notify_string }, + /* 0x04 */ { "JOB_NOTIFY_FIELD_NOTIFY_NAME", NULL }, + /* 0x05 */ { "JOB_NOTIFY_FIELD_DATATYPE", NULL }, + /* 0x06 */ { "JOB_NOTIFY_FIELD_PRINT_PROCESSOR", NULL }, + /* 0x07 */ { "JOB_NOTIFY_FIELD_PARAMETERS", NULL }, + /* 0x08 */ { "JOB_NOTIFY_FIELD_DRIVER_NAME", NULL }, + /* 0x09 */ { "JOB_NOTIFY_FIELD_DEVMODE", NULL }, + /* 0x0a */ { "JOB_NOTIFY_FIELD_STATUS", notify_one_value }, + /* 0x0b */ { "JOB_NOTIFY_FIELD_STATUS_STRING", NULL }, + /* 0x0c */ { "JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NULL }, + /* 0x0d */ { "JOB_NOTIFY_FIELD_DOCUMENT", notify_string }, + /* 0x0e */ { "JOB_NOTIFY_FIELD_PRIORITY", NULL }, + /* 0x0f */ { "JOB_NOTIFY_FIELD_POSITION", NULL }, + /* 0x10 */ { "JOB_NOTIFY_FIELD_SUBMITTED", notify_system_time }, + /* 0x11 */ { "JOB_NOTIFY_FIELD_START_TIME", NULL }, + /* 0x12 */ { "JOB_NOTIFY_FIELD_UNTIL_TIME", NULL }, + /* 0x13 */ { "JOB_NOTIFY_FIELD_TIME", NULL }, + /* 0x14 */ { "JOB_NOTIFY_FIELD_TOTAL_PAGES", notify_one_value }, + /* 0x15 */ { "JOB_NOTIFY_FIELD_PAGES_PRINTED", NULL }, + /* 0x16 */ { "JOB_NOTIFY_FIELD_TOTAL_BYTES", notify_one_value }, + /* 0x17 */ { "JOB_NOTIFY_FIELD_BYTES_PRINTED", NULL }, +}; + + +/*********************************************************************** + Allocate talloc context for container object + **********************************************************************/ + +static void notify_msg_ctr_init( SPOOLSS_NOTIFY_MSG_CTR *ctr ) +{ + if ( !ctr ) + return; + + ctr->ctx = talloc_init("notify_msg_ctr_init %p", ctr); + + return; +} + +/*********************************************************************** + release all allocated memory and zero out structure + **********************************************************************/ + +static void notify_msg_ctr_destroy( SPOOLSS_NOTIFY_MSG_CTR *ctr ) +{ + if ( !ctr ) + return; + + if ( ctr->ctx ) + talloc_destroy(ctr->ctx); + + ZERO_STRUCTP(ctr); + + return; +} + +/*********************************************************************** + **********************************************************************/ + +static TALLOC_CTX* notify_ctr_getctx( SPOOLSS_NOTIFY_MSG_CTR *ctr ) +{ + if ( !ctr ) + return NULL; + + return ctr->ctx; +} + +/*********************************************************************** + **********************************************************************/ + +static SPOOLSS_NOTIFY_MSG_GROUP* notify_ctr_getgroup( SPOOLSS_NOTIFY_MSG_CTR *ctr, uint32_t idx ) +{ + if ( !ctr || !ctr->msg_groups ) + return NULL; + + if ( idx >= ctr->num_groups ) + return NULL; + + return &ctr->msg_groups[idx]; + +} + +/*********************************************************************** + How many groups of change messages do we have ? + **********************************************************************/ + +static uint32_t notify_msg_ctr_numgroups( SPOOLSS_NOTIFY_MSG_CTR *ctr ) +{ + if ( !ctr ) + return 0; + + return ctr->num_groups; +} + +/*********************************************************************** + Add a SPOOLSS_NOTIFY_MSG_CTR to the correct group + **********************************************************************/ + +static int notify_msg_ctr_addmsg( SPOOLSS_NOTIFY_MSG_CTR *ctr, SPOOLSS_NOTIFY_MSG *msg ) +{ + SPOOLSS_NOTIFY_MSG_GROUP *groups = NULL; + SPOOLSS_NOTIFY_MSG_GROUP *msg_grp = NULL; + SPOOLSS_NOTIFY_MSG *msg_list = NULL; + uint32_t i, new_slot; + + if ( !ctr || !msg ) + return 0; + + /* loop over all groups looking for a matching printer name */ + + for ( i=0; i<ctr->num_groups; i++ ) { + if ( strcmp(ctr->msg_groups[i].printername, msg->printer) == 0 ) + break; + } + + /* add a new group? */ + + if ( i == ctr->num_groups ) { + ctr->num_groups++; + + if ( !(groups = talloc_realloc( ctr->ctx, ctr->msg_groups, SPOOLSS_NOTIFY_MSG_GROUP, ctr->num_groups)) ) { + DEBUG(0,("notify_msg_ctr_addmsg: talloc_realloc() failed!\n")); + return 0; + } + ctr->msg_groups = groups; + + /* clear the new entry and set the printer name */ + + ZERO_STRUCT( ctr->msg_groups[ctr->num_groups-1] ); + fstrcpy( ctr->msg_groups[ctr->num_groups-1].printername, msg->printer ); + } + + /* add the change messages; 'i' is the correct index now regardless */ + + msg_grp = &ctr->msg_groups[i]; + + msg_grp->num_msgs++; + + if ( !(msg_list = talloc_realloc( ctr->ctx, msg_grp->msgs, SPOOLSS_NOTIFY_MSG, msg_grp->num_msgs )) ) { + DEBUG(0,("notify_msg_ctr_addmsg: talloc_realloc() failed for new message [%d]!\n", msg_grp->num_msgs)); + return 0; + } + msg_grp->msgs = msg_list; + + new_slot = msg_grp->num_msgs-1; + memcpy( &msg_grp->msgs[new_slot], msg, sizeof(SPOOLSS_NOTIFY_MSG) ); + + /* need to allocate own copy of data */ + + if ( msg->len != 0 ) + msg_grp->msgs[new_slot].notify.data = (char *) + talloc_memdup( ctr->ctx, msg->notify.data, msg->len ); + + return ctr->num_groups; +} + +static void construct_info_data(struct spoolss_Notify *info_data, + enum spoolss_NotifyType type, + uint16_t field, int id); + +/*********************************************************************** + Send a change notification message on all handles which have a call + back registered + **********************************************************************/ + +static int build_notify2_messages(TALLOC_CTX *mem_ctx, + struct printer_handle *prn_hnd, + SPOOLSS_NOTIFY_MSG *messages, + uint32_t num_msgs, + struct spoolss_Notify **_notifies, + size_t *_count) +{ + struct spoolss_Notify *notifies; + SPOOLSS_NOTIFY_MSG *msg; + size_t count = 0; + uint32_t id; + uint32_t i; + + notifies = talloc_zero_array(mem_ctx, + struct spoolss_Notify, num_msgs); + if (!notifies) { + return ENOMEM; + } + + for (i = 0; i < num_msgs; i++) { + + msg = &messages[i]; + + /* Are we monitoring this event? */ + + if (!is_monitoring_event(prn_hnd, msg->type, msg->field)) { + continue; + } + + DEBUG(10, ("Sending message type [0x%x] field [0x%2x] " + "for printer [%s]\n", + msg->type, msg->field, prn_hnd->sharename)); + + /* + * if the is a printer notification handle and not a job + * notification type, then set the id to 0. + * Otherwise just use what was specified in the message. + * + * When registering change notification on a print server + * handle we always need to send back the id (snum) matching + * the printer for which the change took place. + * For change notify registered on a printer handle, + * this does not matter and the id should be 0. + * + * --jerry + */ + + if ((msg->type == PRINTER_NOTIFY_TYPE) && + (prn_hnd->printer_type == SPLHND_PRINTER)) { + id = 0; + } else { + id = msg->id; + } + + /* Convert unix jobid to smb jobid */ + + if (msg->flags & SPOOLSS_NOTIFY_MSG_UNIX_JOBID) { + id = sysjob_to_jobid(msg->id); + + if (id == -1) { + DEBUG(3, ("no such unix jobid %d\n", + msg->id)); + continue; + } + } + + construct_info_data(¬ifies[count], + msg->type, msg->field, id); + + switch(msg->type) { + case PRINTER_NOTIFY_TYPE: + if (printer_notify_table[msg->field].fn) { + printer_notify_table[msg->field].fn(msg, + ¬ifies[count], mem_ctx); + } + break; + + case JOB_NOTIFY_TYPE: + if (job_notify_table[msg->field].fn) { + job_notify_table[msg->field].fn(msg, + ¬ifies[count], mem_ctx); + } + break; + + default: + DEBUG(5, ("Unknown notification type %d\n", + msg->type)); + continue; + } + + count++; + } + + *_notifies = notifies; + *_count = count; + + return 0; +} + +static int send_notify2_printer(TALLOC_CTX *mem_ctx, + struct printer_handle *prn_hnd, + SPOOLSS_NOTIFY_MSG_GROUP *msg_group) +{ + struct spoolss_Notify *notifies; + size_t count = 0; + union spoolss_ReplyPrinterInfo info; + struct spoolss_NotifyInfo info0; + uint32_t reply_result; + NTSTATUS status; + WERROR werr; + int ret; + + /* Is there notification on this handle? */ + if (prn_hnd->notify.cli_chan == NULL || + prn_hnd->notify.cli_chan->cli_pipe == NULL || + prn_hnd->notify.cli_chan->cli_pipe->binding_handle == NULL || + prn_hnd->notify.cli_chan->active_connections == 0) { + return 0; + } + + DEBUG(10, ("Client connected! [\\\\%s\\%s]\n", + prn_hnd->servername, prn_hnd->sharename)); + + /* For this printer? Print servers always receive notifications. */ + if ((prn_hnd->printer_type == SPLHND_PRINTER) && + (!strequal(msg_group->printername, prn_hnd->sharename))) { + return 0; + } + + DEBUG(10,("Our printer\n")); + + /* build the array of change notifications */ + ret = build_notify2_messages(mem_ctx, prn_hnd, + msg_group->msgs, + msg_group->num_msgs, + ¬ifies, &count); + if (ret) { + return ret; + } + + info0.version = 0x2; + info0.flags = count ? 0x00020000 /* ??? */ : PRINTER_NOTIFY_INFO_DISCARDED; + info0.count = count; + info0.notifies = notifies; + + info.info0 = &info0; + + status = dcerpc_spoolss_RouterReplyPrinterEx( + prn_hnd->notify.cli_chan->cli_pipe->binding_handle, + mem_ctx, + &prn_hnd->notify.cli_hnd, + prn_hnd->notify.change, /* color */ + prn_hnd->notify.flags, + &reply_result, + 0, /* reply_type, must be 0 */ + info, &werr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("dcerpc_spoolss_RouterReplyPrinterEx to client: %s " + "failed: %s\n", + prn_hnd->notify.cli_chan->cli_pipe->srv_name_slash, + nt_errstr(status))); + werr = ntstatus_to_werror(status); + } else if (!W_ERROR_IS_OK(werr)) { + DEBUG(1, ("RouterReplyPrinterEx to client: %s " + "failed: %s\n", + prn_hnd->notify.cli_chan->cli_pipe->srv_name_slash, + win_errstr(werr))); + } + switch (reply_result) { + case 0: + break; + case PRINTER_NOTIFY_INFO_DISCARDED: + case PRINTER_NOTIFY_INFO_DISCARDNOTED: + case PRINTER_NOTIFY_INFO_COLOR_MISMATCH: + break; + default: + break; + } + + return 0; +} + +static void send_notify2_changes( SPOOLSS_NOTIFY_MSG_CTR *ctr, uint32_t idx ) +{ + struct printer_handle *p; + TALLOC_CTX *mem_ctx = notify_ctr_getctx( ctr ); + SPOOLSS_NOTIFY_MSG_GROUP *msg_group = notify_ctr_getgroup( ctr, idx ); + int ret; + + if ( !msg_group ) { + DEBUG(5,("send_notify2_changes() called with no msg group!\n")); + return; + } + + if (!msg_group->msgs) { + DEBUG(5, ("send_notify2_changes() called with no messages!\n")); + return; + } + + DEBUG(8,("send_notify2_changes: Enter...[%s]\n", msg_group->printername)); + + /* loop over all printers */ + + for (p = printers_list; p; p = p->next) { + ret = send_notify2_printer(mem_ctx, p, msg_group); + if (ret) { + goto done; + } + } + +done: + DEBUG(8,("send_notify2_changes: Exit...\n")); + return; +} + +/*********************************************************************** + **********************************************************************/ + +static bool notify2_unpack_msg( SPOOLSS_NOTIFY_MSG *msg, struct timeval *tv, void *buf, size_t len ) +{ + + uint32_t tv_sec, tv_usec; + size_t offset = 0; + + /* Unpack message */ + + offset += tdb_unpack((uint8_t *)buf + offset, len - offset, "f", + msg->printer); + + offset += tdb_unpack((uint8_t *)buf + offset, len - offset, "ddddddd", + &tv_sec, &tv_usec, + &msg->type, &msg->field, &msg->id, &msg->len, &msg->flags); + + if (msg->len == 0) + tdb_unpack((uint8_t *)buf + offset, len - offset, "dd", + &msg->notify.value[0], &msg->notify.value[1]); + else + tdb_unpack((uint8_t *)buf + offset, len - offset, "B", + &msg->len, &msg->notify.data); + + DEBUG(3, ("notify2_unpack_msg: got NOTIFY2 message for printer %s, jobid %u type %d, field 0x%02x, flags 0x%04x\n", + msg->printer, (unsigned int)msg->id, msg->type, msg->field, msg->flags)); + + tv->tv_sec = tv_sec; + tv->tv_usec = tv_usec; + + if (msg->len == 0) + DEBUG(3, ("notify2_unpack_msg: value1 = %d, value2 = %d\n", msg->notify.value[0], + msg->notify.value[1])); + else + dump_data(3, (uint8_t *)msg->notify.data, msg->len); + + return true; +} + +/******************************************************************** + Receive a notify2 message list + ********************************************************************/ + +static void receive_notify2_message_list(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + size_t msg_count, i, num_groups; + char *buf = (char *)data->data; + char *msg_ptr; + size_t msg_len; + SPOOLSS_NOTIFY_MSG notify; + SPOOLSS_NOTIFY_MSG_CTR messages; + + if (data->length < 4) { + DEBUG(0,("receive_notify2_message_list: bad message format (len < 4)!\n")); + return; + } + + msg_count = IVAL(buf, 0); + msg_ptr = buf + 4; + + DEBUG(5, ("receive_notify2_message_list: got %lu messages in list\n", (unsigned long)msg_count)); + + if (msg_count == 0) { + DEBUG(0,("receive_notify2_message_list: bad message format (msg_count == 0) !\n")); + return; + } + + /* initialize the container */ + + ZERO_STRUCT( messages ); + notify_msg_ctr_init( &messages ); + + /* + * build message groups for each printer identified + * in a change_notify msg. Remember that a PCN message + * includes the handle returned for the srv_spoolss_replyopenprinter() + * call. Therefore messages are grouped according to printer handle. + */ + + for ( i=0; i<msg_count; i++ ) { + struct timeval msg_tv; + + if (msg_ptr + 4 - buf > data->length) { + DEBUG(0,("receive_notify2_message_list: bad message format (len > buf_size) !\n")); + return; + } + + msg_len = IVAL(msg_ptr,0); + msg_ptr += 4; + + if (msg_ptr + msg_len - buf > data->length) { + DEBUG(0,("receive_notify2_message_list: bad message format (bad len) !\n")); + return; + } + + /* unpack messages */ + + ZERO_STRUCT( notify ); + notify2_unpack_msg( ¬ify, &msg_tv, msg_ptr, msg_len ); + msg_ptr += msg_len; + + /* add to correct list in container */ + + notify_msg_ctr_addmsg( &messages, ¬ify ); + + /* free memory that might have been allocated by notify2_unpack_msg() */ + + if ( notify.len != 0 ) + SAFE_FREE( notify.notify.data ); + } + + /* process each group of messages */ + + num_groups = notify_msg_ctr_numgroups( &messages ); + for ( i=0; i<num_groups; i++ ) + send_notify2_changes( &messages, i ); + + + /* cleanup */ + + DEBUG(10,("receive_notify2_message_list: processed %u messages\n", + (uint32_t)msg_count )); + + notify_msg_ctr_destroy( &messages ); + + return; +} + +/******************************************************************** + Send a message to ourself about new driver being installed + so we can upgrade the information for each printer bound to this + driver + ********************************************************************/ + +static bool srv_spoolss_drv_upgrade_printer(const char *drivername, + struct messaging_context *msg_ctx) +{ + int len = strlen(drivername); + + if (!len) + return false; + + DEBUG(10,("srv_spoolss_drv_upgrade_printer: Sending message about driver upgrade [%s]\n", + drivername)); + + messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx), + MSG_PRINTER_DRVUPGRADE, + (const uint8_t *)drivername, len+1); + + return true; +} + +void srv_spoolss_cleanup(void) +{ + struct printer_session_counter *session_counter; + + for (session_counter = counter_list; + session_counter != NULL; + session_counter = counter_list) { + DLIST_REMOVE(counter_list, session_counter); + TALLOC_FREE(session_counter); + } +} + +/********************************************************************** + callback to receive a MSG_PRINTER_DRVUPGRADE message and iterate + over all printers, upgrading ones as necessary + This is now *ONLY* called inside the background lpq updater. JRA. + **********************************************************************/ + +void do_drv_upgrade_printer(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + TALLOC_CTX *tmp_ctx; + const struct auth_session_info *session_info = get_session_info_system(); + struct spoolss_PrinterInfo2 *pinfo2; + WERROR result; + const char *drivername; + int snum; + int n_services = lp_numservices(); + struct dcerpc_binding_handle *b = NULL; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return; + + drivername = talloc_strndup(tmp_ctx, (const char *)data->data, data->length); + if (!drivername) { + DEBUG(0, ("do_drv_upgrade_printer: Out of memory ?!\n")); + goto done; + } + + DEBUG(10, ("do_drv_upgrade_printer: " + "Got message for new driver [%s]\n", drivername)); + + /* Iterate the printer list */ + + for (snum = 0; snum < n_services; snum++) { + if (!lp_snum_ok(snum) || !lp_printable(snum)) { + continue; + } + + /* ignore [printers] share */ + if (strequal(lp_const_servicename(snum), "printers")) { + continue; + } + + if (b == NULL) { + result = winreg_printer_binding_handle(tmp_ctx, + session_info, + msg, + &b); + if (!W_ERROR_IS_OK(result)) { + break; + } + } + + result = winreg_get_printer(tmp_ctx, b, + lp_const_servicename(snum), + &pinfo2); + + if (!W_ERROR_IS_OK(result)) { + continue; + } + + if (!pinfo2->drivername) { + continue; + } + + if (strcmp(drivername, pinfo2->drivername) != 0) { + continue; + } + + DEBUG(6,("Updating printer [%s]\n", pinfo2->printername)); + + /* all we care about currently is the change_id */ + result = winreg_printer_update_changeid(tmp_ctx, b, + pinfo2->printername); + + if (!W_ERROR_IS_OK(result)) { + DEBUG(3, ("do_drv_upgrade_printer: " + "Failed to update changeid [%s]\n", + win_errstr(result))); + } + } + + /* all done */ +done: + talloc_free(tmp_ctx); +} + +/******************************************************************** + Update the cache for all printq's with a registered client + connection + ********************************************************************/ + +void update_monitored_printq_cache(struct messaging_context *msg_ctx) +{ + struct printer_handle *printer = printers_list; + int snum; + + /* loop through all printers and update the cache where + a client is connected */ + while (printer) { + if ((printer->printer_type == SPLHND_PRINTER) && + ((printer->notify.cli_chan != NULL) && + (printer->notify.cli_chan->active_connections > 0))) { + snum = print_queue_snum(printer->sharename); + print_queue_status(msg_ctx, snum, NULL, NULL); + } + + printer = printer->next; + } + + return; +} + +/**************************************************************** + _spoolss_OpenPrinter +****************************************************************/ + +WERROR _spoolss_OpenPrinter(struct pipes_struct *p, + struct spoolss_OpenPrinter *r) +{ + struct spoolss_OpenPrinterEx e; + struct spoolss_UserLevel1 level1; + WERROR werr; + + ZERO_STRUCT(level1); + + e.in.printername = r->in.printername; + e.in.datatype = r->in.datatype; + e.in.devmode_ctr = r->in.devmode_ctr; + e.in.access_mask = r->in.access_mask; + e.in.userlevel_ctr.level = 1; + e.in.userlevel_ctr.user_info.level1 = &level1; + + e.out.handle = r->out.handle; + + werr = _spoolss_OpenPrinterEx(p, &e); + + if (W_ERROR_EQUAL(werr, WERR_INVALID_PARAMETER)) { + /* OpenPrinterEx returns this for a bad + * printer name. We must return WERR_INVALID_PRINTER_NAME + * instead. + */ + werr = WERR_INVALID_PRINTER_NAME; + } + + return werr; +} + +static WERROR copy_devicemode(TALLOC_CTX *mem_ctx, + struct spoolss_DeviceMode *orig, + struct spoolss_DeviceMode **dest) +{ + struct spoolss_DeviceMode *dm; + + dm = talloc(mem_ctx, struct spoolss_DeviceMode); + if (!dm) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* copy all values, then duplicate strings and structs */ + *dm = *orig; + + dm->devicename = talloc_strdup(dm, orig->devicename); + if (!dm->devicename) { + return WERR_NOT_ENOUGH_MEMORY; + } + dm->formname = talloc_strdup(dm, orig->formname); + if (!dm->formname) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (orig->driverextra_data.data) { + dm->driverextra_data.data = + (uint8_t *) talloc_memdup(dm, orig->driverextra_data.data, + orig->driverextra_data.length); + if (!dm->driverextra_data.data) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + *dest = dm; + return WERR_OK; +} + +/**************************************************************** + _spoolss_OpenPrinterEx +****************************************************************/ + +WERROR _spoolss_OpenPrinterEx(struct pipes_struct *p, + struct spoolss_OpenPrinterEx *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + int snum; + char *raddr; + char *rhost; + struct printer_handle *Printer=NULL; + WERROR result; + int rc; + + if (!r->in.printername) { + return WERR_INVALID_PARAMETER; + } + + if (!*r->in.printername) { + return WERR_INVALID_PARAMETER; + } + + if (r->in.userlevel_ctr.level > 3) { + return WERR_INVALID_PARAMETER; + } + if ((r->in.userlevel_ctr.level == 1 && !r->in.userlevel_ctr.user_info.level1) || + (r->in.userlevel_ctr.level == 2 && !r->in.userlevel_ctr.user_info.level2) || + (r->in.userlevel_ctr.level == 3 && !r->in.userlevel_ctr.user_info.level3)) { + return WERR_INVALID_PARAMETER; + } + + /* + * The printcap printer share inventory is updated on client + * enumeration. For clients that do not perform enumeration prior to + * access, such as cupssmbadd, we reinitialise the printer share + * inventory on open as well. + */ + become_root(); + delete_and_reload_printers(); + unbecome_root(); + + /* some sanity check because you can open a printer or a print server */ + /* aka: \\server\printer or \\server */ + + DEBUGADD(3,("checking name: %s\n", r->in.printername)); + + result = open_printer_hnd(p, r->out.handle, r->in.printername, 0); + if (!W_ERROR_IS_OK(result)) { + DEBUG(3,("_spoolss_OpenPrinterEx: Cannot open a printer handle " + "for printer %s\n", r->in.printername)); + ZERO_STRUCTP(r->out.handle); + return result; + } + + Printer = find_printer_index_by_hnd(p, r->out.handle); + if ( !Printer ) { + DEBUG(0,("_spoolss_OpenPrinterEx: logic error. Can't find printer " + "handle we created for printer %s\n", r->in.printername)); + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_INVALID_PARAMETER; + } + + /* + * First case: the user is opening the print server: + * + * Disallow MS AddPrinterWizard if parameter disables it. A Win2k + * client 1st tries an OpenPrinterEx with access==0, MUST be allowed. + * + * Then both Win2k and WinNT clients try an OpenPrinterEx with + * SERVER_ALL_ACCESS, which we allow only if the user is root (uid=0) + * or if the user is listed in the smb.conf printer admin parameter. + * + * Then they try OpenPrinterEx with SERVER_READ which we allow. This lets the + * client view printer folder, but does not show the MSAPW. + * + * Note: this test needs code to check access rights here too. Jeremy + * could you look at this? + * + * Second case: the user is opening a printer: + * NT doesn't let us connect to a printer if the connecting user + * doesn't have print permission. + * + * Third case: user is opening a Port Monitor + * access checks same as opening a handle to the print server. + */ + + switch (Printer->printer_type ) + { + case SPLHND_SERVER: + case SPLHND_PORTMON_TCP: + case SPLHND_PORTMON_LOCAL: + /* Printserver handles use global struct... */ + + snum = -1; + + if (r->in.access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + r->in.access_mask |= SERVER_ACCESS_ADMINISTER; + r->in.access_mask |= SERVER_ACCESS_ENUMERATE; + } + + /* Map standard access rights to object specific access rights */ + + se_map_standard(&r->in.access_mask, + &printserver_std_mapping); + + /* Deny any object specific bits that don't apply to print + servers (i.e printer and job specific bits) */ + + r->in.access_mask &= SEC_MASK_SPECIFIC; + + if (r->in.access_mask & + ~(SERVER_ACCESS_ADMINISTER | SERVER_ACCESS_ENUMERATE)) { + DEBUG(3, ("access DENIED for non-printserver bits\n")); + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_ACCESS_DENIED; + } + + /* Allow admin access */ + + if ( r->in.access_mask & SERVER_ACCESS_ADMINISTER ) + { + if (!lp_show_add_printer_wizard()) { + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_ACCESS_DENIED; + } + + /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege, + and not a printer admin, then fail */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege( + session_info->security_token, + SEC_PRIV_PRINT_OPERATOR) && + !nt_token_check_sid(&global_sid_Builtin_Print_Operators, + session_info->security_token)) { + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + DEBUG(3,("access DENIED as user is not root, " + "has no printoperator privilege and is " + "not a member of the printoperator builtin group\n")); + return WERR_ACCESS_DENIED; + } + + r->in.access_mask = SERVER_ACCESS_ADMINISTER; + } + else + { + r->in.access_mask = SERVER_ACCESS_ENUMERATE; + } + + DEBUG(4,("Setting print server access = %s\n", (r->in.access_mask == SERVER_ACCESS_ADMINISTER) + ? "SERVER_ACCESS_ADMINISTER" : "SERVER_ACCESS_ENUMERATE" )); + + break; + + case SPLHND_PRINTER: + /* NT doesn't let us connect to a printer if the connecting user + doesn't have print permission. */ + + if (!get_printer_snum(p, r->out.handle, &snum, NULL)) { + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_INVALID_HANDLE; + } + + if (r->in.access_mask == SEC_FLAG_MAXIMUM_ALLOWED) { + r->in.access_mask = PRINTER_ACCESS_ADMINISTER; + } + + se_map_standard(&r->in.access_mask, &printer_std_mapping); + + /* map an empty access mask to the minimum access mask */ + if (r->in.access_mask == 0x0) + r->in.access_mask = PRINTER_ACCESS_USE; + + /* + * If we are not serving the printer driver for this printer, + * map PRINTER_ACCESS_ADMINISTER to PRINTER_ACCESS_USE. This + * will keep NT clients happy --jerry + */ + + if (lp_use_client_driver(snum) + && (r->in.access_mask & PRINTER_ACCESS_ADMINISTER)) + { + r->in.access_mask = PRINTER_ACCESS_USE; + } + + /* check smb.conf parameters and the the sec_desc */ + raddr = tsocket_address_inet_addr_string(remote_address, + p->mem_ctx); + if (raddr == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + rc = get_remote_hostname(remote_address, + &rhost, + p->mem_ctx); + if (rc < 0) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (strequal(rhost, "UNKNOWN")) { + rhost = raddr; + } + + if (!allow_access(lp_hosts_deny(snum), lp_hosts_allow(snum), + rhost, raddr)) { + DEBUG(3, ("access DENIED (hosts allow/deny) for printer open\n")); + ZERO_STRUCTP(r->out.handle); + return WERR_ACCESS_DENIED; + } + + if (!user_ok_token(session_info->unix_info->unix_name, + session_info->info->domain_name, + session_info->security_token, snum) || + !W_ERROR_IS_OK(print_access_check(session_info, + p->msg_ctx, + snum, + r->in.access_mask))) { + DEBUG(3, ("access DENIED for printer open\n")); + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_ACCESS_DENIED; + } + + if ((r->in.access_mask & SEC_MASK_SPECIFIC)& ~(PRINTER_ACCESS_ADMINISTER|PRINTER_ACCESS_USE)) { + DEBUG(3, ("access DENIED for printer open - unknown bits\n")); + close_printer_handle(p, r->out.handle); + ZERO_STRUCTP(r->out.handle); + return WERR_ACCESS_DENIED; + } + + if (r->in.access_mask & PRINTER_ACCESS_ADMINISTER) + r->in.access_mask = PRINTER_ACCESS_ADMINISTER; + else + r->in.access_mask = PRINTER_ACCESS_USE; + + DEBUG(4,("Setting printer access = %s\n", (r->in.access_mask == PRINTER_ACCESS_ADMINISTER) + ? "PRINTER_ACCESS_ADMINISTER" : "PRINTER_ACCESS_USE" )); + + winreg_create_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum)); + + break; + + default: + /* sanity check to prevent programmer error */ + ZERO_STRUCTP(r->out.handle); + return WERR_INVALID_HANDLE; + } + + Printer->access_granted = r->in.access_mask; + + /* + * If the client sent a devmode in the OpenPrinter() call, then + * save it here in case we get a job submission on this handle + */ + + if ((Printer->printer_type != SPLHND_SERVER) + && (r->in.devmode_ctr.devmode != NULL)) { + copy_devicemode(NULL, r->in.devmode_ctr.devmode, + &Printer->devmode); + } + + return WERR_OK; +} + +/**************************************************************** + _spoolss_ClosePrinter +****************************************************************/ + +WERROR _spoolss_ClosePrinter(struct pipes_struct *p, + struct spoolss_ClosePrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (Printer && Printer->document_started) { + struct spoolss_EndDocPrinter e; + + e.in.handle = r->in.handle; + + _spoolss_EndDocPrinter(p, &e); + } + + if (!close_printer_handle(p, r->in.handle)) + return WERR_INVALID_HANDLE; + + /* clear the returned printer handle. Observed behavior + from Win2k server. Don't think this really matters. + Previous code just copied the value of the closed + handle. --jerry */ + + ZERO_STRUCTP(r->out.handle); + + return WERR_OK; +} + +/**************************************************************** + _spoolss_DeletePrinter +****************************************************************/ + +WERROR _spoolss_DeletePrinter(struct pipes_struct *p, + struct spoolss_DeletePrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + WERROR result; + int snum; + + if (Printer && Printer->document_started) { + struct spoolss_EndDocPrinter e; + + e.in.handle = r->in.handle; + + _spoolss_EndDocPrinter(p, &e); + } + + if (get_printer_snum(p, r->in.handle, &snum, NULL)) { + winreg_delete_printer_key_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + ""); + } + + result = delete_printer_handle(p, r->in.handle); + + return result; +} + +/******************************************************************* + * static function to lookup the version id corresponding to an + * long architecture string + ******************************************************************/ + +static const int drv_cversion[] = {SPOOLSS_DRIVER_VERSION_9X, + SPOOLSS_DRIVER_VERSION_NT35, + SPOOLSS_DRIVER_VERSION_NT4, + SPOOLSS_DRIVER_VERSION_200X, + -1}; + +static int get_version_id(const char *arch) +{ + int i; + + for (i=0; archi_table[i].long_archi != NULL; i++) + { + if (strcmp(arch, archi_table[i].long_archi) == 0) + return (archi_table[i].version); + } + + return -1; +} + +/**************************************************************** + _spoolss_DeletePrinterDriver +****************************************************************/ + +WERROR _spoolss_DeletePrinterDriver(struct pipes_struct *p, + struct spoolss_DeletePrinterDriver *r) +{ + + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_DriverInfo8 *info = NULL; + int version; + WERROR status; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + int i; + bool found; + + /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege, + and not a printer admin, then fail */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege(session_info->security_token, + SEC_PRIV_PRINT_OPERATOR)) { + return WERR_ACCESS_DENIED; + } + + if (r->in.architecture == NULL || r->in.driver == NULL) { + return WERR_INVALID_ENVIRONMENT; + } + + /* check that we have a valid driver name first */ + + if ((version = get_version_id(r->in.architecture)) == -1) { + return WERR_INVALID_ENVIRONMENT; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + for (found = false, i = 0; drv_cversion[i] >= 0; i++) { + status = winreg_get_driver(tmp_ctx, b, + r->in.architecture, r->in.driver, + drv_cversion[i], &info); + if (!W_ERROR_IS_OK(status)) { + DEBUG(5, ("skipping del of driver with version %d\n", + drv_cversion[i])); + continue; + } + found = true; + + if (printer_driver_in_use(tmp_ctx, b, info)) { + status = WERR_PRINTER_DRIVER_IN_USE; + goto done; + } + + status = winreg_del_driver(tmp_ctx, b, info, drv_cversion[i]); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0, ("failed del of driver with version %d\n", + drv_cversion[i])); + goto done; + } + } + if (found == false) { + DEBUG(0, ("driver %s not found for deletion\n", r->in.driver)); + status = WERR_UNKNOWN_PRINTER_DRIVER; + } else { + status = WERR_OK; + } + +done: + talloc_free(tmp_ctx); + + return status; +} + +static WERROR spoolss_dpd_version(TALLOC_CTX *mem_ctx, + struct pipes_struct *p, + struct spoolss_DeletePrinterDriverEx *r, + struct dcerpc_binding_handle *b, + struct spoolss_DriverInfo8 *info) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR status; + bool delete_files; + + if (printer_driver_in_use(mem_ctx, b, info)) { + status = WERR_PRINTER_DRIVER_IN_USE; + goto done; + } + + /* + * we have a couple of cases to consider. + * (1) Are any files in use? If so and DPD_DELETE_ALL_FILES is set, + * then the delete should fail if **any** files overlap with + * other drivers + * (2) If DPD_DELETE_UNUSED_FILES is set, then delete all + * non-overlapping files + * (3) If neither DPD_DELETE_ALL_FILES nor DPD_DELETE_UNUSED_FILES + * are set, then do not delete any files + * Refer to MSDN docs on DeletePrinterDriverEx() for details. + */ + + delete_files = r->in.delete_flags + & (DPD_DELETE_ALL_FILES | DPD_DELETE_UNUSED_FILES); + + + if (delete_files) { + bool in_use = printer_driver_files_in_use(mem_ctx, b, info); + if (in_use && (r->in.delete_flags & DPD_DELETE_ALL_FILES)) { + status = WERR_PRINTER_DRIVER_IN_USE; + goto done; + } + /* + * printer_driver_files_in_use() has trimmed overlapping files + * from info so they are not removed on DPD_DELETE_UNUSED_FILES + */ + } + + + status = winreg_del_driver(mem_ctx, b, info, info->version); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + /* + * now delete any associated files if delete_files is + * true. Even if this part fails, we return success + * because the driver does not exist any more + */ + if (delete_files) { + delete_driver_files(session_info, info); + } + +done: + return status; +} + +/**************************************************************** + _spoolss_DeletePrinterDriverEx +****************************************************************/ + +WERROR _spoolss_DeletePrinterDriverEx(struct pipes_struct *p, + struct spoolss_DeletePrinterDriverEx *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_DriverInfo8 *info = NULL; + WERROR status; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + int i; + bool found; + + /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege, + and not a printer admin, then fail */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege(session_info->security_token, + SEC_PRIV_PRINT_OPERATOR)) { + return WERR_ACCESS_DENIED; + } + + if (r->in.architecture == NULL || r->in.driver == NULL) { + return WERR_INVALID_ENVIRONMENT; + } + + /* check that we have a valid driver name first */ + if (get_version_id(r->in.architecture) == -1) { + /* this is what NT returns */ + return WERR_INVALID_ENVIRONMENT; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + for (found = false, i = 0; drv_cversion[i] >= 0; i++) { + if ((r->in.delete_flags & DPD_DELETE_SPECIFIC_VERSION) + && (drv_cversion[i] != r->in.version)) { + continue; + } + + /* check if a driver with this version exists before delete */ + status = winreg_get_driver(tmp_ctx, b, + r->in.architecture, r->in.driver, + drv_cversion[i], &info); + if (!W_ERROR_IS_OK(status)) { + DEBUG(5, ("skipping del of driver with version %d\n", + drv_cversion[i])); + continue; + } + found = true; + + status = spoolss_dpd_version(tmp_ctx, p, r, b, info); + if (!W_ERROR_IS_OK(status)) { + DEBUG(0, ("failed to delete driver with version %d\n", + drv_cversion[i])); + goto done; + } + } + if (found == false) { + DEBUG(0, ("driver %s not found for deletion\n", r->in.driver)); + status = WERR_UNKNOWN_PRINTER_DRIVER; + } else { + status = WERR_OK; + } + +done: + talloc_free(tmp_ctx); + return status; +} + + +/******************************************************************** + GetPrinterData on a printer server Handle. +********************************************************************/ + +static WERROR getprinterdata_printer_server(TALLOC_CTX *mem_ctx, + const char *value, + enum winreg_Type *type, + union spoolss_PrinterData *data) +{ + DEBUG(8,("getprinterdata_printer_server:%s\n", value)); + + if (!strcasecmp_m(value, "W3SvcInstalled")) { + *type = REG_DWORD; + SIVAL(&data->value, 0, 0x00); + return WERR_OK; + } + + if (!strcasecmp_m(value, "BeepEnabled")) { + *type = REG_DWORD; + SIVAL(&data->value, 0, 0x00); + return WERR_OK; + } + + if (!strcasecmp_m(value, "EventLog")) { + *type = REG_DWORD; + /* formally was 0x1b */ + SIVAL(&data->value, 0, 0x00); + return WERR_OK; + } + + if (!strcasecmp_m(value, "NetPopup")) { + *type = REG_DWORD; + SIVAL(&data->value, 0, 0x00); + return WERR_OK; + } + + if (!strcasecmp_m(value, "MajorVersion")) { + *type = REG_DWORD; + + /* Windows NT 4.0 seems to not allow uploading of drivers + to a server that reports 0x3 as the MajorVersion. + need to investigate more how Win2k gets around this . + -- jerry */ + + if (RA_WINNT == get_remote_arch()) { + SIVAL(&data->value, 0, 0x02); + } else { + SIVAL(&data->value, 0, 0x03); + } + + return WERR_OK; + } + + if (!strcasecmp_m(value, "MinorVersion")) { + *type = REG_DWORD; + SIVAL(&data->value, 0, 0x00); + return WERR_OK; + } + + /* REG_BINARY + * uint32_t size = 0x114 + * uint32_t major = 5 + * uint32_t minor = [0|1] + * uint32_t build = [2195|2600] + * extra unicode string = e.g. "Service Pack 3" + */ + if (!strcasecmp_m(value, "OSVersion")) { + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct spoolss_OSVersion os; + + /* + * Set the default OSVersion to: + * + * Windows Server 2003R2 SP2 (5.2.3790) + * + * used to be Windows 2000 (5.0.2195) + */ + os.major = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_major", + GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT); + os.minor = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_minor", + GLOBAL_SPOOLSS_OS_MINOR_DEFAULT); + os.build = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_build", + GLOBAL_SPOOLSS_OS_BUILD_DEFAULT); + os.extra_string = ""; /* leave extra string empty */ + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &os, + (ndr_push_flags_fn_t)ndr_push_spoolss_OSVersion); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(spoolss_OSVersion, &os); + } + + *type = REG_BINARY; + data->binary = blob; + + return WERR_OK; + } + + + if (!strcasecmp_m(value, "DefaultSpoolDirectory")) { + *type = REG_SZ; + + data->string = talloc_strdup(mem_ctx, SPOOLSS_DEFAULT_SERVER_PATH); + W_ERROR_HAVE_NO_MEMORY(data->string); + + return WERR_OK; + } + + if (!strcasecmp_m(value, "Architecture")) { + *type = REG_SZ; + data->string = talloc_strdup(mem_ctx, + lp_parm_const_string(GLOBAL_SECTION_SNUM, "spoolss", "architecture", GLOBAL_SPOOLSS_ARCHITECTURE)); + W_ERROR_HAVE_NO_MEMORY(data->string); + + return WERR_OK; + } + + if (!strcasecmp_m(value, "DsPresent")) { + *type = REG_DWORD; + + /* only show the publish check box if we are a + member of a AD domain */ + + if (lp_security() == SEC_ADS) { + SIVAL(&data->value, 0, 0x01); + } else { + SIVAL(&data->value, 0, 0x00); + } + return WERR_OK; + } + + if (!strcasecmp_m(value, "DNSMachineName")) { + const char *hostname = get_mydnsfullname(); + + if (!hostname) { + return WERR_FILE_NOT_FOUND; + } + + *type = REG_SZ; + data->string = talloc_strdup(mem_ctx, hostname); + W_ERROR_HAVE_NO_MEMORY(data->string); + + return WERR_OK; + } + + *type = REG_NONE; + + return WERR_INVALID_PARAMETER; +} + +/**************************************************************** + _spoolss_GetPrinterData +****************************************************************/ + +WERROR _spoolss_GetPrinterData(struct pipes_struct *p, + struct spoolss_GetPrinterData *r) +{ + struct spoolss_GetPrinterDataEx r2; + + r2.in.handle = r->in.handle; + r2.in.key_name = "PrinterDriverData"; + r2.in.value_name = r->in.value_name; + r2.in.offered = r->in.offered; + r2.out.type = r->out.type; + r2.out.data = r->out.data; + r2.out.needed = r->out.needed; + + return _spoolss_GetPrinterDataEx(p, &r2); +} + +/********************************************************* + Connect to the client machine. +**********************************************************/ + +static bool spoolss_connect_to_client(struct rpc_pipe_client **pp_pipe, struct cli_state **pp_cli, + struct sockaddr_storage *client_ss, const char *remote_machine) +{ + NTSTATUS ret; + struct sockaddr_storage rm_addr; + char addr[INET6_ADDRSTRLEN]; + struct cli_credentials *anon_creds = NULL; + + if ( is_zero_addr(client_ss) ) { + DEBUG(2,("spoolss_connect_to_client: resolving %s\n", + remote_machine)); + if ( !resolve_name( remote_machine, &rm_addr, 0x20, false) ) { + DEBUG(2,("spoolss_connect_to_client: Can't resolve address for %s\n", remote_machine)); + return false; + } + print_sockaddr(addr, sizeof(addr), &rm_addr); + } else { + rm_addr = *client_ss; + print_sockaddr(addr, sizeof(addr), &rm_addr); + DEBUG(5,("spoolss_connect_to_client: Using address %s (no name resolution necessary)\n", + addr)); + } + + if (ismyaddr((struct sockaddr *)(void *)&rm_addr)) { + DEBUG(0,("spoolss_connect_to_client: Machine %s is one of our addresses. Cannot add to ourselves.\n", + addr)); + return false; + } + + anon_creds = cli_credentials_init_anon(NULL); + if (anon_creds == NULL) { + DBG_ERR("cli_credentials_init_anon() failed\n"); + return false; + } + + /* setup the connection */ + ret = cli_full_connection_creds( pp_cli, lp_netbios_name(), remote_machine, + &rm_addr, 0, "IPC$", "IPC", + anon_creds, + CLI_FULL_CONNECTION_IPC); + TALLOC_FREE(anon_creds); + if ( !NT_STATUS_IS_OK( ret ) ) { + DEBUG(2,("spoolss_connect_to_client: connection to [%s] failed!\n", + remote_machine )); + return false; + } + + if ( smbXcli_conn_protocol((*pp_cli)->conn) < PROTOCOL_NT1 ) { + DEBUG(0,("spoolss_connect_to_client: machine %s didn't negotiate NT protocol.\n", remote_machine)); + cli_shutdown(*pp_cli); + return false; + } + + /* + * Ok - we have an anonymous connection to the IPC$ share. + * Now start the NT Domain stuff :-). + */ + + ret = cli_rpc_pipe_open_noauth(*pp_cli, &ndr_table_spoolss, pp_pipe); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(2,("spoolss_connect_to_client: unable to open the spoolss pipe on machine %s. Error was : %s.\n", + remote_machine, nt_errstr(ret))); + cli_shutdown(*pp_cli); + return false; + } + + return true; +} + +/*************************************************************************** + Connect to the client. +****************************************************************************/ + +static bool srv_spoolss_replyopenprinter(int snum, const char *printer, + uint32_t localprinter, + enum winreg_Type type, + struct policy_handle *handle, + struct notify_back_channel **_chan, + struct sockaddr_storage *client_ss, + struct messaging_context *msg_ctx) +{ + WERROR result; + NTSTATUS status; + struct notify_back_channel *chan; + + for (chan = back_channels; chan; chan = chan->next) { + if (memcmp(&chan->client_address, client_ss, + sizeof(struct sockaddr_storage)) == 0) { + break; + } + } + + /* + * If it's the first connection, contact the client + * and connect to the IPC$ share anonymously + */ + if (!chan) { + fstring unix_printer; + + /* the +2 is to strip the leading 2 backslashes */ + fstrcpy(unix_printer, printer + 2); + + chan = talloc_zero(NULL, struct notify_back_channel); + if (!chan) { + return false; + } + chan->client_address = *client_ss; + + if (!spoolss_connect_to_client(&chan->cli_pipe, &chan->cli, client_ss, unix_printer)) { + TALLOC_FREE(chan); + return false; + } + + DLIST_ADD(back_channels, chan); + + messaging_register(msg_ctx, NULL, MSG_PRINTER_NOTIFY2, + receive_notify2_message_list); + } + + if (chan->cli_pipe == NULL || + chan->cli_pipe->binding_handle == NULL) { + DEBUG(0, ("srv_spoolss_replyopenprinter: error - " + "NULL %s for printer %s\n", + chan->cli_pipe == NULL ? + "chan->cli_pipe" : "chan->cli_pipe->binding_handle", + printer)); + return false; + } + + /* + * Tell the specific printing tdb we want messages for this printer + * by registering our PID. + */ + + if (!print_notify_register_pid(snum)) { + DEBUG(0, ("Failed to register our pid for printer %s\n", + printer)); + } + + status = dcerpc_spoolss_ReplyOpenPrinter(chan->cli_pipe->binding_handle, + talloc_tos(), + printer, + localprinter, + type, + 0, + NULL, + handle, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("dcerpc_spoolss_ReplyOpenPrinter returned [%s]\n", nt_errstr(status))); + result = ntstatus_to_werror(status); + } else if (!W_ERROR_IS_OK(result)) { + DEBUG(5, ("ReplyOpenPrinter returned [%s]\n", win_errstr(result))); + } + + chan->active_connections++; + *_chan = chan; + + return (W_ERROR_IS_OK(result)); +} + +/**************************************************************** + ****************************************************************/ + +static struct spoolss_NotifyOption *dup_spoolss_NotifyOption(TALLOC_CTX *mem_ctx, + const struct spoolss_NotifyOption *r) +{ + struct spoolss_NotifyOption *option; + uint32_t i,k; + + if (!r) { + return NULL; + } + + option = talloc_zero(mem_ctx, struct spoolss_NotifyOption); + if (!option) { + return NULL; + } + + *option = *r; + + if (!option->count) { + return option; + } + + option->types = talloc_zero_array(option, + struct spoolss_NotifyOptionType, option->count); + if (!option->types) { + talloc_free(option); + return NULL; + } + + for (i=0; i < option->count; i++) { + option->types[i] = r->types[i]; + + if (option->types[i].count) { + option->types[i].fields = talloc_zero_array(option, + union spoolss_Field, option->types[i].count); + if (!option->types[i].fields) { + talloc_free(option); + return NULL; + } + for (k=0; k<option->types[i].count; k++) { + option->types[i].fields[k] = + r->types[i].fields[k]; + } + } + } + + return option; +} + +/**************************************************************** + * _spoolss_RemoteFindFirstPrinterChangeNotifyEx + * + * before replying OK: status=0 a rpc call is made to the workstation + * asking ReplyOpenPrinter + * + * in fact ReplyOpenPrinter is the changenotify equivalent on the spoolss pipe + * called from api_spoolss_rffpcnex +****************************************************************/ + +WERROR _spoolss_RemoteFindFirstPrinterChangeNotifyEx(struct pipes_struct *p, + struct spoolss_RemoteFindFirstPrinterChangeNotifyEx *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + int snum = -1; + struct spoolss_NotifyOption *option = r->in.notify_options; + struct sockaddr_storage client_ss; + ssize_t client_len; + + /* store the notify value in the printer struct */ + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(2,("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: " + "Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + Printer->notify.flags = r->in.flags; + Printer->notify.options = r->in.options; + Printer->notify.printerlocal = r->in.printer_local; + Printer->notify.msg_ctx = p->msg_ctx; + + TALLOC_FREE(Printer->notify.option); + Printer->notify.option = dup_spoolss_NotifyOption(Printer, option); + + fstrcpy(Printer->notify.localmachine, r->in.local_machine); + + /* Connect to the client machine and send a ReplyOpenPrinter */ + + if ( Printer->printer_type == SPLHND_SERVER) + snum = -1; + else if ( (Printer->printer_type == SPLHND_PRINTER) && + !get_printer_snum(p, r->in.handle, &snum, NULL) ) + return WERR_INVALID_HANDLE; + + DEBUG(10,("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: " + "remote_address is %s\n", + tsocket_address_string(remote_address, p->mem_ctx))); + + if (!lp_print_notify_backchannel(snum)) { + DEBUG(10, ("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: " + "backchannel disabled\n")); + return WERR_RPC_S_SERVER_UNAVAILABLE; + } + + client_len = tsocket_address_bsd_sockaddr(remote_address, + (struct sockaddr *) &client_ss, + sizeof(struct sockaddr_storage)); + if (client_len < 0) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if(!srv_spoolss_replyopenprinter(snum, Printer->notify.localmachine, + Printer->notify.printerlocal, REG_SZ, + &Printer->notify.cli_hnd, + &Printer->notify.cli_chan, + &client_ss, p->msg_ctx)) { + return WERR_RPC_S_SERVER_UNAVAILABLE; + } + + return WERR_OK; +} + +/******************************************************************* + * fill a notify_info_data with the servername + ********************************************************************/ + +static void spoolss_notify_server_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->servername); +} + +/******************************************************************* + * fill a notify_info_data with the printername (not including the servername). + ********************************************************************/ + +static void spoolss_notify_printer_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + /* the notify name should not contain the \\server\ part */ + const char *p = strrchr(pinfo2->printername, '\\'); + + if (!p) { + p = pinfo2->printername; + } else { + p++; + } + + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p); +} + +/******************************************************************* + * fill a notify_info_data with the servicename + ********************************************************************/ + +static void spoolss_notify_share_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, lp_servicename(talloc_tos(), lp_sub, snum)); +} + +/******************************************************************* + * fill a notify_info_data with the port name + ********************************************************************/ + +static void spoolss_notify_port_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->portname); +} + +/******************************************************************* + * fill a notify_info_data with the printername + * but it doesn't exist, have to see what to do + ********************************************************************/ + +static void spoolss_notify_driver_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->drivername); +} + +/******************************************************************* + * fill a notify_info_data with the comment + ********************************************************************/ + +static void spoolss_notify_comment(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *p; + + if (*pinfo2->comment == '\0') { + p = lp_comment(talloc_tos(), lp_sub, snum); + } else { + p = pinfo2->comment; + } + + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p); +} + +/******************************************************************* + * fill a notify_info_data with the comment + * location = "Room 1, floor 2, building 3" + ********************************************************************/ + +static void spoolss_notify_location(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + const char *loc = pinfo2->location; + NTSTATUS status; + + status = printer_list_get_printer(mem_ctx, + pinfo2->sharename, + NULL, + &loc, + NULL); + if (NT_STATUS_IS_OK(status)) { + if (loc == NULL) { + loc = pinfo2->location; + } + } + + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, loc); +} + +/******************************************************************* + * fill a notify_info_data with the device mode + * jfm:xxxx don't to it for know but that's a real problem !!! + ********************************************************************/ + +static void spoolss_notify_devmode(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + /* for a dummy implementation we have to zero the fields */ + SETUP_SPOOLSS_NOTIFY_DATA_DEVMODE(data, NULL); +} + +/******************************************************************* + * fill a notify_info_data with the separator file name + ********************************************************************/ + +static void spoolss_notify_sepfile(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->sepfile); +} + +/******************************************************************* + * fill a notify_info_data with the print processor + * jfm:xxxx return always winprint to indicate we don't do anything to it + ********************************************************************/ + +static void spoolss_notify_print_processor(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->printprocessor); +} + +/******************************************************************* + * fill a notify_info_data with the print processor options + * jfm:xxxx send an empty string + ********************************************************************/ + +static void spoolss_notify_parameters(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->parameters); +} + +/******************************************************************* + * fill a notify_info_data with the data type + * jfm:xxxx always send RAW as data type + ********************************************************************/ + +static void spoolss_notify_datatype(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->datatype); +} + +/******************************************************************* + * fill a notify_info_data with the security descriptor + * jfm:xxxx send an null pointer to say no security desc + * have to implement security before ! + ********************************************************************/ + +static void spoolss_notify_security_desc(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + if (pinfo2->secdesc == NULL) { + data->data.sd.sd = NULL; + } else { + data->data.sd.sd = security_descriptor_copy(mem_ctx, + pinfo2->secdesc); + } + data->data.sd.sd_size = ndr_size_security_descriptor(data->data.sd.sd, + 0); +} + +/******************************************************************* + * fill a notify_info_data with the attributes + * jfm:xxxx a samba printer is always shared + ********************************************************************/ + +static void spoolss_notify_attributes(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->attributes); +} + +/******************************************************************* + * fill a notify_info_data with the priority + ********************************************************************/ + +static void spoolss_notify_priority(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->priority); +} + +/******************************************************************* + * fill a notify_info_data with the default priority + ********************************************************************/ + +static void spoolss_notify_default_priority(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->defaultpriority); +} + +/******************************************************************* + * fill a notify_info_data with the start time + ********************************************************************/ + +static void spoolss_notify_start_time(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->starttime); +} + +/******************************************************************* + * fill a notify_info_data with the until time + ********************************************************************/ + +static void spoolss_notify_until_time(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->untiltime); +} + +/******************************************************************* + * fill a notify_info_data with the status + ********************************************************************/ + +static void spoolss_notify_status(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + print_status_struct status; + + print_queue_length(msg_ctx, snum, &status); + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, status.status); +} + +/******************************************************************* + * fill a notify_info_data with the number of jobs queued + ********************************************************************/ + +static void spoolss_notify_cjobs(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER( + data, print_queue_length(msg_ctx, snum, NULL)); +} + +/******************************************************************* + * fill a notify_info_data with the average ppm + ********************************************************************/ + +static void spoolss_notify_average_ppm(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + /* always respond 8 pages per minutes */ + /* a little hard ! */ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->averageppm); +} + +/******************************************************************* + * fill a notify_info_data with username + ********************************************************************/ + +static void spoolss_notify_username(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, queue->fs_user); +} + +/******************************************************************* + * fill a notify_info_data with job status + ********************************************************************/ + +static void spoolss_notify_job_status(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, nt_printj_status(queue->status)); +} + +/******************************************************************* + * fill a notify_info_data with job name + ********************************************************************/ + +static void spoolss_notify_job_name(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, queue->fs_file); +} + +/******************************************************************* + * fill a notify_info_data with job status + ********************************************************************/ + +static void spoolss_notify_job_status_string(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + /* + * Now we're returning job status codes we just return a "" here. JRA. + */ + + const char *p = ""; + +#if 0 /* NO LONGER NEEDED - JRA. 02/22/2001 */ + p = "unknown"; + + switch (queue->status) { + case LPQ_QUEUED: + p = "Queued"; + break; + case LPQ_PAUSED: + p = ""; /* NT provides the paused string */ + break; + case LPQ_SPOOLING: + p = "Spooling"; + break; + case LPQ_PRINTING: + p = "Printing"; + break; + } +#endif /* NO LONGER NEEDED. */ + + SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p); +} + +/******************************************************************* + * fill a notify_info_data with job time + ********************************************************************/ + +static void spoolss_notify_job_time(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, 0); +} + +/******************************************************************* + * fill a notify_info_data with job size + ********************************************************************/ + +static void spoolss_notify_job_size(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->size); +} + +/******************************************************************* + * fill a notify_info_data with page info + ********************************************************************/ +static void spoolss_notify_total_pages(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->page_count); +} + +/******************************************************************* + * fill a notify_info_data with pages printed info. + ********************************************************************/ +static void spoolss_notify_pages_printed(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + /* Add code when back-end tracks this */ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, 0); +} + +/******************************************************************* + Fill a notify_info_data with job position. + ********************************************************************/ + +static void spoolss_notify_job_position(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->sysjob); +} + +/******************************************************************* + Fill a notify_info_data with submitted time. + ********************************************************************/ + +static void spoolss_notify_submitted_time(struct messaging_context *msg_ctx, + int snum, + struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx) +{ + data->data.string.string = NULL; + data->data.string.size = 0; + + init_systemtime_buffer(mem_ctx, gmtime(&queue->time), + &data->data.string.string, + &data->data.string.size); + +} + +struct s_notify_info_data_table +{ + enum spoolss_NotifyType type; + uint16_t field; + const char *name; + enum spoolss_NotifyTable variable_type; + void (*fn) (struct messaging_context *msg_ctx, + int snum, struct spoolss_Notify *data, + print_queue_struct *queue, + struct spoolss_PrinterInfo2 *pinfo2, + TALLOC_CTX *mem_ctx); +}; + +/* A table describing the various print notification constants and + whether the notification data is a pointer to a variable sized + buffer, a one value uint32_t or a two value uint32_t. */ + +static const struct s_notify_info_data_table notify_info_data_table[] = +{ +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SERVER_NAME, "PRINTER_NOTIFY_FIELD_SERVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_server_name }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINTER_NAME, "PRINTER_NOTIFY_FIELD_PRINTER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_printer_name }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SHARE_NAME, "PRINTER_NOTIFY_FIELD_SHARE_NAME", NOTIFY_TABLE_STRING, spoolss_notify_share_name }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PORT_NAME, "PRINTER_NOTIFY_FIELD_PORT_NAME", NOTIFY_TABLE_STRING, spoolss_notify_port_name }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DRIVER_NAME, "PRINTER_NOTIFY_FIELD_DRIVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_driver_name }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_COMMENT, "PRINTER_NOTIFY_FIELD_COMMENT", NOTIFY_TABLE_STRING, spoolss_notify_comment }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_LOCATION, "PRINTER_NOTIFY_FIELD_LOCATION", NOTIFY_TABLE_STRING, spoolss_notify_location }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DEVMODE, "PRINTER_NOTIFY_FIELD_DEVMODE", NOTIFY_TABLE_DEVMODE, spoolss_notify_devmode }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SEPFILE, "PRINTER_NOTIFY_FIELD_SEPFILE", NOTIFY_TABLE_STRING, spoolss_notify_sepfile }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR, "PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR", NOTIFY_TABLE_STRING, spoolss_notify_print_processor }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PARAMETERS, "PRINTER_NOTIFY_FIELD_PARAMETERS", NOTIFY_TABLE_STRING, spoolss_notify_parameters }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DATATYPE, "PRINTER_NOTIFY_FIELD_DATATYPE", NOTIFY_TABLE_STRING, spoolss_notify_datatype }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR, "PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NOTIFY_TABLE_SECURITYDESCRIPTOR, spoolss_notify_security_desc }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_ATTRIBUTES, "PRINTER_NOTIFY_FIELD_ATTRIBUTES", NOTIFY_TABLE_DWORD, spoolss_notify_attributes }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRIORITY, "PRINTER_NOTIFY_FIELD_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_priority }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY, "PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_default_priority }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_START_TIME, "PRINTER_NOTIFY_FIELD_START_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_start_time }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_UNTIL_TIME, "PRINTER_NOTIFY_FIELD_UNTIL_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_until_time }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_STATUS, "PRINTER_NOTIFY_FIELD_STATUS", NOTIFY_TABLE_DWORD, spoolss_notify_status }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_STATUS_STRING, "PRINTER_NOTIFY_FIELD_STATUS_STRING", NOTIFY_TABLE_STRING, NULL }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_CJOBS, "PRINTER_NOTIFY_FIELD_CJOBS", NOTIFY_TABLE_DWORD, spoolss_notify_cjobs }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_AVERAGE_PPM, "PRINTER_NOTIFY_FIELD_AVERAGE_PPM", NOTIFY_TABLE_DWORD, spoolss_notify_average_ppm }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_TOTAL_PAGES, "PRINTER_NOTIFY_FIELD_TOTAL_PAGES", NOTIFY_TABLE_DWORD, NULL }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PAGES_PRINTED, "PRINTER_NOTIFY_FIELD_PAGES_PRINTED", NOTIFY_TABLE_DWORD, NULL }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_TOTAL_BYTES, "PRINTER_NOTIFY_FIELD_TOTAL_BYTES", NOTIFY_TABLE_DWORD, NULL }, +{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_BYTES_PRINTED, "PRINTER_NOTIFY_FIELD_BYTES_PRINTED", NOTIFY_TABLE_DWORD, NULL }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRINTER_NAME, "JOB_NOTIFY_FIELD_PRINTER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_printer_name }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_MACHINE_NAME, "JOB_NOTIFY_FIELD_MACHINE_NAME", NOTIFY_TABLE_STRING, spoolss_notify_server_name }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PORT_NAME, "JOB_NOTIFY_FIELD_PORT_NAME", NOTIFY_TABLE_STRING, spoolss_notify_port_name }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_USER_NAME, "JOB_NOTIFY_FIELD_USER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_username }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_NOTIFY_NAME, "JOB_NOTIFY_FIELD_NOTIFY_NAME", NOTIFY_TABLE_STRING, spoolss_notify_username }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DATATYPE, "JOB_NOTIFY_FIELD_DATATYPE", NOTIFY_TABLE_STRING, spoolss_notify_datatype }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRINT_PROCESSOR, "JOB_NOTIFY_FIELD_PRINT_PROCESSOR", NOTIFY_TABLE_STRING, spoolss_notify_print_processor }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PARAMETERS, "JOB_NOTIFY_FIELD_PARAMETERS", NOTIFY_TABLE_STRING, spoolss_notify_parameters }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DRIVER_NAME, "JOB_NOTIFY_FIELD_DRIVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_driver_name }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DEVMODE, "JOB_NOTIFY_FIELD_DEVMODE", NOTIFY_TABLE_DEVMODE, spoolss_notify_devmode }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_STATUS, "JOB_NOTIFY_FIELD_STATUS", NOTIFY_TABLE_DWORD, spoolss_notify_job_status }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_STATUS_STRING, "JOB_NOTIFY_FIELD_STATUS_STRING", NOTIFY_TABLE_STRING, spoolss_notify_job_status_string }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR, "JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NOTIFY_TABLE_SECURITYDESCRIPTOR, NULL }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DOCUMENT, "JOB_NOTIFY_FIELD_DOCUMENT", NOTIFY_TABLE_STRING, spoolss_notify_job_name }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRIORITY, "JOB_NOTIFY_FIELD_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_priority }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_POSITION, "JOB_NOTIFY_FIELD_POSITION", NOTIFY_TABLE_DWORD, spoolss_notify_job_position }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SUBMITTED, "JOB_NOTIFY_FIELD_SUBMITTED", NOTIFY_TABLE_TIME, spoolss_notify_submitted_time }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_START_TIME, "JOB_NOTIFY_FIELD_START_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_start_time }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_UNTIL_TIME, "JOB_NOTIFY_FIELD_UNTIL_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_until_time }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TIME, "JOB_NOTIFY_FIELD_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_job_time }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TOTAL_PAGES, "JOB_NOTIFY_FIELD_TOTAL_PAGES", NOTIFY_TABLE_DWORD, spoolss_notify_total_pages }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PAGES_PRINTED, "JOB_NOTIFY_FIELD_PAGES_PRINTED", NOTIFY_TABLE_DWORD, spoolss_notify_pages_printed }, +{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TOTAL_BYTES, "JOB_NOTIFY_FIELD_TOTAL_BYTES", NOTIFY_TABLE_DWORD, spoolss_notify_job_size }, +}; + +/******************************************************************* + Return the variable_type of info_data structure. +********************************************************************/ + +static enum spoolss_NotifyTable variable_type_of_notify_info_data(enum spoolss_NotifyType type, + uint16_t field) +{ + int i=0; + + for (i = 0; i < ARRAY_SIZE(notify_info_data_table); i++) { + if ( (notify_info_data_table[i].type == type) && + (notify_info_data_table[i].field == field) ) { + return notify_info_data_table[i].variable_type; + } + } + + DEBUG(5, ("invalid notify data type %d/%d\n", type, field)); + + return (enum spoolss_NotifyTable) 0; +} + +/**************************************************************************** +****************************************************************************/ + +static bool search_notify(enum spoolss_NotifyType type, + uint16_t field, + int *value) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(notify_info_data_table); i++) { + if (notify_info_data_table[i].type == type && + notify_info_data_table[i].field == field && + notify_info_data_table[i].fn != NULL) { + *value = i; + return true; + } + } + + return false; +} + +/**************************************************************************** +****************************************************************************/ + +static void construct_info_data(struct spoolss_Notify *info_data, + enum spoolss_NotifyType type, + uint16_t field, int id) +{ + info_data->type = type; + info_data->field.field = field; + info_data->variable_type = variable_type_of_notify_info_data(type, field); + info_data->job_id = id; +} + +/******************************************************************* + * + * fill a notify_info struct with info asked + * + ********************************************************************/ + +static bool construct_notify_printer_info(struct messaging_context *msg_ctx, + struct printer_handle *print_hnd, + struct spoolss_NotifyInfo *info, + struct spoolss_PrinterInfo2 *pinfo2, + int snum, + const struct spoolss_NotifyOptionType *option_type, + uint32_t id, + TALLOC_CTX *mem_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int field_num,j; + enum spoolss_NotifyType type; + uint16_t field; + + struct spoolss_Notify *current_data; + + type = option_type->type; + + DEBUG(4,("construct_notify_printer_info: Notify type: [%s], number of notify info: [%d] on printer: [%s]\n", + (type == PRINTER_NOTIFY_TYPE ? "PRINTER_NOTIFY_TYPE" : "JOB_NOTIFY_TYPE"), + option_type->count, lp_servicename(talloc_tos(), lp_sub, snum))); + + for(field_num=0; field_num < option_type->count; field_num++) { + field = option_type->fields[field_num].field; + + DEBUG(4,("construct_notify_printer_info: notify [%d]: type [%x], field [%x]\n", field_num, type, field)); + + if (!search_notify(type, field, &j) ) + continue; + + info->notifies = talloc_realloc(info, info->notifies, + struct spoolss_Notify, + info->count + 1); + if (info->notifies == NULL) { + DEBUG(2,("construct_notify_printer_info: failed to enlarge buffer info->data!\n")); + return false; + } + + current_data = &info->notifies[info->count]; + + construct_info_data(current_data, type, field, id); + + DEBUG(10, ("construct_notify_printer_info: " + "calling [%s] snum=%d printername=[%s])\n", + notify_info_data_table[j].name, snum, + pinfo2->printername)); + + notify_info_data_table[j].fn(msg_ctx, snum, current_data, + NULL, pinfo2, mem_ctx); + + info->count++; + } + + return true; +} + +/******************************************************************* + * + * fill a notify_info struct with info asked + * + ********************************************************************/ + +static bool construct_notify_jobs_info(struct messaging_context *msg_ctx, + print_queue_struct *queue, + struct spoolss_NotifyInfo *info, + struct spoolss_PrinterInfo2 *pinfo2, + int snum, + const struct spoolss_NotifyOptionType *option_type, + uint32_t id, + TALLOC_CTX *mem_ctx) +{ + int field_num,j; + enum spoolss_NotifyType type; + uint16_t field; + struct spoolss_Notify *current_data; + + DEBUG(4,("construct_notify_jobs_info\n")); + + type = option_type->type; + + DEBUGADD(4,("Notify type: [%s], number of notify info: [%d]\n", + (type == PRINTER_NOTIFY_TYPE ? "PRINTER_NOTIFY_TYPE" : "JOB_NOTIFY_TYPE"), + option_type->count)); + + for(field_num=0; field_num<option_type->count; field_num++) { + field = option_type->fields[field_num].field; + + if (!search_notify(type, field, &j) ) + continue; + + info->notifies = talloc_realloc(info, info->notifies, + struct spoolss_Notify, + info->count + 1); + if (info->notifies == NULL) { + DEBUG(2,("construct_notify_jobs_info: failed to enlarge buffer info->data!\n")); + return false; + } + + current_data=&(info->notifies[info->count]); + + construct_info_data(current_data, type, field, id); + notify_info_data_table[j].fn(msg_ctx, snum, current_data, + queue, pinfo2, mem_ctx); + info->count++; + } + + return true; +} + +/* + * JFM: The enumeration is not that simple, it's even non obvious. + * + * let's take an example: I want to monitor the PRINTER SERVER for + * the printer's name and the number of jobs currently queued. + * So in the NOTIFY_OPTION, I have one NOTIFY_OPTION_TYPE structure. + * Its type is PRINTER_NOTIFY_TYPE and it has 2 fields NAME and CJOBS. + * + * I have 3 printers on the back of my server. + * + * Now the response is a NOTIFY_INFO structure, with 6 NOTIFY_INFO_DATA + * structures. + * Number Data Id + * 1 printer 1 name 1 + * 2 printer 1 cjob 1 + * 3 printer 2 name 2 + * 4 printer 2 cjob 2 + * 5 printer 3 name 3 + * 6 printer 3 name 3 + * + * that's the print server case, the printer case is even worse. + */ + +/******************************************************************* + * + * enumerate all printers on the printserver + * fill a notify_info struct with info asked + * + ********************************************************************/ + +static WERROR printserver_notify_info(struct pipes_struct *p, + struct policy_handle *hnd, + struct spoolss_NotifyInfo *info, + TALLOC_CTX *mem_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int snum; + struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd); + int n_services=lp_numservices(); + int i; + struct spoolss_NotifyOption *option; + struct spoolss_NotifyOptionType option_type; + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + WERROR result; + + DEBUG(4,("printserver_notify_info\n")); + + if (!Printer) + return WERR_INVALID_HANDLE; + + option = Printer->notify.option; + + info->version = 2; + info->notifies = NULL; + info->count = 0; + + /* a bug in xp sp2 rc2 causes it to send a fnpcn request without + sending a ffpcn() request first */ + + if ( !option ) + return WERR_INVALID_HANDLE; + + for (i=0; i<option->count; i++) { + option_type = option->types[i]; + + if (option_type.type != PRINTER_NOTIFY_TYPE) + continue; + + for (snum = 0; snum < n_services; snum++) { + if (!lp_browseable(snum) || + !lp_snum_ok(snum) || + !lp_printable(snum)) { + continue; /* skip */ + } + + /* Maybe we should use the SYSTEM session_info here... */ + result = winreg_get_printer_internal(mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_servicename(talloc_tos(), lp_sub, snum), + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + DEBUG(4, ("printserver_notify_info: " + "Failed to get printer [%s]\n", + lp_servicename(talloc_tos(), lp_sub, snum))); + continue; + } + + + construct_notify_printer_info(p->msg_ctx, + Printer, info, + pinfo2, snum, + &option_type, snum, + mem_ctx); + + TALLOC_FREE(pinfo2); + } + } + +#if 0 + /* + * Debugging information, don't delete. + */ + + DEBUG(1,("dumping the NOTIFY_INFO\n")); + DEBUGADD(1,("info->version:[%d], info->flags:[%d], info->count:[%d]\n", info->version, info->flags, info->count)); + DEBUGADD(1,("num\ttype\tfield\tres\tid\tsize\tenc_type\n")); + + for (i=0; i<info->count; i++) { + DEBUGADD(1,("[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\n", + i, info->data[i].type, info->data[i].field, info->data[i].reserved, + info->data[i].id, info->data[i].size, info->data[i].enc_type)); + } +#endif + + return WERR_OK; +} + +/******************************************************************* + * + * fill a notify_info struct with info asked + * + ********************************************************************/ + +static WERROR printer_notify_info(struct pipes_struct *p, + struct policy_handle *hnd, + struct spoolss_NotifyInfo *info, + TALLOC_CTX *mem_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int snum; + struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd); + int i; + uint32_t id; + struct spoolss_NotifyOption *option; + struct spoolss_NotifyOptionType option_type; + int count,j; + print_queue_struct *queue=NULL; + print_status_struct status; + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + WERROR result; + struct tdb_print_db *pdb; + + DEBUG(4,("printer_notify_info\n")); + + if (!Printer) + return WERR_INVALID_HANDLE; + + option = Printer->notify.option; + id = 0x0; + + info->version = 2; + info->notifies = NULL; + info->count = 0; + + /* a bug in xp sp2 rc2 causes it to send a fnpcn request without + sending a ffpcn() request first */ + + if ( !option ) + return WERR_INVALID_HANDLE; + + if (!get_printer_snum(p, hnd, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + pdb = get_print_db_byname(Printer->sharename); + if (pdb == NULL) { + return WERR_INVALID_HANDLE; + } + + /* Maybe we should use the SYSTEM session_info here... */ + result = winreg_get_printer_internal(mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_servicename(talloc_tos(), lp_sub, snum), &pinfo2); + if (!W_ERROR_IS_OK(result)) { + result = WERR_INVALID_HANDLE; + goto err_pdb_drop; + } + + /* + * When sending a PRINTER_NOTIFY_FIELD_SERVER_NAME we should send the + * correct servername. + */ + pinfo2->servername = talloc_strdup(pinfo2, Printer->servername); + if (pinfo2->servername == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto err_pdb_drop; + } + + for (i = 0; i < option->count; i++) { + option_type = option->types[i]; + + switch (option_type.type) { + case PRINTER_NOTIFY_TYPE: + if (construct_notify_printer_info(p->msg_ctx, + Printer, info, + pinfo2, snum, + &option_type, id, + mem_ctx)) { + id--; + } + break; + + case JOB_NOTIFY_TYPE: + + count = print_queue_status(p->msg_ctx, snum, &queue, + &status); + + for (j = 0; j < count; j++) { + uint32_t jobid; + jobid = sysjob_to_jobid_pdb(pdb, + queue[j].sysjob); + if (jobid == (uint32_t)-1) { + DEBUG(2, ("ignoring untracked job %d\n", + queue[j].sysjob)); + continue; + } + /* FIXME check return value */ + construct_notify_jobs_info(p->msg_ctx, + &queue[j], info, + pinfo2, snum, + &option_type, + jobid, + mem_ctx); + } + + SAFE_FREE(queue); + break; + } + } + + /* + * Debugging information, don't delete. + */ + /* + DEBUG(1,("dumping the NOTIFY_INFO\n")); + DEBUGADD(1,("info->version:[%d], info->flags:[%d], info->count:[%d]\n", info->version, info->flags, info->count)); + DEBUGADD(1,("num\ttype\tfield\tres\tid\tsize\tenc_type\n")); + + for (i=0; i<info->count; i++) { + DEBUGADD(1,("[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\n", + i, info->data[i].type, info->data[i].field, info->data[i].reserved, + info->data[i].id, info->data[i].size, info->data[i].enc_type)); + } + */ + + talloc_free(pinfo2); + result = WERR_OK; +err_pdb_drop: + release_print_db(pdb); + return result; +} + +/**************************************************************** + _spoolss_RouterRefreshPrinterChangeNotify +****************************************************************/ + +WERROR _spoolss_RouterRefreshPrinterChangeNotify(struct pipes_struct *p, + struct spoolss_RouterRefreshPrinterChangeNotify *r) +{ + struct spoolss_NotifyInfo *info; + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + WERROR result = WERR_INVALID_HANDLE; + + /* we always have a spoolss_NotifyInfo struct */ + info = talloc_zero(p->mem_ctx, struct spoolss_NotifyInfo); + if (!info) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + *r->out.info = info; + + if (!Printer) { + DEBUG(2,("_spoolss_RouterRefreshPrinterChangeNotify: " + "Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + goto done; + } + + DEBUG(4,("Printer type %x\n",Printer->printer_type)); + + /* + * We are now using the change value, and + * I should check for PRINTER_NOTIFY_OPTIONS_REFRESH but as + * I don't have a global notification system, I'm sending back all the + * information even when _NOTHING_ has changed. + */ + + /* We need to keep track of the change value to send back in + RRPCN replies otherwise our updates are ignored. */ + + Printer->notify.fnpcn = true; + + if (Printer->notify.cli_chan != NULL && + Printer->notify.cli_chan->active_connections > 0) { + DEBUG(10,("_spoolss_RouterRefreshPrinterChangeNotify: " + "Saving change value in request [%x]\n", + r->in.change_low)); + Printer->notify.change = r->in.change_low; + } + + /* just ignore the spoolss_NotifyOption */ + + switch (Printer->printer_type) { + case SPLHND_SERVER: + result = printserver_notify_info(p, r->in.handle, + info, p->mem_ctx); + break; + + case SPLHND_PRINTER: + result = printer_notify_info(p, r->in.handle, + info, p->mem_ctx); + break; + } + + Printer->notify.fnpcn = false; + +done: + return result; +} + +/******************************************************************** + ********************************************************************/ + +static WERROR create_printername(TALLOC_CTX *mem_ctx, + const char *servername, + const char *printername, + const char **printername_p) +{ + /* FIXME: add lp_force_printername() */ + + if (servername == NULL) { + *printername_p = talloc_strdup(mem_ctx, printername); + W_ERROR_HAVE_NO_MEMORY(*printername_p); + return WERR_OK; + } + + if (servername[0] == '\\' && servername[1] == '\\') { + servername += 2; + } + + *printername_p = talloc_asprintf(mem_ctx, "\\\\%s\\%s", servername, printername); + W_ERROR_HAVE_NO_MEMORY(*printername_p); + + return WERR_OK; +} + +/******************************************************************** + ********************************************************************/ + +static void compose_devicemode_devicename(struct spoolss_DeviceMode *dm, + const char *printername) +{ + if (dm == NULL) { + return; + } + + dm->devicename = talloc_strndup(dm, printername, + MIN(strlen(printername), 31)); +} + +/******************************************************************** + * construct_printer_info_0 + * fill a printer_info_0 struct + ********************************************************************/ + +static WERROR construct_printer_info0(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo0 *r, + int snum) +{ + int count; + struct printer_session_counter *session_counter; + struct timeval setuptime; + print_status_struct status; + WERROR result; + int os_major, os_minor, os_build; + const char *architecture; + uint32_t processor_architecture, processor_type; + + result = create_printername(mem_ctx, servername, info2->printername, &r->printername); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + if (servername) { + r->servername = talloc_strdup(mem_ctx, servername); + W_ERROR_HAVE_NO_MEMORY(r->servername); + } else { + r->servername = NULL; + } + + count = print_queue_length(msg_ctx, snum, &status); + + /* check if we already have a counter for this printer */ + for (session_counter = counter_list; session_counter; session_counter = session_counter->next) { + if (session_counter->snum == snum) + break; + } + + /* it's the first time, add it to the list */ + if (session_counter == NULL) { + session_counter = talloc_zero(counter_list, struct printer_session_counter); + W_ERROR_HAVE_NO_MEMORY(session_counter); + session_counter->snum = snum; + session_counter->counter = 0; + DLIST_ADD(counter_list, session_counter); + } + + /* increment it */ + session_counter->counter++; + + r->cjobs = count; + r->total_jobs = 0; + r->total_bytes = 0; + + get_startup_time(&setuptime); + init_systemtime(&r->time, gmtime(&setuptime.tv_sec)); + + /* JFM: + * the global_counter should be stored in a TDB as it's common to all the clients + * and should be zeroed on samba startup + */ + r->global_counter = session_counter->counter; + r->total_pages = 0; + + /* in 2.2 we reported ourselves as 0x0004 and 0x0565 */ + os_major = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_major", + GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT); + os_minor = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_minor", + GLOBAL_SPOOLSS_OS_MINOR_DEFAULT); + os_build = lp_parm_int(GLOBAL_SECTION_SNUM, + "spoolss", "os_build", + GLOBAL_SPOOLSS_OS_BUILD_DEFAULT); + + SCVAL(&r->version, 0, os_major); + SCVAL(&r->version, 1, os_minor); + SSVAL(&r->version, 2, os_build); + + architecture = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "spoolss", + "architecture", + GLOBAL_SPOOLSS_ARCHITECTURE); + + if (strequal(architecture, SPOOLSS_ARCHITECTURE_x64)) { + processor_architecture = PROCESSOR_ARCHITECTURE_AMD64; + processor_type = PROCESSOR_AMD_X8664; + } else if (strequal(architecture, SPOOLSS_ARCHITECTURE_ARM64)) { + processor_architecture = PROCESSOR_ARCHITECTURE_ARM64; + processor_type = PROCESSOR_ARM820; + } else { + processor_architecture = PROCESSOR_ARCHITECTURE_INTEL; + processor_type = PROCESSOR_INTEL_PENTIUM; + } + + r->free_build = SPOOLSS_RELEASE_BUILD; + r->spooling = 0; + r->max_spooling = 0; + r->session_counter = session_counter->counter; + r->num_error_out_of_paper = 0x0; + r->num_error_not_ready = 0x0; /* number of print failure */ + r->job_error = 0x0; + r->number_of_processors = 0x1; + r->processor_type = processor_type; + r->high_part_total_bytes = 0x0; + + /* ChangeID in milliseconds*/ + winreg_printer_get_changeid_internal(mem_ctx, session_info, msg_ctx, + info2->sharename, &r->change_id); + + r->last_error = WERR_OK; + r->status = nt_printq_status(status.status); + r->enumerate_network_printers = 0x0; + r->c_setprinter = 0x0; + r->processor_architecture = processor_architecture; + r->processor_level = 0x6; /* 6 ???*/ + r->ref_ic = 0; + r->reserved2 = 0; + r->reserved3 = 0; + + return WERR_OK; +} + + +/******************************************************************** + * construct_printer_info1 + * fill a spoolss_PrinterInfo1 struct +********************************************************************/ + +static WERROR construct_printer_info1(TALLOC_CTX *mem_ctx, + const struct spoolss_PrinterInfo2 *info2, + uint32_t flags, + const char *servername, + struct spoolss_PrinterInfo1 *r, + int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + WERROR result; + + r->flags = flags; + + if (info2->comment == NULL || info2->comment[0] == '\0') { + r->comment = lp_comment(mem_ctx, lp_sub, snum); + } else { + r->comment = talloc_strdup(mem_ctx, info2->comment); /* saved comment */ + } + W_ERROR_HAVE_NO_MEMORY(r->comment); + + result = create_printername(mem_ctx, servername, info2->printername, &r->name); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->description = talloc_asprintf(mem_ctx, "%s,%s,%s", + r->name, + info2->drivername, + r->comment); + W_ERROR_HAVE_NO_MEMORY(r->description); + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info2 + * fill a spoolss_PrinterInfo2 struct +********************************************************************/ + +static WERROR construct_printer_info2(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo2 *r, + int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int count; + print_status_struct status; + WERROR result; + + count = print_queue_length(msg_ctx, snum, &status); + + if (servername) { + r->servername = talloc_strdup(mem_ctx, servername); + W_ERROR_HAVE_NO_MEMORY(r->servername); + } else { + r->servername = NULL; + } + + result = create_printername(mem_ctx, servername, info2->printername, &r->printername); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->sharename = lp_servicename(mem_ctx, lp_sub, snum); + W_ERROR_HAVE_NO_MEMORY(r->sharename); + r->portname = talloc_strdup(mem_ctx, info2->portname); + W_ERROR_HAVE_NO_MEMORY(r->portname); + r->drivername = talloc_strdup(mem_ctx, info2->drivername); + W_ERROR_HAVE_NO_MEMORY(r->drivername); + + if (info2->comment[0] == '\0') { + r->comment = lp_comment(mem_ctx, lp_sub, snum); + } else { + r->comment = talloc_strdup(mem_ctx, info2->comment); + } + W_ERROR_HAVE_NO_MEMORY(r->comment); + + r->location = talloc_strdup(mem_ctx, info2->location); + if (info2->location[0] == '\0') { + const char *loc = NULL; + NTSTATUS nt_status; + + nt_status = printer_list_get_printer(mem_ctx, + info2->sharename, + NULL, + &loc, + NULL); + if (NT_STATUS_IS_OK(nt_status)) { + if (loc != NULL) { + r->location = talloc_strdup(mem_ctx, loc); + } + } + } + W_ERROR_HAVE_NO_MEMORY(r->location); + + r->sepfile = talloc_strdup(mem_ctx, info2->sepfile); + W_ERROR_HAVE_NO_MEMORY(r->sepfile); + r->printprocessor = talloc_strdup(mem_ctx, info2->printprocessor); + W_ERROR_HAVE_NO_MEMORY(r->printprocessor); + r->datatype = talloc_strdup(mem_ctx, info2->datatype); + W_ERROR_HAVE_NO_MEMORY(r->datatype); + r->parameters = talloc_strdup(mem_ctx, info2->parameters); + W_ERROR_HAVE_NO_MEMORY(r->parameters); + + r->attributes = info2->attributes; + + r->priority = info2->priority; + r->defaultpriority = info2->defaultpriority; + r->starttime = info2->starttime; + r->untiltime = info2->untiltime; + r->status = nt_printq_status(status.status); + r->cjobs = count; + r->averageppm = info2->averageppm; + + if (info2->devmode != NULL) { + result = copy_devicemode(mem_ctx, + info2->devmode, + &r->devmode); + if (!W_ERROR_IS_OK(result)) { + return result; + } + } else if (lp_default_devmode(snum)) { + result = spoolss_create_default_devmode(mem_ctx, + info2->printername, + &r->devmode); + if (!W_ERROR_IS_OK(result)) { + return result; + } + } else { + r->devmode = NULL; + DEBUG(8,("Returning NULL Devicemode!\n")); + } + + compose_devicemode_devicename(r->devmode, r->printername); + + r->secdesc = NULL; + + if (info2->secdesc != NULL) { + /* don't use talloc_steal() here unless you do a deep steal of all + the SEC_DESC members */ + + r->secdesc = security_descriptor_copy(mem_ctx, info2->secdesc); + if (r->secdesc == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info3 + * fill a spoolss_PrinterInfo3 struct + ********************************************************************/ + +static WERROR construct_printer_info3(TALLOC_CTX *mem_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo3 *r, + int snum) +{ + /* These are the components of the SD we are returning. */ + + if (info2->secdesc != NULL) { + /* don't use talloc_steal() here unless you do a deep steal of all + the SEC_DESC members */ + + r->secdesc = security_descriptor_copy(mem_ctx, info2->secdesc); + if (r->secdesc == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info4 + * fill a spoolss_PrinterInfo4 struct + ********************************************************************/ + +static WERROR construct_printer_info4(TALLOC_CTX *mem_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo4 *r, + int snum) +{ + WERROR result; + + result = create_printername(mem_ctx, servername, info2->printername, &r->printername); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + if (servername) { + r->servername = talloc_strdup(mem_ctx, servername); + W_ERROR_HAVE_NO_MEMORY(r->servername); + } else { + r->servername = NULL; + } + + r->attributes = info2->attributes; + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info5 + * fill a spoolss_PrinterInfo5 struct + ********************************************************************/ + +static WERROR construct_printer_info5(TALLOC_CTX *mem_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo5 *r, + int snum) +{ + WERROR result; + + result = create_printername(mem_ctx, servername, info2->printername, &r->printername); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->portname = talloc_strdup(mem_ctx, info2->portname); + W_ERROR_HAVE_NO_MEMORY(r->portname); + + r->attributes = info2->attributes; + + /* + * These two are not used by NT+ according to MSDN. However the values + * we saw on Windows Server 2012 and 2016 are always set to the 0xafc8. + */ + r->device_not_selected_timeout = 0xafc8; /* 45 sec */ + r->transmission_retry_timeout = 0xafc8; /* 45 sec */ + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info_6 + * fill a spoolss_PrinterInfo6 struct + ********************************************************************/ + +static WERROR construct_printer_info6(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_PrinterInfo6 *r, + int snum) +{ + print_status_struct status; + + print_queue_length(msg_ctx, snum, &status); + + r->status = nt_printq_status(status.status); + + return WERR_OK; +} + +/******************************************************************** + * construct_printer_info7 + * fill a spoolss_PrinterInfo7 struct + ********************************************************************/ + +static WERROR construct_printer_info7(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *servername, + struct spoolss_PrinterInfo7 *r, + int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const struct auth_session_info *session_info; + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + char *printer; + WERROR werr; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + session_info = get_session_info_system(); + SMB_ASSERT(session_info != NULL); + + printer = lp_servicename(tmp_ctx, lp_sub, snum); + if (printer == NULL) { + DEBUG(0, ("invalid printer snum %d\n", snum)); + werr = WERR_INVALID_PARAMETER; + goto out_tmp_free; + } + + if (is_printer_published(tmp_ctx, session_info, msg_ctx, + servername, printer, &pinfo2)) { + struct GUID guid; + char *guidstr; + werr = nt_printer_guid_get(tmp_ctx, session_info, msg_ctx, + printer, &guid); + if (!W_ERROR_IS_OK(werr)) { + /* + * If we do not have a GUID entry in the registry, then + * try to retrieve it from AD and store it now. + */ + werr = nt_printer_guid_retrieve(tmp_ctx, printer, + &guid); + if (!W_ERROR_IS_OK(werr)) { + DBG_NOTICE("Failed to retrieve GUID for " + "printer [%s] from AD - %s\n", + printer, + win_errstr(werr)); + if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) { + /* + * If we did not find it in AD, then it + * is unpublished and we should reflect + * this in the registry and return + * success. + */ + DBG_WARNING("Unpublish printer [%s]\n", + pinfo2->sharename); + nt_printer_publish(tmp_ctx, + session_info, + msg_ctx, + pinfo2, + DSPRINT_UNPUBLISH); + r->guid = talloc_strdup(mem_ctx, ""); + r->action = DSPRINT_UNPUBLISH; + + if (r->guid == NULL) { + werr = WERR_NOT_ENOUGH_MEMORY; + } else { + werr = WERR_OK; + } + } + goto out_tmp_free; + } + + werr = nt_printer_guid_store(msg_ctx, printer, guid); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(3, ("failed to store printer %s guid\n", + printer)); + } + } + + /* [MS-RPRN] section 2.2: must use curly-braced GUIDs */ + guidstr = GUID_string2(mem_ctx, &guid); + if (guidstr == NULL) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto out_tmp_free; + } + /* Convert GUID string to uppercase otherwise printers + * are pruned */ + r->guid = talloc_strdup_upper(mem_ctx, guidstr); + r->action = DSPRINT_PUBLISH; + + TALLOC_FREE(guidstr); + } else { + r->guid = talloc_strdup(mem_ctx, ""); + r->action = DSPRINT_UNPUBLISH; + } + if (r->guid == NULL) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto out_tmp_free; + } + + werr = WERR_OK; +out_tmp_free: + talloc_free(tmp_ctx); + return werr; +} + +/******************************************************************** + * construct_printer_info8 + * fill a spoolss_PrinterInfo8 struct + ********************************************************************/ + +static WERROR construct_printer_info8(TALLOC_CTX *mem_ctx, + const struct spoolss_PrinterInfo2 *info2, + const char *servername, + struct spoolss_DeviceModeInfo *r, + int snum) +{ + WERROR result; + const char *printername; + + result = create_printername(mem_ctx, servername, info2->printername, &printername); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + if (info2->devmode != NULL) { + result = copy_devicemode(mem_ctx, + info2->devmode, + &r->devmode); + if (!W_ERROR_IS_OK(result)) { + return result; + } + } else if (lp_default_devmode(snum)) { + result = spoolss_create_default_devmode(mem_ctx, + info2->printername, + &r->devmode); + if (!W_ERROR_IS_OK(result)) { + return result; + } + } else { + r->devmode = NULL; + DEBUG(8,("Returning NULL Devicemode!\n")); + } + + compose_devicemode_devicename(r->devmode, printername); + + return WERR_OK; +} + +/******************************************************************** + Spoolss_enumprinters. +********************************************************************/ + +static WERROR enum_all_printers_info_level(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + uint32_t level, + uint32_t flags, + union spoolss_PrinterInfo **info_p, + uint32_t *count_p) +{ + int snum; + int n_services; + union spoolss_PrinterInfo *info = NULL; + uint32_t count = 0; + WERROR result = WERR_OK; + struct dcerpc_binding_handle *b = NULL; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* + * printer shares are updated on client enumeration. The background + * printer process updates printer_list.tdb at regular intervals. + */ + become_root(); + delete_and_reload_printers(); + unbecome_root(); + + n_services = lp_numservices(); + *count_p = 0; + *info_p = NULL; + + for (snum = 0; snum < n_services; snum++) { + + const char *printer; + struct spoolss_PrinterInfo2 *info2; + + if (!snum_is_shared_printer(snum)) { + continue; + } + + printer = lp_const_servicename(snum); + + DEBUG(4,("Found a printer in smb.conf: %s[%x]\n", + printer, snum)); + + if (b == NULL) { + result = winreg_printer_binding_handle(tmp_ctx, + session_info, + msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + } + + result = winreg_create_printer(tmp_ctx, b, + printer); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + info = talloc_realloc(tmp_ctx, info, + union spoolss_PrinterInfo, + count + 1); + if (!info) { + result = WERR_NOT_ENOUGH_MEMORY; + goto out; + } + + result = winreg_get_printer(tmp_ctx, b, + printer, &info2); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + switch (level) { + case 0: + result = construct_printer_info0(info, session_info, + msg_ctx, info2, + servername, + &info[count].info0, snum); + break; + case 1: + result = construct_printer_info1(info, info2, flags, + servername, + &info[count].info1, snum); + break; + case 2: + result = construct_printer_info2(info, msg_ctx, info2, + servername, + &info[count].info2, snum); + break; + case 4: + result = construct_printer_info4(info, info2, + servername, + &info[count].info4, snum); + break; + case 5: + result = construct_printer_info5(info, info2, + servername, + &info[count].info5, snum); + break; + + default: + result = WERR_INVALID_LEVEL; + goto out; + } + + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + count++; + } + +out: + if (W_ERROR_IS_OK(result)) { + *info_p = talloc_move(mem_ctx, &info); + *count_p = count; + } + + talloc_free(tmp_ctx); + + return result; +} + +/******************************************************************** + * handle enumeration of printers at level 0 + ********************************************************************/ + +static WERROR enumprinters_level0(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t flags, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_0\n")); + + return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx, + servername, 0, flags, info, count); +} + + +/******************************************************************** +********************************************************************/ + +static WERROR enum_all_printers_info_1(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + uint32_t flags, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_1\n")); + + return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx, + servername, 1, flags, info, count); +} + +/******************************************************************** + enum_all_printers_info_1_local. +*********************************************************************/ + +static WERROR enum_all_printers_info_1_local(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_1_local\n")); + + return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx, + servername, PRINTER_ENUM_ICON8, info, count); +} + +/******************************************************************** + enum_all_printers_info_1_name. +*********************************************************************/ + +static WERROR enum_all_printers_info_1_name(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + const char *s = servername; + + DEBUG(4,("enum_all_printers_info_1_name\n")); + + if (servername != NULL && + (servername[0] == '\\') && (servername[1] == '\\')) { + s = servername + 2; + } + + if (!is_myname_or_ipaddr(s)) { + return WERR_INVALID_NAME; + } + + return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx, + servername, PRINTER_ENUM_ICON8, info, count); +} + +/******************************************************************** + enum_all_printers_info_1_network. +*********************************************************************/ + +static WERROR enum_all_printers_info_1_network(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + const char *s = servername; + + DEBUG(4,("enum_all_printers_info_1_network\n")); + + /* If we respond to a enum_printers level 1 on our name with flags + set to PRINTER_ENUM_REMOTE with a list of printers then these + printers incorrectly appear in the APW browse list. + Specifically the printers for the server appear at the workgroup + level where all the other servers in the domain are + listed. Windows responds to this call with a + WERR_CAN_NOT_COMPLETE so we should do the same. */ + + if (servername != NULL && + (servername[0] == '\\') && (servername[1] == '\\')) { + s = servername + 2; + } + + if (is_myname_or_ipaddr(s)) { + return WERR_CAN_NOT_COMPLETE; + } + + return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx, + servername, PRINTER_ENUM_NAME, info, count); +} + +/******************************************************************** + * api_spoolss_enumprinters + * + * called from api_spoolss_enumprinters (see this to understand) + ********************************************************************/ + +static WERROR enum_all_printers_info_2(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_2\n")); + + return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx, + servername, 2, 0, info, count); +} + +/******************************************************************** + * handle enumeration of printers at level 1 + ********************************************************************/ + +static WERROR enumprinters_level1(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t flags, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + /* Not all the flags are equals */ + + if (flags & PRINTER_ENUM_LOCAL) { + return enum_all_printers_info_1_local(mem_ctx, session_info, + msg_ctx, servername, info, count); + } + + if (flags & PRINTER_ENUM_NAME) { + return enum_all_printers_info_1_name(mem_ctx, session_info, + msg_ctx, servername, info, + count); + } + + if (flags & PRINTER_ENUM_NETWORK) { + return enum_all_printers_info_1_network(mem_ctx, session_info, + msg_ctx, servername, info, + count); + } + + return WERR_OK; /* NT4sp5 does that */ +} + +/******************************************************************** + * handle enumeration of printers at level 2 + ********************************************************************/ + +static WERROR enumprinters_level2(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t flags, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + if (flags & PRINTER_ENUM_LOCAL) { + + return enum_all_printers_info_2(mem_ctx, session_info, msg_ctx, + servername, + info, count); + } + + if (flags & PRINTER_ENUM_NAME) { + if (servername && !is_myname_or_ipaddr(canon_servername(servername))) { + return WERR_INVALID_NAME; + } + + return enum_all_printers_info_2(mem_ctx, session_info, msg_ctx, + servername, + info, count); + } + + if (flags & PRINTER_ENUM_REMOTE) { + return WERR_INVALID_LEVEL; + } + + return WERR_OK; +} + +/******************************************************************** + * handle enumeration of printers at level 4 + ********************************************************************/ + +static WERROR enumprinters_level4(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t flags, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_4\n")); + + return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx, + servername, 4, flags, info, count); +} + + +/******************************************************************** + * handle enumeration of printers at level 5 + ********************************************************************/ + +static WERROR enumprinters_level5(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t flags, + const char *servername, + union spoolss_PrinterInfo **info, + uint32_t *count) +{ + DEBUG(4,("enum_all_printers_info_5\n")); + + return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx, + servername, 5, flags, info, count); +} + +/**************************************************************** + _spoolss_EnumPrinters +****************************************************************/ + +WERROR _spoolss_EnumPrinters(struct pipes_struct *p, + struct spoolss_EnumPrinters *r) +{ + const struct auth_session_info *session_info = get_session_info_system(); + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(4,("_spoolss_EnumPrinters\n")); + + *r->out.needed = 0; + *r->out.count = 0; + *r->out.info = NULL; + + /* + * Level 1: + * flags==PRINTER_ENUM_NAME + * if name=="" then enumerates all printers + * if name!="" then enumerate the printer + * flags==PRINTER_ENUM_REMOTE + * name is NULL, enumerate printers + * Level 2: name!="" enumerates printers, name can't be NULL + * Level 3: doesn't exist + * Level 4: does a local registry lookup + * Level 5: same as Level 2 + */ + + if (r->in.server && r->in.server[0] == '\0') { + r->in.server = NULL; + } + + switch (r->in.level) { + case 0: + result = enumprinters_level0(p->mem_ctx, session_info, + p->msg_ctx, r->in.flags, + r->in.server, + r->out.info, r->out.count); + break; + case 1: + result = enumprinters_level1(p->mem_ctx, session_info, + p->msg_ctx, r->in.flags, + r->in.server, + r->out.info, r->out.count); + break; + case 2: + result = enumprinters_level2(p->mem_ctx, session_info, + p->msg_ctx, r->in.flags, + r->in.server, + r->out.info, r->out.count); + break; + case 4: + result = enumprinters_level4(p->mem_ctx, session_info, + p->msg_ctx, r->in.flags, + r->in.server, + r->out.info, r->out.count); + break; + case 5: + result = enumprinters_level5(p->mem_ctx, session_info, + p->msg_ctx, r->in.flags, + r->in.server, + r->out.info, r->out.count); + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumPrinters, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************** + _spoolss_GetPrinter +****************************************************************/ + +WERROR _spoolss_GetPrinter(struct pipes_struct *p, + struct spoolss_GetPrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + struct spoolss_PrinterInfo2 *info2 = NULL; + WERROR result = WERR_OK; + int snum; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + *r->out.needed = 0; + + if (Printer == NULL) { + result = WERR_INVALID_HANDLE; + goto err_info_free; + } + + if (Printer->printer_type == SPLHND_SERVER) { + + struct dcerpc_binding_handle *b; + + if (r->in.level != 3) { + result = WERR_INVALID_LEVEL; + goto err_info_free; + } + + result = winreg_printer_binding_handle(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto err_info_free; + } + + result = winreg_get_printserver_secdesc(p->mem_ctx, + b, + &r->out.info->info3.secdesc); + if (!W_ERROR_IS_OK(result)) { + goto err_info_free; + } + + goto done; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + result = WERR_INVALID_HANDLE; + goto err_info_free; + } + + result = winreg_get_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + &info2); + if (!W_ERROR_IS_OK(result)) { + goto err_info_free; + } + + switch (r->in.level) { + case 0: + result = construct_printer_info0(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + info2, + Printer->servername, + &r->out.info->info0, + snum); + break; + case 1: + result = construct_printer_info1(p->mem_ctx, info2, + PRINTER_ENUM_ICON8, + Printer->servername, + &r->out.info->info1, snum); + break; + case 2: + result = construct_printer_info2(p->mem_ctx, p->msg_ctx, info2, + Printer->servername, + &r->out.info->info2, snum); + break; + case 3: + result = construct_printer_info3(p->mem_ctx, info2, + Printer->servername, + &r->out.info->info3, snum); + break; + case 4: + result = construct_printer_info4(p->mem_ctx, info2, + Printer->servername, + &r->out.info->info4, snum); + break; + case 5: + result = construct_printer_info5(p->mem_ctx, info2, + Printer->servername, + &r->out.info->info5, snum); + break; + case 6: + result = construct_printer_info6(p->mem_ctx, p->msg_ctx, info2, + Printer->servername, + &r->out.info->info6, snum); + break; + case 7: + result = construct_printer_info7(p->mem_ctx, p->msg_ctx, + Printer->servername, + &r->out.info->info7, snum); + break; + case 8: + result = construct_printer_info8(p->mem_ctx, info2, + Printer->servername, + &r->out.info->info8, snum); + break; + default: + result = WERR_INVALID_LEVEL; + break; + } + TALLOC_FREE(info2); + + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("_spoolss_GetPrinter: failed to construct printer info level %d - %s\n", + r->in.level, win_errstr(result))); + goto err_info_free; + } + done: + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_PrinterInfo, + r->out.info, r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); + +err_info_free: + TALLOC_FREE(r->out.info); + return result; +} + +/******************************************************************** + ********************************************************************/ + +#define FILL_DRIVER_STRING(mem_ctx, in, out) \ + do { \ + if (in && strlen(in)) { \ + out = talloc_strdup(mem_ctx, in); \ + } else { \ + out = talloc_strdup(mem_ctx, ""); \ + } \ + W_ERROR_HAVE_NO_MEMORY(out); \ + } while (0); + +#define FILL_DRIVER_UNC_STRING(mem_ctx, server, arch, ver, in, out) \ + do { \ + if (in && strlen(in)) { \ + out = talloc_asprintf(mem_ctx, "\\\\%s\\print$\\%s\\%d\\%s", server, get_short_archi(arch), ver, in); \ + } else { \ + out = talloc_strdup(mem_ctx, ""); \ + } \ + W_ERROR_HAVE_NO_MEMORY(out); \ + } while (0); + +static WERROR string_array_from_driver_info(TALLOC_CTX *mem_ctx, + const char **string_array, + const char ***presult, + const char *cservername, + const char *arch, + int version) +{ + size_t i; + size_t num_strings = 0; + const char **array = NULL; + + if (string_array == NULL) { + return WERR_INVALID_PARAMETER; + } + + for (i=0; string_array[i] && string_array[i][0] != '\0'; i++) { + const char *str = NULL; + + if (cservername == NULL || arch == NULL) { + FILL_DRIVER_STRING(mem_ctx, string_array[i], str); + } else { + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, arch, version, string_array[i], str); + } + + if (!add_string_to_array(mem_ctx, str, &array, &num_strings)) { + TALLOC_FREE(array); + return WERR_NOT_ENOUGH_MEMORY; + } + } + + if (i > 0) { + ADD_TO_ARRAY(mem_ctx, const char *, NULL, + &array, &num_strings); + } + + if (presult != NULL) { + *presult = array; + } else { + talloc_free(array); + } + + return WERR_OK; +} + +/******************************************************************** + * fill a spoolss_DriverInfo1 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info1(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo1 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + + return WERR_OK; +} + +/******************************************************************** + * fill a spoolss_DriverInfo2 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info2(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo2 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) + +{ + const char *cservername = canon_servername(servername); + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + return WERR_OK; +} + +/******************************************************************** + * fill a spoolss_DriverInfo3 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info3(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo3 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->help_file, + r->help_file); + + FILL_DRIVER_STRING(mem_ctx, + driver->monitor_name, + r->monitor_name); + + FILL_DRIVER_STRING(mem_ctx, + driver->default_datatype, + r->default_datatype); + + return string_array_from_driver_info(mem_ctx, + driver->dependent_files, + &r->dependent_files, + cservername, + driver->architecture, + driver->version); +} + +/******************************************************************** + * fill a spoolss_DriverInfo4 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info4(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo4 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + WERROR result; + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->help_file, + r->help_file); + + result = string_array_from_driver_info(mem_ctx, + driver->dependent_files, + &r->dependent_files, + cservername, + driver->architecture, + driver->version); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + FILL_DRIVER_STRING(mem_ctx, + driver->monitor_name, + r->monitor_name); + + FILL_DRIVER_STRING(mem_ctx, + driver->default_datatype, + r->default_datatype); + + + result = string_array_from_driver_info(mem_ctx, + driver->previous_names, + &r->previous_names, + NULL, NULL, 0); + + return result; +} + +/******************************************************************** + * fill a spoolss_DriverInfo5 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info5(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo5 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + r->driver_attributes = 0; + r->config_version = 0; + r->driver_version = 0; + + return WERR_OK; +} +/******************************************************************** + * fill a spoolss_DriverInfo6 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info6(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo6 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + WERROR result; + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->help_file, + r->help_file); + + FILL_DRIVER_STRING(mem_ctx, + driver->monitor_name, + r->monitor_name); + + FILL_DRIVER_STRING(mem_ctx, + driver->default_datatype, + r->default_datatype); + + result = string_array_from_driver_info(mem_ctx, + driver->dependent_files, + &r->dependent_files, + cservername, + driver->architecture, + driver->version); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + result = string_array_from_driver_info(mem_ctx, + driver->previous_names, + &r->previous_names, + NULL, NULL, 0); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->driver_date = driver->driver_date; + r->driver_version = driver->driver_version; + + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_name, + r->manufacturer_name); + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_url, + r->manufacturer_url); + FILL_DRIVER_STRING(mem_ctx, + driver->hardware_id, + r->hardware_id); + FILL_DRIVER_STRING(mem_ctx, + driver->provider, + r->provider); + + return WERR_OK; +} + +/******************************************************************** + * fill a spoolss_DriverInfo8 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info8(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo8 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + WERROR result; + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->driver_path, + r->driver_path); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->data_file, + r->data_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->config_file, + r->config_file); + + FILL_DRIVER_UNC_STRING(mem_ctx, cservername, + driver->architecture, + driver->version, + driver->help_file, + r->help_file); + + FILL_DRIVER_STRING(mem_ctx, + driver->monitor_name, + r->monitor_name); + + FILL_DRIVER_STRING(mem_ctx, + driver->default_datatype, + r->default_datatype); + + result = string_array_from_driver_info(mem_ctx, + driver->dependent_files, + &r->dependent_files, + cservername, + driver->architecture, + driver->version); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + result = string_array_from_driver_info(mem_ctx, + driver->previous_names, + &r->previous_names, + NULL, NULL, 0); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->driver_date = driver->driver_date; + r->driver_version = driver->driver_version; + + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_name, + r->manufacturer_name); + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_url, + r->manufacturer_url); + FILL_DRIVER_STRING(mem_ctx, + driver->hardware_id, + r->hardware_id); + FILL_DRIVER_STRING(mem_ctx, + driver->provider, + r->provider); + + FILL_DRIVER_STRING(mem_ctx, + driver->print_processor, + r->print_processor); + FILL_DRIVER_STRING(mem_ctx, + driver->vendor_setup, + r->vendor_setup); + + result = string_array_from_driver_info(mem_ctx, + driver->color_profiles, + &r->color_profiles, + NULL, NULL, 0); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + FILL_DRIVER_STRING(mem_ctx, + driver->inf_path, + r->inf_path); + + r->printer_driver_attributes = driver->printer_driver_attributes; + + result = string_array_from_driver_info(mem_ctx, + driver->core_driver_dependencies, + &r->core_driver_dependencies, + NULL, NULL, 0); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->min_inbox_driver_ver_date = driver->min_inbox_driver_ver_date; + r->min_inbox_driver_ver_version = driver->min_inbox_driver_ver_version; + + return WERR_OK; +} + +#if 0 /* disabled until marshalling issues are resolved - gd */ +/******************************************************************** + ********************************************************************/ + +static WERROR fill_spoolss_DriverFileInfo(TALLOC_CTX *mem_ctx, + struct spoolss_DriverFileInfo *r, + const char *cservername, + const char *file_name, + enum spoolss_DriverFileType file_type, + uint32_t file_version) +{ + r->file_name = talloc_asprintf(mem_ctx, "\\\\%s%s", + cservername, file_name); + W_ERROR_HAVE_NO_MEMORY(r->file_name); + r->file_type = file_type; + r->file_version = file_version; + + return WERR_OK; +} + +/******************************************************************** + ********************************************************************/ + +static WERROR spoolss_DriverFileInfo_from_driver(TALLOC_CTX *mem_ctx, + const struct spoolss_DriverInfo8 *driver, + const char *cservername, + struct spoolss_DriverFileInfo **info_p, + uint32_t *count_p) +{ + struct spoolss_DriverFileInfo *info = NULL; + uint32_t count = 0; + WERROR result; + uint32_t i; + + *info_p = NULL; + *count_p = 0; + + if (strlen(driver->driver_path)) { + info = talloc_realloc(mem_ctx, info, + struct spoolss_DriverFileInfo, + count + 1); + W_ERROR_HAVE_NO_MEMORY(info); + result = fill_spoolss_DriverFileInfo(info, + &info[count], + cservername, + driver->driver_path, + SPOOLSS_DRIVER_FILE_TYPE_RENDERING, + 0); + W_ERROR_NOT_OK_RETURN(result); + count++; + } + + if (strlen(driver->config_file)) { + info = talloc_realloc(mem_ctx, info, + struct spoolss_DriverFileInfo, + count + 1); + W_ERROR_HAVE_NO_MEMORY(info); + result = fill_spoolss_DriverFileInfo(info, + &info[count], + cservername, + driver->config_file, + SPOOLSS_DRIVER_FILE_TYPE_CONFIGURATION, + 0); + W_ERROR_NOT_OK_RETURN(result); + count++; + } + + if (strlen(driver->data_file)) { + info = talloc_realloc(mem_ctx, info, + struct spoolss_DriverFileInfo, + count + 1); + W_ERROR_HAVE_NO_MEMORY(info); + result = fill_spoolss_DriverFileInfo(info, + &info[count], + cservername, + driver->data_file, + SPOOLSS_DRIVER_FILE_TYPE_DATA, + 0); + W_ERROR_NOT_OK_RETURN(result); + count++; + } + + if (strlen(driver->help_file)) { + info = talloc_realloc(mem_ctx, info, + struct spoolss_DriverFileInfo, + count + 1); + W_ERROR_HAVE_NO_MEMORY(info); + result = fill_spoolss_DriverFileInfo(info, + &info[count], + cservername, + driver->help_file, + SPOOLSS_DRIVER_FILE_TYPE_HELP, + 0); + W_ERROR_NOT_OK_RETURN(result); + count++; + } + + for (i=0; driver->dependent_files[i] && driver->dependent_files[i][0] != '\0'; i++) { + info = talloc_realloc(mem_ctx, info, + struct spoolss_DriverFileInfo, + count + 1); + W_ERROR_HAVE_NO_MEMORY(info); + result = fill_spoolss_DriverFileInfo(info, + &info[count], + cservername, + driver->dependent_files[i], + SPOOLSS_DRIVER_FILE_TYPE_OTHER, + 0); + W_ERROR_NOT_OK_RETURN(result); + count++; + } + + *info_p = info; + *count_p = count; + + return WERR_OK; +} + +/******************************************************************** + * fill a spoolss_DriverInfo101 struct + ********************************************************************/ + +static WERROR fill_printer_driver_info101(TALLOC_CTX *mem_ctx, + struct spoolss_DriverInfo101 *r, + const struct spoolss_DriverInfo8 *driver, + const char *servername) +{ + const char *cservername = canon_servername(servername); + WERROR result; + + r->version = driver->version; + + r->driver_name = talloc_strdup(mem_ctx, driver->driver_name); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + r->architecture = talloc_strdup(mem_ctx, driver->architecture); + W_ERROR_HAVE_NO_MEMORY(r->architecture); + + result = spoolss_DriverFileInfo_from_driver(mem_ctx, driver, + cservername, + &r->file_info, + &r->file_count); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + FILL_DRIVER_STRING(mem_ctx, + driver->monitor_name, + r->monitor_name); + + FILL_DRIVER_STRING(mem_ctx, + driver->default_datatype, + r->default_datatype); + + result = string_array_from_driver_info(mem_ctx, + driver->previous_names, + &r->previous_names, + NULL, NULL, 0); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + r->driver_date = driver->driver_date; + r->driver_version = driver->driver_version; + + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_name, + r->manufacturer_name); + FILL_DRIVER_STRING(mem_ctx, + driver->manufacturer_url, + r->manufacturer_url); + FILL_DRIVER_STRING(mem_ctx, + driver->hardware_id, + r->hardware_id); + FILL_DRIVER_STRING(mem_ctx, + driver->provider, + r->provider); + + return WERR_OK; +} +#endif +/******************************************************************** + ********************************************************************/ + +static WERROR construct_printer_driver_info_level(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t level, + union spoolss_DriverInfo *r, + int snum, + const char *servername, + const char *architecture, + uint32_t version) +{ + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + struct spoolss_DriverInfo8 *driver; + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + + if (level == 101) { + return WERR_INVALID_LEVEL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, + session_info, + msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + result = winreg_get_printer(tmp_ctx, b, + lp_const_servicename(snum), + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + DBG_ERR("Failed to get printer info2 for [%s]: %s\n", + lp_const_servicename(snum), win_errstr(result)); + result = WERR_INVALID_PRINTER_NAME; + goto done; + } + + if (pinfo2->drivername == NULL || pinfo2->drivername[0] == '\0') { + result = WERR_UNKNOWN_PRINTER_DRIVER; + goto done; + } + + DBG_INFO("Construct printer driver [%s] for [%s]\n", + pinfo2->drivername, + pinfo2->sharename); + + result = winreg_get_driver(tmp_ctx, b, + architecture, + pinfo2->drivername, version, &driver); + + DBG_INFO("winreg_get_driver() status: %s\n", + win_errstr(result)); + + if (!W_ERROR_IS_OK(result)) { + /* + * Is this a W2k client ? + */ + + if (version < 3) { + result = WERR_UNKNOWN_PRINTER_DRIVER; + goto done; + } + + /* Yes - try again with a WinNT driver. */ + version = 2; + result = winreg_get_driver(tmp_ctx, b, + architecture, + pinfo2->drivername, + version, &driver); + DEBUG(8,("construct_printer_driver_level: status: %s\n", + win_errstr(result))); + if (!W_ERROR_IS_OK(result)) { + result = WERR_UNKNOWN_PRINTER_DRIVER; + goto done; + } + } + + /* these are allocated on mem_ctx and not tmp_ctx because they are + * the 'return value' and need to outlive this call */ + switch (level) { + case 1: + result = fill_printer_driver_info1(mem_ctx, &r->info1, driver, servername); + break; + case 2: + result = fill_printer_driver_info2(mem_ctx, &r->info2, driver, servername); + break; + case 3: + result = fill_printer_driver_info3(mem_ctx, &r->info3, driver, servername); + break; + case 4: + result = fill_printer_driver_info4(mem_ctx, &r->info4, driver, servername); + break; + case 5: + result = fill_printer_driver_info5(mem_ctx, &r->info5, driver, servername); + break; + case 6: + result = fill_printer_driver_info6(mem_ctx, &r->info6, driver, servername); + break; + case 8: + result = fill_printer_driver_info8(mem_ctx, &r->info8, driver, servername); + break; +#if 0 /* disabled until marshalling issues are resolved - gd */ + case 101: + result = fill_printer_driver_info101(mem_ctx, &r->info101, driver, servername); + break; +#endif + default: + result = WERR_INVALID_LEVEL; + break; + } + +done: + talloc_free(tmp_ctx); + return result; +} + +/**************************************************************** + _spoolss_GetPrinterDriver2 +****************************************************************/ + +WERROR _spoolss_GetPrinterDriver2(struct pipes_struct *p, + struct spoolss_GetPrinterDriver2 *r) +{ + struct printer_handle *printer; + WERROR result; + uint32_t version = r->in.client_major_version; + + int snum; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + DEBUG(4,("_spoolss_GetPrinterDriver2\n")); + + if (!(printer = find_printer_index_by_hnd(p, r->in.handle))) { + DEBUG(0,("_spoolss_GetPrinterDriver2: invalid printer handle!\n")); + result = WERR_INVALID_PRINTER_NAME; + goto err_info_free; + } + + *r->out.needed = 0; + *r->out.server_major_version = 0; + *r->out.server_minor_version = 0; + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + result = WERR_INVALID_HANDLE; + goto err_info_free; + } + + if (r->in.client_major_version == SPOOLSS_DRIVER_VERSION_2012) { + DEBUG(3,("_spoolss_GetPrinterDriver2: v4 driver requested, " + "downgrading to v3\n")); + version = SPOOLSS_DRIVER_VERSION_200X; + } + + result = construct_printer_driver_info_level(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + r->in.level, r->out.info, + snum, printer->servername, + r->in.architecture, + version); + if (!W_ERROR_IS_OK(result)) { + goto err_info_free; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_DriverInfo, + r->out.info, r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); + +err_info_free: + TALLOC_FREE(r->out.info); + return result; +} + + +/**************************************************************** + _spoolss_StartPagePrinter +****************************************************************/ + +WERROR _spoolss_StartPagePrinter(struct pipes_struct *p, + struct spoolss_StartPagePrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(3,("_spoolss_StartPagePrinter: " + "Error in startpageprinter printer handle\n")); + return WERR_INVALID_HANDLE; + } + + Printer->page_started = true; + return WERR_OK; +} + +/**************************************************************** + _spoolss_EndPagePrinter +****************************************************************/ + +WERROR _spoolss_EndPagePrinter(struct pipes_struct *p, + struct spoolss_EndPagePrinter *r) +{ + int snum; + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(2,("_spoolss_EndPagePrinter: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + Printer->page_started = false; + print_job_endpage(p->msg_ctx, snum, Printer->jobid); + + return WERR_OK; +} + +/**************************************************************** + _spoolss_StartDocPrinter +****************************************************************/ + +WERROR _spoolss_StartDocPrinter(struct pipes_struct *p, + struct spoolss_StartDocPrinter *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_DocumentInfo1 *info_1; + int snum; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + WERROR werr; + char *rhost; + int rc; + + if (!Printer) { + DEBUG(2,("_spoolss_StartDocPrinter: " + "Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (Printer->jobid) { + DEBUG(2, ("_spoolss_StartDocPrinter: " + "StartDocPrinter called twice! " + "(existing jobid = %d)\n", Printer->jobid)); + return WERR_INVALID_HANDLE; + } + + if (r->in.info_ctr->level != 1) { + return WERR_INVALID_LEVEL; + } + + info_1 = r->in.info_ctr->info.info1; + + /* + * a nice thing with NT is it doesn't listen to what you tell it. + * when asked to send _only_ RAW data, it tries to send data + * in EMF format. + * + * So I add checks like in NT Server ... + */ + + if (info_1->datatype) { + /* + * The v4 driver model used in Windows 8 declares print jobs + * intended to bypass the XPS processing layer by setting + * datatype to "XPS_PASS" instead of "RAW". + */ + if ((strcmp(info_1->datatype, "RAW") != 0) + && (strcmp(info_1->datatype, "XPS_PASS") != 0)) { + *r->out.job_id = 0; + return WERR_INVALID_DATATYPE; + } + } + + /* get the share number of the printer */ + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + rc = get_remote_hostname(remote_address, + &rhost, + p->mem_ctx); + if (rc < 0) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (strequal(rhost,"UNKNOWN")) { + rhost = tsocket_address_inet_addr_string(remote_address, + p->mem_ctx); + if (rhost == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + werr = print_job_start(session_info, + p->msg_ctx, + rhost, + snum, + info_1->document_name, + info_1->output_file, + Printer->devmode, + &Printer->jobid); + + /* An error occurred in print_job_start() so return an appropriate + NT error code. */ + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + Printer->document_started = true; + *r->out.job_id = Printer->jobid; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_EndDocPrinter +****************************************************************/ + +WERROR _spoolss_EndDocPrinter(struct pipes_struct *p, + struct spoolss_EndDocPrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + NTSTATUS status; + int snum; + + if (!Printer) { + DEBUG(2,("_spoolss_EndDocPrinter: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + Printer->document_started = false; + status = print_job_end(p->msg_ctx, snum, Printer->jobid, NORMAL_CLOSE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("_spoolss_EndDocPrinter: " + "print_job_end failed [%s]\n", + nt_errstr(status))); + } + + Printer->jobid = 0; + return ntstatus_to_werror(status); +} + +/**************************************************************** + _spoolss_WritePrinter +****************************************************************/ + +WERROR _spoolss_WritePrinter(struct pipes_struct *p, + struct spoolss_WritePrinter *r) +{ + ssize_t buffer_written; + int snum; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(2,("_spoolss_WritePrinter: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(r->in.handle))); + *r->out.num_written = r->in._data_size; + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + /* print_job_write takes care of checking for PJOB_SMBD_SPOOLING */ + buffer_written = print_job_write(global_event_context(),p->msg_ctx, + snum, Printer->jobid, + (const char *)r->in.data.data, + (size_t)r->in._data_size); + if (buffer_written == (ssize_t)-1) { + *r->out.num_written = 0; + if (errno == ENOSPC) + return WERR_NO_SPOOL_SPACE; + else + return WERR_ACCESS_DENIED; + } + + *r->out.num_written = r->in._data_size; + + return WERR_OK; +} + +/******************************************************************** + * api_spoolss_getprinter + * called from the spoolss dispatcher + * + ********************************************************************/ + +static WERROR control_printer(struct policy_handle *handle, uint32_t command, + 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); + int snum; + WERROR errcode = WERR_INVALID_FUNCTION; + struct printer_handle *Printer = find_printer_index_by_hnd(p, handle); + + if (!Printer) { + DEBUG(2,("control_printer: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + switch (command) { + case SPOOLSS_PRINTER_CONTROL_PAUSE: + errcode = print_queue_pause(session_info, p->msg_ctx, snum); + break; + case SPOOLSS_PRINTER_CONTROL_RESUME: + case SPOOLSS_PRINTER_CONTROL_UNPAUSE: + errcode = print_queue_resume(session_info, p->msg_ctx, snum); + break; + case SPOOLSS_PRINTER_CONTROL_PURGE: + errcode = print_queue_purge(session_info, p->msg_ctx, snum); + break; + default: + return WERR_INVALID_LEVEL; + } + + return errcode; +} + + +/**************************************************************** + _spoolss_AbortPrinter + * From MSDN: "Deletes printer's spool file if printer is configured + * for spooling" +****************************************************************/ + +WERROR _spoolss_AbortPrinter(struct pipes_struct *p, + struct spoolss_AbortPrinter *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum; + WERROR errcode = WERR_OK; + + if (!Printer) { + DEBUG(2,("_spoolss_AbortPrinter: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + if (!Printer->document_started) { + return WERR_SPL_NO_STARTDOC; + } + + errcode = print_job_delete(session_info, + p->msg_ctx, + snum, + Printer->jobid); + + return errcode; +} + +/******************************************************************** + * called by spoolss_api_setprinter + * when updating a printer description + ********************************************************************/ + +static WERROR update_printer_sec(struct policy_handle *handle, + struct pipes_struct *p, + struct sec_desc_buf *secdesc_ctr) +{ + struct spoolss_security_descriptor *new_secdesc = NULL; + struct spoolss_security_descriptor *old_secdesc = NULL; + const char *printer = NULL; + WERROR result; + int snum = -1; + struct printer_handle *Printer = find_printer_index_by_hnd(p, handle); + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + bool ok = false; + + if (!Printer) { + DEBUG(2,("update_printer_sec: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(handle))); + + result = WERR_INVALID_HANDLE; + goto done; + } + + if (secdesc_ctr == NULL) { + DEBUG(10,("update_printer_sec: secdesc_ctr is NULL !\n")); + result = WERR_INVALID_PARAMETER; + goto done; + } + + switch (Printer->printer_type) { + case SPLHND_SERVER: + break; + case SPLHND_PRINTER: + if (!get_printer_snum(p, handle, &snum, NULL)) { + DEBUG(2,("update_printer_sec: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(handle))); + result = WERR_INVALID_HANDLE; + goto done; + } + printer = lp_const_servicename(snum); + break; + default: + break; + } + + /* Check the user has permissions to change the security + descriptor. By experimentation with two NT machines, the user + requires Full Access to the printer to change security + information. */ + + switch (Printer->printer_type) { + case SPLHND_SERVER: + ok = Printer->access_granted == SERVER_ACCESS_ADMINISTER; + break; + case SPLHND_PRINTER: + ok = Printer->access_granted == PRINTER_ACCESS_ADMINISTER; + break; + default: + break; + } + + if (!ok) { + DEBUG(4,("update_printer_sec: updated denied by printer permissions " + "(access_granted: 0x%08x)\n", Printer->access_granted)); + result = WERR_ACCESS_DENIED; + goto done; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + /* NT seems to like setting the security descriptor even though + nothing may have actually changed. */ + + if (printer != NULL) { + result = winreg_get_printer_secdesc(tmp_ctx, b, + printer, + &old_secdesc); + } else { + result = winreg_get_printserver_secdesc(tmp_ctx, b, + &old_secdesc); + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(2,("update_printer_sec: winreg_get_printer_secdesc_internal() failed\n")); + result = WERR_INVALID_HANDLE; + goto done; + } + + if (DEBUGLEVEL >= 10) { + struct dom_sid_buf buf; + struct security_acl *the_acl; + int i; + + the_acl = old_secdesc->dacl; + DEBUG(10, ("old_secdesc_ctr for %s has %d aces:\n", + printer, the_acl->num_aces)); + + for (i = 0; i < the_acl->num_aces; i++) { + DEBUG(10, ("%s 0x%08x\n", + dom_sid_str_buf( + &the_acl->aces[i].trustee, + &buf), + the_acl->aces[i].access_mask)); + } + + the_acl = secdesc_ctr->sd->dacl; + + if (the_acl) { + DEBUG(10, ("secdesc_ctr for %s has %d aces:\n", + printer, the_acl->num_aces)); + + for (i = 0; i < the_acl->num_aces; i++) { + DEBUG(10, ("%s 0x%08x\n", + dom_sid_str_buf( + &the_acl->aces[i].trustee, + &buf), + the_acl->aces[i].access_mask)); + } + } else { + DEBUG(10, ("dacl for secdesc_ctr is NULL\n")); + } + } + + new_secdesc = sec_desc_merge(tmp_ctx, secdesc_ctr->sd, old_secdesc); + if (new_secdesc == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + if (security_descriptor_equal(new_secdesc, old_secdesc)) { + result = WERR_OK; + goto done; + } + + if (printer != NULL) { + result = winreg_set_printer_secdesc(tmp_ctx, b, + printer, + new_secdesc); + } else { + result = winreg_set_printserver_secdesc(tmp_ctx, b, + new_secdesc); + } + +done: + talloc_free(tmp_ctx); + return result; +} + +/******************************************************************** + Canonicalize printer info from a client + ********************************************************************/ + +static bool check_printer_ok(TALLOC_CTX *mem_ctx, + struct spoolss_SetPrinterInfo2 *info2, + int snum) +{ + fstring printername; + const char *p; + + DEBUG(5,("check_printer_ok: servername=%s printername=%s sharename=%s " + "portname=%s drivername=%s comment=%s location=%s\n", + info2->servername, info2->printername, info2->sharename, + info2->portname, info2->drivername, info2->comment, + info2->location)); + + /* we force some elements to "correct" values */ + info2->servername = talloc_asprintf(mem_ctx, "\\\\%s", lp_netbios_name()); + if (info2->servername == NULL) { + return false; + } + info2->sharename = talloc_strdup(mem_ctx, lp_const_servicename(snum)); + if (info2->sharename == NULL) { + return false; + } + + /* check to see if we allow printername != sharename */ + if (lp_force_printername(snum)) { + info2->printername = talloc_asprintf(mem_ctx, "\\\\%s\\%s", + lp_netbios_name(), info2->sharename); + } else { + /* make sure printername is in \\server\printername format */ + fstrcpy(printername, info2->printername); + p = printername; + if ( printername[0] == '\\' && printername[1] == '\\' ) { + if ( (p = strchr_m( &printername[2], '\\' )) != NULL ) + p++; + } + + info2->printername = talloc_asprintf(mem_ctx, "\\\\%s\\%s", + lp_netbios_name(), p); + } + if (info2->printername == NULL) { + return false; + } + + info2->attributes |= PRINTER_ATTRIBUTE_SAMBA; + info2->attributes &= ~PRINTER_ATTRIBUTE_NOT_SAMBA; + + return true; +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR add_port_hook(TALLOC_CTX *ctx, struct security_token *token, const char *portname, const char *uri) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *cmd = lp_addport_command(talloc_tos(), lp_sub); + char *command = NULL; + int ret; + bool is_print_op = false; + + if ( !*cmd ) { + return WERR_ACCESS_DENIED; + } + + command = talloc_asprintf(ctx, + "%s \"%s\" \"%s\"", cmd, portname, uri ); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if ( token ) + is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR); + + DEBUG(10,("Running [%s]\n", command)); + + /********* BEGIN SePrintOperatorPrivilege **********/ + + if ( is_print_op ) + become_root(); + + ret = smbrun(command, NULL, NULL); + + if ( is_print_op ) + unbecome_root(); + + /********* END SePrintOperatorPrivilege **********/ + + DEBUGADD(10,("returned [%d]\n", ret)); + + TALLOC_FREE(command); + + if ( ret != 0 ) { + return WERR_ACCESS_DENIED; + } + + return WERR_OK; +} + +/**************************************************************************** +****************************************************************************/ + +static bool spoolss_conn_snum_used(struct smbd_server_connection *sconn, + int snum) +{ + /* + * As we do not know if we are embedded in the file server process + * or not, we have to pretend that all shares are in use. + */ + return true; +} + +static bool add_printer_hook(TALLOC_CTX *ctx, struct security_token *token, + struct spoolss_SetPrinterInfo2 *info2, + const char *remote_machine, + struct messaging_context *msg_ctx) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *cmd = lp_addprinter_command(talloc_tos(), lp_sub); + char **qlines; + char *command = NULL; + int numlines; + int ret; + int fd; + bool is_print_op = false; + + if (!remote_machine) { + return false; + } + + command = talloc_asprintf(ctx, + "%s \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"", + cmd, info2->printername, info2->sharename, + info2->portname, info2->drivername, + info2->location, info2->comment, remote_machine); + if (!command) { + return false; + } + + if ( token ) + is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR); + + DEBUG(10,("Running [%s]\n", command)); + + /********* BEGIN SePrintOperatorPrivilege **********/ + + if ( is_print_op ) + become_root(); + + ret = smbrun(command, &fd, NULL); + if (ret == 0) { + /* Tell everyone we updated smb.conf. */ + messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + } + + if ( is_print_op ) + unbecome_root(); + + /********* END SePrintOperatorPrivilege **********/ + + DEBUGADD(10,("returned [%d]\n", ret)); + + TALLOC_FREE(command); + + if ( ret != 0 ) { + if (fd != -1) + close(fd); + return false; + } + + /* reload our services immediately */ + become_root(); + reload_services(NULL, spoolss_conn_snum_used, false); + unbecome_root(); + + numlines = 0; + /* Get lines and convert them back to dos-codepage */ + qlines = fd_lines_load(fd, &numlines, 0, NULL); + DEBUGADD(10,("Lines returned = [%d]\n", numlines)); + close(fd); + + /* Set the portname to what the script says the portname should be. */ + /* but don't require anything to be return from the script exit a good error code */ + + if (numlines) { + /* Set the portname to what the script says the portname should be. */ + info2->portname = talloc_strdup(ctx, qlines[0]); + DEBUGADD(6,("Line[0] = [%s]\n", qlines[0])); + } + + TALLOC_FREE(qlines); + return true; +} + +static WERROR update_dsspooler(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + int snum, + struct spoolss_SetPrinterInfo2 *printer, + struct spoolss_PrinterInfo2 *old_printer) +{ + bool force_update = (old_printer == NULL); + const char *dnsdomname; + const char *longname; + const char *uncname; + const char *spooling; + DATA_BLOB buffer; + WERROR result = WERR_OK; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + bool ok; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, + session_info, + msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + if (printer->drivername != NULL && + (force_update || + !strequal(printer->drivername, old_printer->drivername))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->drivername); + if (!ok) { + DEBUG(0, ("%s data corrupted\n", SPOOL_REG_DRIVERNAME)); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_DRIVERNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_DRIVERNAME)); + goto done; + } + + if (!force_update) { + DEBUG(10,("update_printer: changing driver [%s]! Sending event!\n", + printer->drivername)); + + notify_printer_driver(global_event_context(), msg_ctx, + snum, printer->drivername ? + printer->drivername : ""); + } + } + + if (printer->comment != NULL && + (force_update || + !strequal(printer->comment, old_printer->comment))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->comment); + if (!ok) { + DEBUG(0, ("comment data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_DESCRIPTION, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_DESCRIPTION)); + goto done; + } + + if (!force_update) { + notify_printer_comment(global_event_context(), msg_ctx, + snum, printer->comment ? + printer->comment : ""); + } + } + + if (printer->sharename != NULL && + (force_update || + !strequal(printer->sharename, old_printer->sharename))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->sharename); + if (!ok) { + DEBUG(0, ("sharename data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTSHARENAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSHARENAME)); + goto done; + } + + if (!force_update) { + notify_printer_sharename(global_event_context(), + msg_ctx, + snum, printer->sharename ? + printer->sharename : ""); + } + + /* name change, purge any cache entries for the old */ + prune_printername_cache(); + } + + if (printer->printername != NULL && + (force_update || + !strequal(printer->printername, old_printer->printername))) { + const char *p; + + p = strrchr(printer->printername, '\\' ); + if (p != NULL) { + p++; + } else { + p = printer->printername; + } + + ok = push_reg_sz(tmp_ctx, &buffer, p); + if (!ok) { + DEBUG(0, ("printername data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTERNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DBG_ERR("Failed to set %s\n", SPOOL_REG_PRINTERNAME); + goto done; + } + + if (!force_update) { + notify_printer_printername(global_event_context(), + msg_ctx, snum, p ? p : ""); + } + + /* name change, purge any cache entries for the old */ + prune_printername_cache(); + } + + if (printer->portname != NULL && + (force_update || + !strequal(printer->portname, old_printer->portname))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->portname); + if (!ok) { + DEBUG(0, ("portname data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PORTNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PORTNAME)); + goto done; + } + + if (!force_update) { + notify_printer_port(global_event_context(), + msg_ctx, snum, printer->portname ? + printer->portname : ""); + } + } + + if (printer->location != NULL && + (force_update || + !strequal(printer->location, old_printer->location))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->location); + if (!ok) { + DEBUG(0, ("location data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_LOCATION, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_LOCATION)); + goto done; + } + + if (!force_update) { + notify_printer_location(global_event_context(), + msg_ctx, snum, + printer->location ? + printer->location : ""); + } + } + + if (printer->sepfile != NULL && + (force_update || + !strequal(printer->sepfile, old_printer->sepfile))) { + ok = push_reg_sz(tmp_ctx, &buffer, printer->sepfile); + if (!ok) { + DEBUG(0, ("sepfile data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTSEPARATORFILE, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSEPARATORFILE)); + goto done; + } + + if (!force_update) { + notify_printer_sepfile(global_event_context(), + msg_ctx, snum, + printer->sepfile ? + printer->sepfile : ""); + } + } + + if (printer->starttime != 0 && + (force_update || + printer->starttime != old_printer->starttime)) { + buffer = data_blob_talloc(tmp_ctx, NULL, 4); + SIVAL(buffer.data, 0, printer->starttime); + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTSTARTTIME, + REG_DWORD, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSTARTTIME)); + goto done; + } + } + + if (printer->untiltime != 0 && + (force_update || + printer->untiltime != old_printer->untiltime)) { + buffer = data_blob_talloc(tmp_ctx, NULL, 4); + SIVAL(buffer.data, 0, printer->untiltime); + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTENDTIME, + REG_DWORD, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME)); + goto done; + } + } + + if (force_update || printer->priority != old_printer->priority) { + buffer = data_blob_talloc(tmp_ctx, NULL, 4); + SIVAL(buffer.data, 0, printer->priority); + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRIORITY, + REG_DWORD, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME)); + goto done; + } + } + + if (force_update || printer->attributes != old_printer->attributes) { + buffer = data_blob_talloc(tmp_ctx, NULL, 4); + SIVAL(buffer.data, 0, (printer->attributes & + PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS)); + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTKEEPPRINTEDJOBS, + REG_DWORD, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME)); + goto done; + } + + switch (printer->attributes & 0x3) { + case 0: + spooling = SPOOL_REGVAL_PRINTWHILESPOOLING; + break; + case 1: + spooling = SPOOL_REGVAL_PRINTAFTERSPOOLED; + break; + case 2: + spooling = SPOOL_REGVAL_PRINTDIRECT; + break; + default: + spooling = "unknown"; + } + ok = push_reg_sz(tmp_ctx, &buffer, spooling); + if (!ok) { + DEBUG(0, ("printSpooling data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_PRINTSPOOLING, + REG_SZ, + buffer.data, + buffer.length); + } + + ok = push_reg_sz(tmp_ctx, &buffer, lp_netbios_name()); + if (!ok) { + DEBUG(0, ("shortServerName data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_SHORTSERVERNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_SHORTSERVERNAME)); + goto done; + } + + dnsdomname = get_mydnsfullname(); + if (dnsdomname != NULL && dnsdomname[0] != '\0') { + longname = talloc_strdup(tmp_ctx, dnsdomname); + } else { + longname = talloc_strdup(tmp_ctx, lp_netbios_name()); + } + if (longname == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + ok = push_reg_sz(tmp_ctx, &buffer, longname); + if (!ok) { + DEBUG(0, ("longname data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_SERVERNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_SERVERNAME)); + goto done; + } + + uncname = talloc_asprintf(tmp_ctx, "\\\\%s\\%s", + lp_netbios_name(), printer->sharename); + ok = push_reg_sz(tmp_ctx, &buffer, uncname); + if (!ok) { + DEBUG(0, ("uncName data corrupted\n")); + result = WERR_INVALID_DATA; + goto done; + } + result = winreg_set_printer_dataex(tmp_ctx, b, + printer->sharename, + SPOOL_DSSPOOLER_KEY, + SPOOL_REG_UNCNAME, + REG_SZ, + buffer.data, + buffer.length); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Failed to set %s\n", SPOOL_REG_UNCNAME)); + goto done; + } + +done: + talloc_free(tmp_ctx); + return result; +} + +/******************************************************************** + * Called by spoolss_api_setprinter + * when updating a printer description. + ********************************************************************/ + +static WERROR update_printer(struct pipes_struct *p, + struct policy_handle *handle, + struct spoolss_SetPrinterInfoCtr *info_ctr, + struct spoolss_DeviceMode *devmode) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + uint32_t printer_mask = SPOOLSS_PRINTER_INFO_ALL; + struct spoolss_SetPrinterInfo2 *printer = info_ctr->info.info2; + struct spoolss_PrinterInfo2 *old_printer; + struct printer_handle *Printer = find_printer_index_by_hnd(p, handle); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int snum; + WERROR result = WERR_OK; + TALLOC_CTX *tmp_ctx; + struct dcerpc_binding_handle *b; + + DEBUG(8,("update_printer\n")); + + tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (!Printer) { + result = WERR_INVALID_HANDLE; + goto done; + } + + if (!get_printer_snum(p, handle, &snum, NULL)) { + result = WERR_INVALID_HANDLE; + goto done; + } + + result = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + result = winreg_get_printer(tmp_ctx, b, + lp_const_servicename(snum), + &old_printer); + if (!W_ERROR_IS_OK(result)) { + result = WERR_INVALID_HANDLE; + goto done; + } + + /* Do sanity check on the requested changes for Samba */ + if (!check_printer_ok(tmp_ctx, printer, snum)) { + result = WERR_INVALID_PARAMETER; + goto done; + } + + /* FIXME!!! If the driver has changed we really should verify that + it is installed before doing much else --jerry */ + + /* Check calling user has permission to update printer description */ + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("update_printer: printer property change denied by handle\n")); + result = WERR_ACCESS_DENIED; + goto done; + } + + /* Call addprinter hook */ + /* Check changes to see if this is really needed */ + + if (*lp_addprinter_command(talloc_tos(), lp_sub) && + (!strequal(printer->drivername, old_printer->drivername) || + !strequal(printer->comment, old_printer->comment) || + !strequal(printer->portname, old_printer->portname) || + !strequal(printer->location, old_printer->location)) ) + { + char *raddr; + + raddr = tsocket_address_inet_addr_string(remote_address, + p->mem_ctx); + if (raddr == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + /* add_printer_hook() will call reload_services() */ + if (!add_printer_hook(tmp_ctx, session_info->security_token, + printer, raddr, + p->msg_ctx)) { + result = WERR_ACCESS_DENIED; + goto done; + } + } + + result = update_dsspooler(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + snum, + printer, + old_printer); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + printer_mask &= ~SPOOLSS_PRINTER_INFO_SECDESC; + + if (devmode == NULL) { + printer_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE; + } + result = winreg_update_printer(tmp_ctx, b, + printer->sharename, + printer_mask, + printer, + devmode, + NULL); + +done: + talloc_free(tmp_ctx); + + return result; +} + +/**************************************************************************** +****************************************************************************/ +static WERROR publish_or_unpublish_printer(struct pipes_struct *p, + struct policy_handle *handle, + struct spoolss_SetPrinterInfo7 *info7) +{ +#ifdef HAVE_ADS + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + WERROR result; + int snum; + struct printer_handle *Printer; + + if ( lp_security() != SEC_ADS ) { + return WERR_INVALID_LEVEL; + } + + Printer = find_printer_index_by_hnd(p, handle); + + DEBUG(5,("publish_or_unpublish_printer, action = %d\n",info7->action)); + + if (!Printer) + return WERR_INVALID_HANDLE; + + if (!get_printer_snum(p, handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + result = winreg_get_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_servicename(talloc_tos(), lp_sub, snum), + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + return WERR_INVALID_HANDLE; + } + + nt_printer_publish(pinfo2, + get_session_info_system(), + p->msg_ctx, + pinfo2, + info7->action); + + TALLOC_FREE(pinfo2); + return WERR_OK; +#else + return WERR_INVALID_LEVEL; +#endif +} + +/******************************************************************** + ********************************************************************/ + +static WERROR update_printer_devmode(struct pipes_struct *p, + struct policy_handle *handle, + struct spoolss_DeviceMode *devmode) +{ + int snum; + struct printer_handle *Printer = find_printer_index_by_hnd(p, handle); + uint32_t info2_mask = SPOOLSS_PRINTER_INFO_DEVMODE; + + DEBUG(8,("update_printer_devmode\n")); + + if (!Printer) { + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + /* Check calling user has permission to update printer description */ + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("update_printer: printer property change denied by handle\n")); + return WERR_ACCESS_DENIED; + } + + return winreg_update_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + info2_mask, + NULL, + devmode, + NULL); +} + + +/**************************************************************** + _spoolss_SetPrinter +****************************************************************/ + +WERROR _spoolss_SetPrinter(struct pipes_struct *p, + struct spoolss_SetPrinter *r) +{ + WERROR result; + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(2,("_spoolss_SetPrinter: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* check the level */ + switch (r->in.info_ctr->level) { + case 0: + return control_printer(r->in.handle, r->in.command, p); + case 2: + result = update_printer(p, r->in.handle, + r->in.info_ctr, + r->in.devmode_ctr->devmode); + if (!W_ERROR_IS_OK(result)) + return result; + if (r->in.secdesc_ctr->sd) + result = update_printer_sec(r->in.handle, p, + r->in.secdesc_ctr); + return result; + case 3: + return update_printer_sec(r->in.handle, p, + r->in.secdesc_ctr); + case 4: { + struct spoolss_PrinterInfo2 *old_printer; + struct spoolss_SetPrinterInfo2 *set_old_printer; + struct spoolss_SetPrinterInfoCtr *info_ctr; + struct dcerpc_binding_handle *b; + int snum; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(p->mem_ctx); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + TALLOC_FREE(tmp_ctx); + return WERR_INVALID_HANDLE; + } + + result = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(tmp_ctx); + return result; + } + + result = winreg_get_printer(tmp_ctx, b, + lp_const_servicename(snum), + &old_printer); + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(tmp_ctx); + return WERR_INVALID_HANDLE; + } + + old_printer->servername = talloc_strdup(tmp_ctx, r->in.info_ctr->info.info4->servername); + if (old_printer->servername == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + old_printer->printername = talloc_strdup(tmp_ctx, r->in.info_ctr->info.info4->printername); + if (old_printer->printername == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + old_printer->attributes = r->in.info_ctr->info.info4->attributes; + + set_old_printer = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfo2); + if (set_old_printer == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + spoolss_printerinfo2_to_setprinterinfo2(old_printer, set_old_printer); + + info_ctr = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfoCtr); + if (info_ctr == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + info_ctr->level = 2; + info_ctr->info.info2 = set_old_printer; + + result = update_printer(p, r->in.handle, + info_ctr, + r->in.devmode_ctr->devmode); + + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(tmp_ctx); + return result; + } + + if (r->in.secdesc_ctr->sd) { + result = update_printer_sec(r->in.handle, p, + r->in.secdesc_ctr); + } + + TALLOC_FREE(tmp_ctx); + return result; + } + case 7: + return publish_or_unpublish_printer(p, r->in.handle, + r->in.info_ctr->info.info7); + case 8: + return update_printer_devmode(p, r->in.handle, + r->in.devmode_ctr->devmode); + default: + return WERR_INVALID_LEVEL; + } +} + +/**************************************************************** + _spoolss_FindClosePrinterNotify +****************************************************************/ + +WERROR _spoolss_FindClosePrinterNotify(struct pipes_struct *p, + struct spoolss_FindClosePrinterNotify *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + if (!Printer) { + DEBUG(2,("_spoolss_FindClosePrinterNotify: " + "Invalid handle (%s:%u:%u)\n", OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (Printer->notify.cli_chan != NULL && + Printer->notify.cli_chan->active_connections > 0) { + int snum = -1; + + if (Printer->printer_type == SPLHND_PRINTER) { + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + } + + srv_spoolss_replycloseprinter(snum, Printer); + } + + Printer->notify.flags=0; + Printer->notify.options=0; + Printer->notify.localmachine[0]='\0'; + Printer->notify.printerlocal=0; + TALLOC_FREE(Printer->notify.option); + + return WERR_OK; +} + +/**************************************************************** + _spoolss_AddJob +****************************************************************/ + +WERROR _spoolss_AddJob(struct pipes_struct *p, + struct spoolss_AddJob *r) +{ + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + /* this is what a NT server returns for AddJob. AddJob must fail on + * non-local printers */ + + if (r->in.level != 1) { + return WERR_INVALID_LEVEL; + } + + return WERR_INVALID_PARAMETER; +} + +/**************************************************************************** +fill_job_info1 +****************************************************************************/ + +static WERROR fill_job_info1(TALLOC_CTX *mem_ctx, + struct spoolss_JobInfo1 *r, + const print_queue_struct *queue, + uint32_t jobid, + int position, int snum, + struct spoolss_PrinterInfo2 *pinfo2) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct tm *t; + + t = gmtime(&queue->time); + + r->job_id = jobid; + + r->printer_name = lp_servicename(mem_ctx, lp_sub, snum); + W_ERROR_HAVE_NO_MEMORY(r->printer_name); + r->server_name = talloc_strdup(mem_ctx, pinfo2->servername); + W_ERROR_HAVE_NO_MEMORY(r->server_name); + r->user_name = talloc_strdup(mem_ctx, queue->fs_user); + W_ERROR_HAVE_NO_MEMORY(r->user_name); + r->document_name = talloc_strdup(mem_ctx, queue->fs_file); + W_ERROR_HAVE_NO_MEMORY(r->document_name); + r->data_type = talloc_strdup(mem_ctx, "RAW"); + W_ERROR_HAVE_NO_MEMORY(r->data_type); + r->text_status = talloc_strdup(mem_ctx, ""); + W_ERROR_HAVE_NO_MEMORY(r->text_status); + + r->status = nt_printj_status(queue->status); + r->priority = queue->priority; + r->position = position; + r->total_pages = queue->page_count; + r->pages_printed = 0; /* ??? */ + + init_systemtime(&r->submitted, t); + + return WERR_OK; +} + +/**************************************************************************** +fill_job_info2 +****************************************************************************/ + +static WERROR fill_job_info2(TALLOC_CTX *mem_ctx, + struct spoolss_JobInfo2 *r, + const print_queue_struct *queue, + uint32_t jobid, + int position, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + struct spoolss_DeviceMode *devmode) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct tm *t; + + t = gmtime(&queue->time); + + r->job_id = jobid; + + r->printer_name = lp_servicename(mem_ctx, lp_sub, snum); + W_ERROR_HAVE_NO_MEMORY(r->printer_name); + r->server_name = talloc_strdup(mem_ctx, pinfo2->servername); + W_ERROR_HAVE_NO_MEMORY(r->server_name); + r->user_name = talloc_strdup(mem_ctx, queue->fs_user); + W_ERROR_HAVE_NO_MEMORY(r->user_name); + r->document_name = talloc_strdup(mem_ctx, queue->fs_file); + W_ERROR_HAVE_NO_MEMORY(r->document_name); + r->notify_name = talloc_strdup(mem_ctx, queue->fs_user); + W_ERROR_HAVE_NO_MEMORY(r->notify_name); + r->data_type = talloc_strdup(mem_ctx, "RAW"); + W_ERROR_HAVE_NO_MEMORY(r->data_type); + r->print_processor = talloc_strdup(mem_ctx, "winprint"); + W_ERROR_HAVE_NO_MEMORY(r->print_processor); + r->parameters = talloc_strdup(mem_ctx, ""); + W_ERROR_HAVE_NO_MEMORY(r->parameters); + r->driver_name = talloc_strdup(mem_ctx, pinfo2->drivername); + W_ERROR_HAVE_NO_MEMORY(r->driver_name); + + r->devmode = devmode; + + r->text_status = talloc_strdup(mem_ctx, ""); + W_ERROR_HAVE_NO_MEMORY(r->text_status); + + r->secdesc = NULL; + + r->status = nt_printj_status(queue->status); + r->priority = queue->priority; + r->position = position; + r->start_time = 0; + r->until_time = 0; + r->total_pages = queue->page_count; + r->size = queue->size; + init_systemtime(&r->submitted, t); + r->time = 0; + r->pages_printed = 0; /* ??? */ + + return WERR_OK; +} + +/**************************************************************************** + Enumjobs at level 1. +****************************************************************************/ + +static WERROR enumjobs_level1(TALLOC_CTX *mem_ctx, + const print_queue_struct *queue, + uint32_t num_queues, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + union spoolss_JobInfo **info_p, + uint32_t *count) +{ + union spoolss_JobInfo *info; + int i; + WERROR result = WERR_OK; + uint32_t num_filled; + struct tdb_print_db *pdb; + + info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues); + if (info == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + pdb = get_print_db_byname(pinfo2->sharename); + if (pdb == NULL) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + num_filled = 0; + for (i = 0; i < num_queues; i++) { + uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob); + if (jobid == (uint32_t)-1) { + DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob)); + continue; + } + + result = fill_job_info1(info, + &info[num_filled].info1, + &queue[i], + jobid, + i, + snum, + pinfo2); + if (!W_ERROR_IS_OK(result)) { + goto err_pdb_drop; + } + + num_filled++; + } + + release_print_db(pdb); + *info_p = info; + *count = num_filled; + + return WERR_OK; + +err_pdb_drop: + release_print_db(pdb); +err_info_free: + TALLOC_FREE(info); +err_out: + *count = 0; + return result; +} + +/**************************************************************************** + Enumjobs at level 2. +****************************************************************************/ + +static WERROR enumjobs_level2(TALLOC_CTX *mem_ctx, + const print_queue_struct *queue, + uint32_t num_queues, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + union spoolss_JobInfo **info_p, + uint32_t *count) +{ + union spoolss_JobInfo *info; + int i; + WERROR result = WERR_OK; + uint32_t num_filled; + struct tdb_print_db *pdb; + + info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues); + if (info == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + pdb = get_print_db_byname(pinfo2->sharename); + if (pdb == NULL) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + num_filled = 0; + for (i = 0; i< num_queues; i++) { + struct spoolss_DeviceMode *devmode; + uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob); + if (jobid == (uint32_t)-1) { + DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob)); + continue; + } + + result = spoolss_create_default_devmode(info, + pinfo2->printername, + &devmode); + if (!W_ERROR_IS_OK(result)) { + DEBUG(3, ("Can't proceed w/o a devmode!\n")); + goto err_pdb_drop; + } + + result = fill_job_info2(info, + &info[num_filled].info2, + &queue[i], + jobid, + i, + snum, + pinfo2, + devmode); + if (!W_ERROR_IS_OK(result)) { + goto err_pdb_drop; + } + num_filled++; + } + + release_print_db(pdb); + *info_p = info; + *count = num_filled; + + return WERR_OK; + +err_pdb_drop: + release_print_db(pdb); +err_info_free: + TALLOC_FREE(info); +err_out: + *count = 0; + return result; +} + +/**************************************************************************** + Enumjobs at level 3. +****************************************************************************/ + +static WERROR enumjobs_level3(TALLOC_CTX *mem_ctx, + const print_queue_struct *queue, + uint32_t num_queues, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + union spoolss_JobInfo **info_p, + uint32_t *count) +{ + union spoolss_JobInfo *info; + int i; + WERROR result = WERR_OK; + uint32_t num_filled; + struct tdb_print_db *pdb; + + info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues); + if (info == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + pdb = get_print_db_byname(pinfo2->sharename); + if (pdb == NULL) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + num_filled = 0; + for (i = 0; i < num_queues; i++) { + uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob); + if (jobid == (uint32_t)-1) { + DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob)); + continue; + } + + info[num_filled].info3.job_id = jobid; + /* next_job_id is overwritten on next iteration */ + info[num_filled].info3.next_job_id = 0; + info[num_filled].info3.reserved = 0; + + if (num_filled > 0) { + info[num_filled - 1].info3.next_job_id = jobid; + } + num_filled++; + } + + release_print_db(pdb); + *info_p = info; + *count = num_filled; + + return WERR_OK; + +err_info_free: + TALLOC_FREE(info); +err_out: + *count = 0; + return result; +} + +/**************************************************************** + _spoolss_EnumJobs +****************************************************************/ + +WERROR _spoolss_EnumJobs(struct pipes_struct *p, + struct spoolss_EnumJobs *r) +{ + WERROR result; + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + int snum; + print_status_struct prt_status; + print_queue_struct *queue = NULL; + uint32_t count; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + if ((r->in.level != 1) && (r->in.level != 2) && (r->in.level != 3)) { + DEBUG(4, ("EnumJobs level %d not supported\n", r->in.level)); + return WERR_INVALID_LEVEL; + } + + DEBUG(4,("_spoolss_EnumJobs\n")); + + *r->out.needed = 0; + *r->out.count = 0; + *r->out.info = NULL; + + /* lookup the printer snum and tdb entry */ + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + result = winreg_get_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + count = print_queue_status(p->msg_ctx, snum, &queue, &prt_status); + DEBUGADD(4,("count:[%d], status:[%d], [%s]\n", + count, prt_status.status, prt_status.message)); + + if (count == 0) { + SAFE_FREE(queue); + TALLOC_FREE(pinfo2); + return WERR_OK; + } + + switch (r->in.level) { + case 1: + result = enumjobs_level1(p->mem_ctx, queue, count, snum, + pinfo2, r->out.info, r->out.count); + break; + case 2: + result = enumjobs_level2(p->mem_ctx, queue, count, snum, + pinfo2, r->out.info, r->out.count); + break; + case 3: + result = enumjobs_level3(p->mem_ctx, queue, count, snum, + pinfo2, r->out.info, r->out.count); + break; + default: + SMB_ASSERT(false); /* level checked on entry */ + break; + } + + SAFE_FREE(queue); + TALLOC_FREE(pinfo2); + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumJobs, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************** + _spoolss_ScheduleJob +****************************************************************/ + +WERROR _spoolss_ScheduleJob(struct pipes_struct *p, + struct spoolss_ScheduleJob *r) +{ + return WERR_OK; +} + +/**************************************************************** +****************************************************************/ + +static WERROR spoolss_setjob_1(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + const char *printer_name, + uint32_t job_id, + struct spoolss_SetJobInfo1 *r) +{ + char *old_doc_name; + + if (!print_job_get_name(mem_ctx, printer_name, job_id, &old_doc_name)) { + return WERR_INVALID_HANDLE; + } + + if (strequal(old_doc_name, r->document_name)) { + return WERR_OK; + } + + if (!print_job_set_name(global_event_context(), msg_ctx, + printer_name, job_id, r->document_name)) { + return WERR_INVALID_HANDLE; + } + + return WERR_OK; +} + +/**************************************************************** + _spoolss_SetJob +****************************************************************/ + +WERROR _spoolss_SetJob(struct pipes_struct *p, + struct spoolss_SetJob *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + int snum; + WERROR errcode = WERR_INVALID_FUNCTION; + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + if (!print_job_exists(lp_const_servicename(snum), r->in.job_id)) { + return WERR_INVALID_PRINTER_NAME; + } + + switch (r->in.command) { + case SPOOLSS_JOB_CONTROL_CANCEL: + case SPOOLSS_JOB_CONTROL_DELETE: + errcode = print_job_delete(session_info, p->msg_ctx, + snum, r->in.job_id); + if (W_ERROR_EQUAL(errcode, WERR_PRINTER_HAS_JOBS_QUEUED)) { + errcode = WERR_OK; + } + break; + case SPOOLSS_JOB_CONTROL_PAUSE: + errcode = print_job_pause(session_info, p->msg_ctx, + snum, r->in.job_id); + break; + case SPOOLSS_JOB_CONTROL_RESTART: + case SPOOLSS_JOB_CONTROL_RESUME: + errcode = print_job_resume(session_info, p->msg_ctx, + snum, r->in.job_id); + break; + case SPOOLSS_JOB_CONTROL_NOOP: + errcode = WERR_OK; + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(errcode)) { + return errcode; + } + + if (r->in.ctr == NULL) { + return errcode; + } + + switch (r->in.ctr->level) { + case 1: + errcode = spoolss_setjob_1(p->mem_ctx, p->msg_ctx, + lp_const_servicename(snum), + r->in.job_id, + r->in.ctr->info.info1); + break; + case 2: + case 3: + case 4: + default: + return WERR_INVALID_LEVEL; + } + + return errcode; +} + +/**************************************************************************** + Enumerates all printer drivers by level and architecture. +****************************************************************************/ + +static WERROR enumprinterdrivers_level_by_architecture(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + const char *architecture, + uint32_t level, + union spoolss_DriverInfo **info_p, + uint32_t *count_p) +{ + int i; + uint32_t version; + struct spoolss_DriverInfo8 *driver; + union spoolss_DriverInfo *info = NULL; + uint32_t count = 0; + WERROR result = WERR_OK; + uint32_t num_drivers; + const char **drivers; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + + *count_p = 0; + *info_p = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, + session_info, + msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + for (version=0; version<DRIVER_MAX_VERSION; version++) { + result = winreg_get_driver_list(tmp_ctx, b, + architecture, version, + &num_drivers, &drivers); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + DEBUG(4, ("we have:[%d] drivers in environment" + " [%s] and version [%d]\n", + num_drivers, architecture, version)); + + if (num_drivers != 0) { + info = talloc_realloc(tmp_ctx, info, + union spoolss_DriverInfo, + count + num_drivers); + if (!info) { + DEBUG(0,("enumprinterdrivers_level_by_architecture: " + "failed to enlarge driver info buffer!\n")); + result = WERR_NOT_ENOUGH_MEMORY; + goto out; + } + } + + for (i = 0; i < num_drivers; i++) { + DEBUG(5, ("\tdriver: [%s]\n", drivers[i])); + + result = winreg_get_driver(tmp_ctx, b, + architecture, drivers[i], + version, &driver); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + switch (level) { + case 1: + result = fill_printer_driver_info1(info, &info[count+i].info1, + driver, servername); + break; + case 2: + result = fill_printer_driver_info2(info, &info[count+i].info2, + driver, servername); + break; + case 3: + result = fill_printer_driver_info3(info, &info[count+i].info3, + driver, servername); + break; + case 4: + result = fill_printer_driver_info4(info, &info[count+i].info4, + driver, servername); + break; + case 5: + result = fill_printer_driver_info5(info, &info[count+i].info5, + driver, servername); + break; + case 6: + result = fill_printer_driver_info6(info, &info[count+i].info6, + driver, servername); + break; + case 8: + result = fill_printer_driver_info8(info, &info[count+i].info8, + driver, servername); + break; + default: + result = WERR_INVALID_LEVEL; + break; + } + + TALLOC_FREE(driver); + + if (!W_ERROR_IS_OK(result)) { + goto out; + } + } + + count += num_drivers; + TALLOC_FREE(drivers); + } + +out: + if (W_ERROR_IS_OK(result)) { + *info_p = talloc_move(mem_ctx, &info); + *count_p = count; + } + + talloc_free(tmp_ctx); + return result; +} + +/**************************************************************************** + Enumerates all printer drivers by level. +****************************************************************************/ + +static WERROR enumprinterdrivers_level(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *servername, + const char *architecture, + uint32_t level, + union spoolss_DriverInfo **info_p, + uint32_t *count_p) +{ + uint32_t a,i; + WERROR result = WERR_OK; + + if (strequal(architecture, SPOOLSS_ARCHITECTURE_ALL)) { + + for (a=0; archi_table[a].long_archi != NULL; a++) { + + union spoolss_DriverInfo *info = NULL; + uint32_t count = 0; + + result = enumprinterdrivers_level_by_architecture(mem_ctx, + session_info, + msg_ctx, + servername, + archi_table[a].long_archi, + level, + &info, + &count); + if (!W_ERROR_IS_OK(result)) { + continue; + } + + for (i=0; i < count; i++) { + ADD_TO_ARRAY(mem_ctx, union spoolss_DriverInfo, + info[i], info_p, count_p); + } + } + + return result; + } + + return enumprinterdrivers_level_by_architecture(mem_ctx, + session_info, + msg_ctx, + servername, + architecture, + level, + info_p, + count_p); +} + +/**************************************************************** + _spoolss_EnumPrinterDrivers +****************************************************************/ + +WERROR _spoolss_EnumPrinterDrivers(struct pipes_struct *p, + struct spoolss_EnumPrinterDrivers *r) +{ + const char *cservername; + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(4,("_spoolss_EnumPrinterDrivers\n")); + + *r->out.needed = 0; + *r->out.count = 0; + *r->out.info = NULL; + + cservername = canon_servername(r->in.server); + + if (!is_myname_or_ipaddr(cservername)) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + + result = enumprinterdrivers_level(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + cservername, + r->in.environment, + r->in.level, + r->out.info, + r->out.count); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumPrinterDrivers, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************** + _spoolss_EnumForms +****************************************************************/ + +WERROR _spoolss_EnumForms(struct pipes_struct *p, + struct spoolss_EnumForms *r) +{ + WERROR result; + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0) ) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(4,("_spoolss_EnumForms\n")); + DEBUGADD(5,("Offered buffer size [%d]\n", r->in.offered)); + DEBUGADD(5,("Info level [%d]\n", r->in.level)); + + switch (r->in.level) { + case 1: + result = winreg_printer_enumforms1_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + r->out.count, + r->out.info); + break; + default: + result = WERR_INVALID_LEVEL; + break; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + if (*r->out.count == 0) { + return WERR_NO_MORE_ITEMS; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumForms, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************** + _spoolss_GetForm +****************************************************************/ + +WERROR _spoolss_GetForm(struct pipes_struct *p, + struct spoolss_GetForm *r) +{ + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + TALLOC_FREE(r->out.info); + return WERR_INVALID_PARAMETER; + } + + DEBUG(4,("_spoolss_GetForm\n")); + DEBUGADD(5,("Offered buffer size [%d]\n", r->in.offered)); + DEBUGADD(5,("Info level [%d]\n", r->in.level)); + + switch (r->in.level) { + case 1: + result = winreg_printer_getform1_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + r->in.form_name, + &r->out.info->info1); + break; + default: + result = WERR_INVALID_LEVEL; + break; + } + + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(r->out.info); + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_FormInfo, + r->out.info, r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR fill_port_1(TALLOC_CTX *mem_ctx, + struct spoolss_PortInfo1 *r, + const char *name) +{ + r->port_name = talloc_strdup(mem_ctx, name); + W_ERROR_HAVE_NO_MEMORY(r->port_name); + + return WERR_OK; +} + +/**************************************************************************** + TODO: This probably needs distinguish between TCP/IP and Local ports + somehow. +****************************************************************************/ + +static WERROR fill_port_2(TALLOC_CTX *mem_ctx, + struct spoolss_PortInfo2 *r, + const char *name) +{ + r->port_name = talloc_strdup(mem_ctx, name); + W_ERROR_HAVE_NO_MEMORY(r->port_name); + + r->monitor_name = talloc_strdup(mem_ctx, "Local Monitor"); + W_ERROR_HAVE_NO_MEMORY(r->monitor_name); + + r->description = talloc_strdup(mem_ctx, SPL_LOCAL_PORT); + W_ERROR_HAVE_NO_MEMORY(r->description); + + r->port_type = SPOOLSS_PORT_TYPE_WRITE; + r->reserved = 0; + + return WERR_OK; +} + + +/**************************************************************************** + wrapper around the enum ports command +****************************************************************************/ + +static WERROR enumports_hook(TALLOC_CTX *ctx, int *count, char ***lines) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *cmd = lp_enumports_command(talloc_tos(), lp_sub); + char **qlines = NULL; + char *command = NULL; + int numlines; + int ret; + int fd; + + *count = 0; + *lines = NULL; + + /* if no hook then just fill in the default port */ + + if ( !*cmd ) { + if (!(qlines = talloc_array( NULL, char*, 2 ))) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (!(qlines[0] = talloc_strdup(qlines, SAMBA_PRINTER_PORT_NAME ))) { + TALLOC_FREE(qlines); + return WERR_NOT_ENOUGH_MEMORY; + } + qlines[1] = NULL; + numlines = 1; + } + else { + /* we have a valid enumport command */ + + command = talloc_asprintf(ctx, "%s \"%d\"", cmd, 1); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + + DEBUG(10,("Running [%s]\n", command)); + ret = smbrun(command, &fd, NULL); + DEBUG(10,("Returned [%d]\n", ret)); + TALLOC_FREE(command); + if (ret != 0) { + if (fd != -1) { + close(fd); + } + return WERR_ACCESS_DENIED; + } + + numlines = 0; + qlines = fd_lines_load(fd, &numlines, 0, NULL); + DEBUGADD(10,("Lines returned = [%d]\n", numlines)); + close(fd); + } + + *count = numlines; + *lines = qlines; + + return WERR_OK; +} + +/**************************************************************************** + enumports level 1. +****************************************************************************/ + +static WERROR enumports_level_1(TALLOC_CTX *mem_ctx, + union spoolss_PortInfo **info_p, + uint32_t *count) +{ + union spoolss_PortInfo *info = NULL; + int i=0; + WERROR result = WERR_OK; + char **qlines = NULL; + int numlines = 0; + + result = enumports_hook(talloc_tos(), &numlines, &qlines ); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + if (numlines) { + info = talloc_array(mem_ctx, union spoolss_PortInfo, numlines); + if (!info) { + DEBUG(10,("Returning WERR_NOT_ENOUGH_MEMORY\n")); + result = WERR_NOT_ENOUGH_MEMORY; + goto out; + } + + for (i=0; i<numlines; i++) { + DEBUG(6,("Filling port number [%d] with port [%s]\n", i, qlines[i])); + result = fill_port_1(info, &info[i].info1, qlines[i]); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + } + } + TALLOC_FREE(qlines); + +out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + TALLOC_FREE(qlines); + *count = 0; + *info_p = NULL; + return result; + } + + *info_p = info; + *count = numlines; + + return WERR_OK; +} + +/**************************************************************************** + enumports level 2. +****************************************************************************/ + +static WERROR enumports_level_2(TALLOC_CTX *mem_ctx, + union spoolss_PortInfo **info_p, + uint32_t *count) +{ + union spoolss_PortInfo *info = NULL; + int i=0; + WERROR result = WERR_OK; + char **qlines = NULL; + int numlines = 0; + + result = enumports_hook(talloc_tos(), &numlines, &qlines ); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + if (numlines) { + info = talloc_array(mem_ctx, union spoolss_PortInfo, numlines); + if (!info) { + DEBUG(10,("Returning WERR_NOT_ENOUGH_MEMORY\n")); + result = WERR_NOT_ENOUGH_MEMORY; + goto out; + } + + for (i=0; i<numlines; i++) { + DEBUG(6,("Filling port number [%d] with port [%s]\n", i, qlines[i])); + result = fill_port_2(info, &info[i].info2, qlines[i]); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + } + } + TALLOC_FREE(qlines); + +out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + TALLOC_FREE(qlines); + *count = 0; + *info_p = NULL; + return result; + } + + *info_p = info; + *count = numlines; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_EnumPorts +****************************************************************/ + +WERROR _spoolss_EnumPorts(struct pipes_struct *p, + struct spoolss_EnumPorts *r) +{ + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(4,("_spoolss_EnumPorts\n")); + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + switch (r->in.level) { + case 1: + result = enumports_level_1(p->mem_ctx, r->out.info, + r->out.count); + break; + case 2: + result = enumports_level_2(p->mem_ctx, r->out.info, + r->out.count); + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumPorts, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR spoolss_addprinterex_level_2(struct pipes_struct *p, + const char *server, + struct spoolss_SetPrinterInfoCtr *info_ctr, + struct spoolss_DeviceMode *devmode, + struct security_descriptor *secdesc, + struct spoolss_UserLevelCtr *user_ctr, + struct policy_handle *handle) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_SetPrinterInfo2 *info2 = info_ctr->info.info2; + uint32_t info2_mask = SPOOLSS_PRINTER_INFO_ALL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int snum; + WERROR err = WERR_OK; + + /* samba does not have a concept of local, non-shared printers yet, so + * make sure we always setup sharename - gd */ + if ((info2->sharename == NULL || info2->sharename[0] == '\0') && + (info2->printername != NULL && info2->printername[0] != '\0')) { + DEBUG(5, ("spoolss_addprinterex_level_2: " + "no sharename has been set, setting printername %s as sharename\n", + info2->printername)); + info2->sharename = info2->printername; + } + + /* check to see if the printer already exists */ + if ((snum = print_queue_snum(info2->sharename)) != -1) { + DEBUG(5, ("spoolss_addprinterex_level_2: Attempted to add a printer named [%s] when one already existed!\n", + info2->sharename)); + return WERR_PRINTER_ALREADY_EXISTS; + } + + if (!lp_force_printername(GLOBAL_SECTION_SNUM)) { + if ((snum = print_queue_snum(info2->printername)) != -1) { + DEBUG(5, ("spoolss_addprinterex_level_2: Attempted to add a printer named [%s] when one already existed!\n", + info2->printername)); + return WERR_PRINTER_ALREADY_EXISTS; + } + } + + /* validate printer info struct */ + if (!info2->printername || strlen(info2->printername) == 0) { + return WERR_INVALID_PRINTER_NAME; + } + if (!info2->portname || strlen(info2->portname) == 0) { + return WERR_UNKNOWN_PORT; + } + if (!info2->drivername || strlen(info2->drivername) == 0) { + return WERR_UNKNOWN_PRINTER_DRIVER; + } + if (!info2->printprocessor || strlen(info2->printprocessor) == 0) { + return WERR_UNKNOWN_PRINTPROCESSOR; + } + + /* FIXME!!! smbd should check to see if the driver is installed before + trying to add a printer like this --jerry */ + + if (*lp_addprinter_command(talloc_tos(), lp_sub) ) { + char *raddr; + + raddr = tsocket_address_inet_addr_string(remote_address, + p->mem_ctx); + if (raddr == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if ( !add_printer_hook(p->mem_ctx, session_info->security_token, + info2, raddr, + p->msg_ctx) ) { + return WERR_ACCESS_DENIED; + } + } else { + DEBUG(0,("spoolss_addprinterex_level_2: add printer for printer %s called and no " + "smb.conf parameter \"addprinter command\" is defined. This " + "parameter must exist for this call to succeed\n", + info2->sharename )); + } + + if ((snum = print_queue_snum(info2->sharename)) == -1) { + return WERR_ACCESS_DENIED; + } + + /* you must be a printer admin to add a new printer */ + if (!W_ERROR_IS_OK(print_access_check(session_info, + p->msg_ctx, + snum, + PRINTER_ACCESS_ADMINISTER))) { + return WERR_ACCESS_DENIED; + } + + /* + * Do sanity check on the requested changes for Samba. + */ + + if (!check_printer_ok(p->mem_ctx, info2, snum)) { + return WERR_INVALID_PARAMETER; + } + + if (devmode == NULL) { + info2_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE; + } + + err = update_dsspooler(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + 0, + info2, + NULL); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + err = winreg_update_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + info2->sharename, + info2_mask, + info2, + devmode, + secdesc); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + err = open_printer_hnd(p, handle, info2->printername, PRINTER_ACCESS_ADMINISTER); + if (!W_ERROR_IS_OK(err)) { + /* Handle open failed - remove addition. */ + ZERO_STRUCTP(handle); + return err; + } + + return WERR_OK; +} + +/**************************************************************** + _spoolss_AddPrinterEx +****************************************************************/ + +WERROR _spoolss_AddPrinterEx(struct pipes_struct *p, + struct spoolss_AddPrinterEx *r) +{ + switch (r->in.info_ctr->level) { + case 1: + /* we don't handle yet */ + /* but I know what to do ... */ + return WERR_INVALID_LEVEL; + case 2: + return spoolss_addprinterex_level_2(p, r->in.server, + r->in.info_ctr, + r->in.devmode_ctr->devmode, + r->in.secdesc_ctr->sd, + r->in.userlevel_ctr, + r->out.handle); + default: + return WERR_INVALID_LEVEL; + } +} + +/**************************************************************** + _spoolss_AddPrinter +****************************************************************/ + +WERROR _spoolss_AddPrinter(struct pipes_struct *p, + struct spoolss_AddPrinter *r) +{ + struct spoolss_AddPrinterEx a; + struct spoolss_UserLevelCtr userlevel_ctr; + + ZERO_STRUCT(userlevel_ctr); + + userlevel_ctr.level = 1; + + a.in.server = r->in.server; + a.in.info_ctr = r->in.info_ctr; + a.in.devmode_ctr = r->in.devmode_ctr; + a.in.secdesc_ctr = r->in.secdesc_ctr; + a.in.userlevel_ctr = &userlevel_ctr; + a.out.handle = r->out.handle; + + return _spoolss_AddPrinterEx(p, &a); +} + +/**************************************************************** + _spoolss_AddPrinterDriverEx +****************************************************************/ + +WERROR _spoolss_AddPrinterDriverEx(struct pipes_struct *p, + struct spoolss_AddPrinterDriverEx *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR err = WERR_OK; + const char *driver_name = NULL; + const char *driver_directory = NULL; + uint32_t version; + + /* + * we only support the semantics of AddPrinterDriver() + * i.e. only copy files that are newer than existing ones + */ + + if (r->in.flags == 0) { + return WERR_INVALID_PARAMETER; + } + + if (!(r->in.flags & APD_COPY_ALL_FILES) && + !(r->in.flags & APD_COPY_NEW_FILES)) { + return WERR_ACCESS_DENIED; + } + + /* FIXME */ + if (r->in.info_ctr->level != 3 && + r->in.info_ctr->level != 6 && + r->in.info_ctr->level != 8) { + DEBUG(0,("%s: level %d not yet implemented\n", __func__, + r->in.info_ctr->level)); + return WERR_INVALID_LEVEL; + } + + DEBUG(5,("Cleaning driver's information\n")); + err = clean_up_driver_struct(p->mem_ctx, + session_info, + r->in.info_ctr, + r->in.flags, + &driver_directory); + if (!W_ERROR_IS_OK(err)) { + DBG_ERR("clean_up_driver_struct failed - %s\n", + win_errstr(err)); + goto done; + } + + DEBUG(5,("Moving driver to final destination\n")); + err = move_driver_to_download_area(session_info, + r->in.info_ctr, + driver_directory); + if (!W_ERROR_IS_OK(err)) { + DBG_ERR("move_driver_to_download_area failed - %s\n", + win_errstr(err)); + goto done; + } + + err = winreg_add_driver_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + r->in.info_ctr, + &driver_name, + &version); + if (!W_ERROR_IS_OK(err)) { + DBG_ERR("winreg_add_driver_internal failed - %s\n", + win_errstr(err)); + goto done; + } + + /* + * I think this is where the DrvUpgradePrinter() hook would be + * be called in a driver's interface DLL on a Windows NT 4.0/2k + * server. Right now, we just need to send ourselves a message + * to update each printer bound to this driver. --jerry + */ + + if (!srv_spoolss_drv_upgrade_printer(driver_name, p->msg_ctx)) { + DEBUG(0,("%s: Failed to send message about upgrading driver [%s]!\n", + __func__, driver_name)); + } + +done: + return err; +} + +/**************************************************************** + _spoolss_AddPrinterDriver +****************************************************************/ + +WERROR _spoolss_AddPrinterDriver(struct pipes_struct *p, + struct spoolss_AddPrinterDriver *r) +{ + struct spoolss_AddPrinterDriverEx a; + + switch (r->in.info_ctr->level) { + case 2: + case 3: + case 4: + case 5: + break; + default: + return WERR_INVALID_LEVEL; + } + + a.in.servername = r->in.servername; + a.in.info_ctr = r->in.info_ctr; + a.in.flags = APD_COPY_NEW_FILES; + + return _spoolss_AddPrinterDriverEx(p, &a); +} + +/**************************************************************************** +****************************************************************************/ + +struct _spoolss_paths { + int type; + const char *share; + const char *dir; +}; + +enum { SPOOLSS_DRIVER_PATH, SPOOLSS_PRTPROCS_PATH }; + +static const struct _spoolss_paths spoolss_paths[]= { + { SPOOLSS_DRIVER_PATH, "print$", "DRIVERS" }, + { SPOOLSS_PRTPROCS_PATH, "prnproc$", "PRTPROCS" } +}; + +static WERROR compose_spoolss_server_path(TALLOC_CTX *mem_ctx, + const char *servername, + const char *environment, + int component, + char **path) +{ + const char *pservername = NULL; + const char *long_archi; + const char *short_archi; + + *path = NULL; + + /* environment may be empty */ + if (environment && strlen(environment)) { + long_archi = environment; + } else { + long_archi = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "spoolss", "architecture", + GLOBAL_SPOOLSS_ARCHITECTURE); + } + + /* servername may be empty */ + if (servername && strlen(servername)) { + pservername = canon_servername(servername); + + if (!is_myname_or_ipaddr(pservername)) { + return WERR_INVALID_PARAMETER; + } + } + + if (!(short_archi = get_short_archi(long_archi))) { + return WERR_INVALID_ENVIRONMENT; + } + + switch (component) { + case SPOOLSS_PRTPROCS_PATH: + case SPOOLSS_DRIVER_PATH: + if (pservername) { + *path = talloc_asprintf(mem_ctx, + "\\\\%s\\%s\\%s", + pservername, + spoolss_paths[component].share, + short_archi); + } else { + *path = talloc_asprintf(mem_ctx, "%s\\%s\\%s", + SPOOLSS_DEFAULT_SERVER_PATH, + spoolss_paths[component].dir, + short_archi); + } + break; + default: + return WERR_INVALID_PARAMETER; + } + + if (!*path) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR getprinterdriverdir_level_1(TALLOC_CTX *mem_ctx, + const char *servername, + const char *environment, + struct spoolss_DriverDirectoryInfo1 *r) +{ + WERROR werr; + char *path = NULL; + + werr = compose_spoolss_server_path(mem_ctx, + servername, + environment, + SPOOLSS_DRIVER_PATH, + &path); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + DEBUG(4,("printer driver directory: [%s]\n", path)); + + r->directory_name = path; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_GetPrinterDriverDirectory +****************************************************************/ + +WERROR _spoolss_GetPrinterDriverDirectory(struct pipes_struct *p, + struct spoolss_GetPrinterDriverDirectory *r) +{ + WERROR werror; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + TALLOC_FREE(r->out.info); + return WERR_INVALID_PARAMETER; + } + + DEBUG(5,("_spoolss_GetPrinterDriverDirectory: level %d\n", + r->in.level)); + + *r->out.needed = 0; + + /* r->in.level is ignored */ + + werror = getprinterdriverdir_level_1(p->mem_ctx, + r->in.server, + r->in.environment, + &r->out.info->info1); + if (!W_ERROR_IS_OK(werror)) { + TALLOC_FREE(r->out.info); + return werror; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_DriverDirectoryInfo, + r->out.info, r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************** + _spoolss_EnumPrinterData +****************************************************************/ + +WERROR _spoolss_EnumPrinterData(struct pipes_struct *p, + struct spoolss_EnumPrinterData *r) +{ + WERROR result; + struct spoolss_EnumPrinterDataEx r2; + uint32_t count; + struct spoolss_PrinterEnumValues *info, *val = NULL; + uint32_t needed; + + r2.in.handle = r->in.handle; + r2.in.key_name = "PrinterDriverData"; + r2.in.offered = 0; + r2.out.count = &count; + r2.out.info = &info; + r2.out.needed = &needed; + + result = _spoolss_EnumPrinterDataEx(p, &r2); + if (W_ERROR_EQUAL(result, WERR_MORE_DATA)) { + r2.in.offered = needed; + result = _spoolss_EnumPrinterDataEx(p, &r2); + } + if (!W_ERROR_IS_OK(result)) { + return result; + } + + /* + * The NT machine wants to know the biggest size of value and data + * + * cf: MSDN EnumPrinterData remark section + */ + + if (!r->in.value_offered && !r->in.data_offered) { + uint32_t biggest_valuesize = 0; + uint32_t biggest_datasize = 0; + int i, name_length; + + DEBUGADD(6,("Activating NT mega-hack to find sizes\n")); + + for (i=0; i<count; i++) { + + name_length = strlen(info[i].value_name); + if (strlen(info[i].value_name) > biggest_valuesize) { + biggest_valuesize = name_length; + } + + if (info[i].data_length > biggest_datasize) { + biggest_datasize = info[i].data_length; + } + + DEBUG(6,("current values: [%d], [%d]\n", biggest_valuesize, + biggest_datasize)); + } + + /* the value is an UNICODE string but real_value_size is the length + in bytes including the trailing 0 */ + + *r->out.value_needed = 2 * (1 + biggest_valuesize); + *r->out.data_needed = biggest_datasize; + + DEBUG(6,("final values: [%d], [%d]\n", + *r->out.value_needed, *r->out.data_needed)); + + return WERR_OK; + } + + if (r->in.enum_index < count) { + val = &info[r->in.enum_index]; + } + + if (val == NULL) { + /* out_value should default to "" or else NT4 has + problems unmarshalling the response */ + + if (r->in.value_offered) { + *r->out.value_needed = 1; + r->out.value_name = talloc_strdup(r, ""); + if (!r->out.value_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + r->out.value_name = NULL; + *r->out.value_needed = 0; + } + + /* the data is counted in bytes */ + + *r->out.data_needed = r->in.data_offered; + + result = WERR_NO_MORE_ITEMS; + } else { + /* + * the value is: + * - counted in bytes in the request + * - counted in UNICODE chars in the max reply + * - counted in bytes in the real size + * + * take a pause *before* coding not *during* coding + */ + + /* name */ + if (r->in.value_offered) { + r->out.value_name = talloc_strdup(r, val->value_name); + if (!r->out.value_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.value_needed = val->value_name_len; + } else { + r->out.value_name = NULL; + *r->out.value_needed = 0; + } + + /* type */ + + *r->out.type = val->type; + + /* data - counted in bytes */ + + /* + * See the section "Dynamically Typed Query Parameters" + * in MS-RPRN. + */ + + if (r->out.data && val->data && val->data->data && + val->data_length && r->in.data_offered) { + memcpy(r->out.data, val->data->data, + MIN(val->data_length,r->in.data_offered)); + } + + *r->out.data_needed = val->data_length; + + result = WERR_OK; + } + + return result; +} + +/**************************************************************** + _spoolss_SetPrinterData +****************************************************************/ + +WERROR _spoolss_SetPrinterData(struct pipes_struct *p, + struct spoolss_SetPrinterData *r) +{ + struct spoolss_SetPrinterDataEx r2; + + r2.in.handle = r->in.handle; + r2.in.key_name = "PrinterDriverData"; + r2.in.value_name = r->in.value_name; + r2.in.type = r->in.type; + r2.in.data = r->in.data; + r2.in.offered = r->in.offered; + + return _spoolss_SetPrinterDataEx(p, &r2); +} + +/**************************************************************** + _spoolss_ResetPrinter +****************************************************************/ + +WERROR _spoolss_ResetPrinter(struct pipes_struct *p, + struct spoolss_ResetPrinter *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum; + + DEBUG(5,("_spoolss_ResetPrinter\n")); + + /* + * All we do is to check to see if the handle and queue is valid. + * This call really doesn't mean anything to us because we only + * support RAW printing. --jerry + */ + + if (!Printer) { + DEBUG(2,("_spoolss_ResetPrinter: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) + return WERR_INVALID_HANDLE; + + + /* blindly return success */ + return WERR_OK; +} + +/**************************************************************** + _spoolss_DeletePrinterData +****************************************************************/ + +WERROR _spoolss_DeletePrinterData(struct pipes_struct *p, + struct spoolss_DeletePrinterData *r) +{ + struct spoolss_DeletePrinterDataEx r2; + + r2.in.handle = r->in.handle; + r2.in.key_name = "PrinterDriverData"; + r2.in.value_name = r->in.value_name; + + return _spoolss_DeletePrinterDataEx(p, &r2); +} + +/**************************************************************** + _spoolss_AddForm +****************************************************************/ + +WERROR _spoolss_AddForm(struct pipes_struct *p, + struct spoolss_AddForm *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_AddFormInfo1 *form; + int snum = -1; + WERROR status = WERR_OK; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + + DEBUG(5,("_spoolss_AddForm\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_AddForm: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege, + and not a printer admin, then fail */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege(session_info->security_token, + SEC_PRIV_PRINT_OPERATOR)) { + DEBUG(2,("_spoolss_Addform: denied by insufficient permissions.\n")); + return WERR_ACCESS_DENIED; + } + + if (r->in.info_ctr->level != 1) { + return WERR_INVALID_LEVEL; + } + + form = r->in.info_ctr->info.info1; + if (!form) { + return WERR_INVALID_PARAMETER; + } + + switch (form->flags) { + case SPOOLSS_FORM_USER: + case SPOOLSS_FORM_BUILTIN: + case SPOOLSS_FORM_PRINTER: + break; + default: + return WERR_INVALID_PARAMETER; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + status = winreg_printer_addform1(tmp_ctx, b, form); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + /* + * ChangeID must always be set if this is a printer + */ + if (Printer->printer_type == SPLHND_PRINTER) { + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + status = WERR_INVALID_HANDLE; + goto done; + } + + status = winreg_printer_update_changeid(tmp_ctx, b, + lp_const_servicename(snum)); + } + +done: + talloc_free(tmp_ctx); + return status; +} + +/**************************************************************** + _spoolss_DeleteForm +****************************************************************/ + +WERROR _spoolss_DeleteForm(struct pipes_struct *p, + struct spoolss_DeleteForm *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *form_name = r->in.form_name; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum = -1; + WERROR status = WERR_OK; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + + DEBUG(5,("_spoolss_DeleteForm\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_DeleteForm: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege(session_info->security_token, + SEC_PRIV_PRINT_OPERATOR)) { + DEBUG(2,("_spoolss_DeleteForm: denied by insufficient permissions.\n")); + return WERR_ACCESS_DENIED; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + status = winreg_printer_deleteform1(tmp_ctx, b, form_name); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + /* + * ChangeID must always be set if this is a printer + */ + if (Printer->printer_type == SPLHND_PRINTER) { + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + status = WERR_INVALID_HANDLE; + goto done; + } + + status = winreg_printer_update_changeid(tmp_ctx, b, + lp_const_servicename(snum)); + } + +done: + talloc_free(tmp_ctx); + return status; +} + +/**************************************************************** + _spoolss_SetForm +****************************************************************/ + +WERROR _spoolss_SetForm(struct pipes_struct *p, + struct spoolss_SetForm *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct spoolss_AddFormInfo1 *form; + const char *form_name = r->in.form_name; + int snum = -1; + WERROR status = WERR_OK; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx = NULL; + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + DEBUG(5,("_spoolss_SetForm\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_SetForm: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege, + and not a printer admin, then fail */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + !security_token_has_privilege(session_info->security_token, + SEC_PRIV_PRINT_OPERATOR)) { + DEBUG(2,("_spoolss_SetForm: denied by insufficient permissions.\n")); + return WERR_ACCESS_DENIED; + } + + if (r->in.info_ctr->level != 1) { + return WERR_INVALID_LEVEL; + } + + form = r->in.info_ctr->info.info1; + if (!form) { + return WERR_INVALID_PARAMETER; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + status = winreg_printer_setform1(tmp_ctx, b, + form_name, + form); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + /* + * ChangeID must always be set if this is a printer + */ + if (Printer->printer_type == SPLHND_PRINTER) { + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + status = WERR_INVALID_HANDLE; + goto done; + } + + status = winreg_printer_update_changeid(tmp_ctx, b, + lp_const_servicename(snum)); + } + +done: + talloc_free(tmp_ctx); + return status; +} + +/**************************************************************************** + fill_print_processor1 +****************************************************************************/ + +static WERROR fill_print_processor1(TALLOC_CTX *mem_ctx, + struct spoolss_PrintProcessorInfo1 *r, + const char *print_processor_name) +{ + r->print_processor_name = talloc_strdup(mem_ctx, print_processor_name); + W_ERROR_HAVE_NO_MEMORY(r->print_processor_name); + + return WERR_OK; +} + +/**************************************************************************** + enumprintprocessors level 1. +****************************************************************************/ + +static WERROR enumprintprocessors_level_1(TALLOC_CTX *mem_ctx, + union spoolss_PrintProcessorInfo **info_p, + uint32_t *count) +{ + union spoolss_PrintProcessorInfo *info; + WERROR result; + + info = talloc_array(mem_ctx, union spoolss_PrintProcessorInfo, 1); + W_ERROR_HAVE_NO_MEMORY(info); + + *count = 1; + + result = fill_print_processor1(info, &info[0].info1, "winprint"); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + *count = 0; + return result; + } + + *info_p = info; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_EnumPrintProcessors +****************************************************************/ + +WERROR _spoolss_EnumPrintProcessors(struct pipes_struct *p, + struct spoolss_EnumPrintProcessors *r) +{ + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(5,("_spoolss_EnumPrintProcessors\n")); + + /* + * Enumerate the print processors ... + * + * Just reply with "winprint", to keep NT happy + * and I can use my nice printer checker. + */ + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + if (!get_short_archi(r->in.environment)) { + return WERR_INVALID_ENVIRONMENT; + } + + switch (r->in.level) { + case 1: + result = enumprintprocessors_level_1(p->mem_ctx, r->out.info, + r->out.count); + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumPrintProcessors, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************************** + fill_printprocdatatype1 +****************************************************************************/ + +static WERROR fill_printprocdatatype1(TALLOC_CTX *mem_ctx, + struct spoolss_PrintProcDataTypesInfo1 *r, + const char *name_array) +{ + r->name_array = talloc_strdup(mem_ctx, name_array); + W_ERROR_HAVE_NO_MEMORY(r->name_array); + + return WERR_OK; +} + +/**************************************************************************** + enumprintprocdatatypes level 1. +****************************************************************************/ + +static WERROR enumprintprocdatatypes_level_1(TALLOC_CTX *mem_ctx, + union spoolss_PrintProcDataTypesInfo **info_p, + uint32_t *count) +{ + WERROR result; + union spoolss_PrintProcDataTypesInfo *info; + + info = talloc_array(mem_ctx, union spoolss_PrintProcDataTypesInfo, 1); + W_ERROR_HAVE_NO_MEMORY(info); + + *count = 1; + + result = fill_printprocdatatype1(info, &info[0].info1, "RAW"); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + *count = 0; + return result; + } + + *info_p = info; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_EnumPrintProcessorDataTypes +****************************************************************/ + +WERROR _spoolss_EnumPrintProcessorDataTypes(struct pipes_struct *p, + struct spoolss_EnumPrintProcessorDataTypes *r) +{ + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(5,("_spoolss_EnumPrintProcessorDataTypes\n")); + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + if (r->in.print_processor_name == NULL || + !strequal(r->in.print_processor_name, "winprint")) { + return WERR_UNKNOWN_PRINTPROCESSOR; + } + + switch (r->in.level) { + case 1: + result = enumprintprocdatatypes_level_1(p->mem_ctx, r->out.info, + r->out.count); + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumPrintProcessorDataTypes, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************************** + fill_monitor_1 +****************************************************************************/ + +static WERROR fill_monitor_1(TALLOC_CTX *mem_ctx, + struct spoolss_MonitorInfo1 *r, + const char *monitor_name) +{ + r->monitor_name = talloc_strdup(mem_ctx, monitor_name); + W_ERROR_HAVE_NO_MEMORY(r->monitor_name); + + return WERR_OK; +} + +/**************************************************************************** + fill_monitor_2 +****************************************************************************/ + +static WERROR fill_monitor_2(TALLOC_CTX *mem_ctx, + struct spoolss_MonitorInfo2 *r, + const char *monitor_name, + const char *environment, + const char *dll_name) +{ + r->monitor_name = talloc_strdup(mem_ctx, monitor_name); + W_ERROR_HAVE_NO_MEMORY(r->monitor_name); + r->environment = talloc_strdup(mem_ctx, environment); + W_ERROR_HAVE_NO_MEMORY(r->environment); + r->dll_name = talloc_strdup(mem_ctx, dll_name); + W_ERROR_HAVE_NO_MEMORY(r->dll_name); + + return WERR_OK; +} + +/**************************************************************************** + enumprintmonitors level 1. +****************************************************************************/ + +static WERROR enumprintmonitors_level_1(TALLOC_CTX *mem_ctx, + union spoolss_MonitorInfo **info_p, + uint32_t *count) +{ + union spoolss_MonitorInfo *info; + WERROR result = WERR_OK; + + info = talloc_array(mem_ctx, union spoolss_MonitorInfo, 2); + W_ERROR_HAVE_NO_MEMORY(info); + + *count = 2; + + result = fill_monitor_1(info, &info[0].info1, + SPL_LOCAL_PORT); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + result = fill_monitor_1(info, &info[1].info1, + SPL_TCPIP_PORT); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + +out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + *count = 0; + return result; + } + + *info_p = info; + + return WERR_OK; +} + +/**************************************************************************** + enumprintmonitors level 2. +****************************************************************************/ + +static WERROR enumprintmonitors_level_2(TALLOC_CTX *mem_ctx, + union spoolss_MonitorInfo **info_p, + uint32_t *count) +{ + union spoolss_MonitorInfo *info; + WERROR result = WERR_OK; + const char *architecture; + + info = talloc_array(mem_ctx, union spoolss_MonitorInfo, 2); + W_ERROR_HAVE_NO_MEMORY(info); + + *count = 2; + + architecture = lp_parm_const_string(GLOBAL_SECTION_SNUM, + "spoolss", + "architecture", + GLOBAL_SPOOLSS_ARCHITECTURE); + + result = fill_monitor_2(info, &info[0].info2, + SPL_LOCAL_PORT, + architecture, + "localmon.dll"); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + + result = fill_monitor_2(info, &info[1].info2, + SPL_TCPIP_PORT, + architecture, + "tcpmon.dll"); + if (!W_ERROR_IS_OK(result)) { + goto out; + } + +out: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(info); + *count = 0; + return result; + } + + *info_p = info; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_EnumMonitors +****************************************************************/ + +WERROR _spoolss_EnumMonitors(struct pipes_struct *p, + struct spoolss_EnumMonitors *r) +{ + WERROR result; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(5,("_spoolss_EnumMonitors\n")); + + /* + * Enumerate the print monitors ... + * + * Just reply with "Local Port", to keep NT happy + * and I can use my nice printer checker. + */ + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + switch (r->in.level) { + case 1: + result = enumprintmonitors_level_1(p->mem_ctx, r->out.info, + r->out.count); + break; + case 2: + result = enumprintmonitors_level_2(p->mem_ctx, r->out.info, + r->out.count); + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx, + spoolss_EnumMonitors, + *r->out.info, r->in.level, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR getjob_level_1(TALLOC_CTX *mem_ctx, + const print_queue_struct *queue, + int count, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + uint32_t jobid, + int sysjob, + struct spoolss_JobInfo1 *r) +{ + int i = 0; + bool found = false; + + for (i=0; i<count; i++) { + if (queue[i].sysjob == sysjob) { + found = true; + break; + } + } + + if (found == false) { + /* NT treats not found as bad param... yet another bad choice */ + return WERR_INVALID_PARAMETER; + } + + return fill_job_info1(mem_ctx, + r, + &queue[i], + jobid, + i, + snum, + pinfo2); +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR getjob_level_2(TALLOC_CTX *mem_ctx, + const print_queue_struct *queue, + int count, int snum, + struct spoolss_PrinterInfo2 *pinfo2, + uint32_t jobid, + int sysjob, + struct spoolss_JobInfo2 *r) +{ + int i = 0; + bool found = false; + struct spoolss_DeviceMode *devmode; + WERROR result; + + for (i=0; i<count; i++) { + if (queue[i].sysjob == sysjob) { + found = true; + break; + } + } + + if (found == false) { + /* NT treats not found as bad param... yet another bad + choice */ + return WERR_INVALID_PARAMETER; + } + + /* + * if the print job does not have a DEVMODE associated with it, + * just use the one for the printer. A NULL devicemode is not + * a failure condition + */ + + devmode = print_job_devmode(mem_ctx, lp_const_servicename(snum), jobid); + if (!devmode) { + result = spoolss_create_default_devmode(mem_ctx, + pinfo2->printername, + &devmode); + if (!W_ERROR_IS_OK(result)) { + DEBUG(3, ("Can't proceed w/o a devmode!\n")); + return result; + } + } + + return fill_job_info2(mem_ctx, + r, + &queue[i], + jobid, + i, + snum, + pinfo2, + devmode); +} + +/**************************************************************** + _spoolss_GetJob +****************************************************************/ + +WERROR _spoolss_GetJob(struct pipes_struct *p, + struct spoolss_GetJob *r) +{ + WERROR result = WERR_OK; + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + const char *svc_name; + int sysjob; + int snum; + int count; + struct tdb_print_db *pdb; + print_queue_struct *queue = NULL; + print_status_struct prt_status; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + result = WERR_INVALID_PARAMETER; + goto err_jinfo_free; + } + + DEBUG(5,("_spoolss_GetJob\n")); + + *r->out.needed = 0; + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + result = WERR_INVALID_HANDLE; + goto err_jinfo_free; + } + + svc_name = lp_const_servicename(snum); + if (svc_name == NULL) { + result = WERR_INVALID_PARAMETER; + goto err_jinfo_free; + } + + result = winreg_get_printer_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + svc_name, + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + goto err_jinfo_free; + } + + pdb = get_print_db_byname(svc_name); + if (pdb == NULL) { + DEBUG(3, ("failed to get print db for svc %s\n", svc_name)); + result = WERR_INVALID_PARAMETER; + goto err_pinfo_free; + } + + sysjob = jobid_to_sysjob_pdb(pdb, r->in.job_id); + release_print_db(pdb); + if (sysjob == -1) { + DEBUG(3, ("no sysjob for spoolss jobid %u\n", r->in.job_id)); + result = WERR_INVALID_PARAMETER; + goto err_pinfo_free; + } + + count = print_queue_status(p->msg_ctx, snum, &queue, &prt_status); + + DEBUGADD(4,("count:[%d], prt_status:[%d], [%s]\n", + count, prt_status.status, prt_status.message)); + + switch (r->in.level) { + case 1: + result = getjob_level_1(p->mem_ctx, + queue, count, snum, pinfo2, + r->in.job_id, sysjob, + &r->out.info->info1); + break; + case 2: + result = getjob_level_2(p->mem_ctx, + queue, count, snum, pinfo2, + r->in.job_id, sysjob, + &r->out.info->info2); + break; + default: + result = WERR_INVALID_LEVEL; + break; + } + + SAFE_FREE(queue); + TALLOC_FREE(pinfo2); + + if (!W_ERROR_IS_OK(result)) { + goto err_jinfo_free; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_JobInfo, r->out.info, + r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); + +err_pinfo_free: + TALLOC_FREE(pinfo2); +err_jinfo_free: + TALLOC_FREE(r->out.info); + return result; +} + +/**************************************************************** + _spoolss_GetPrinterDataEx +****************************************************************/ + +WERROR _spoolss_GetPrinterDataEx(struct pipes_struct *p, + struct spoolss_GetPrinterDataEx *r) +{ + + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + const char *printer; + int snum = 0; + WERROR result = WERR_OK; + DATA_BLOB blob; + enum winreg_Type val_type = REG_NONE; + uint8_t *val_data = NULL; + uint32_t val_size = 0; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + DEBUG(4,("_spoolss_GetPrinterDataEx\n")); + + DEBUG(10, ("_spoolss_GetPrinterDataEx: key => [%s], value => [%s]\n", + r->in.key_name, r->in.value_name)); + + /* in case of problem, return some default values */ + + *r->out.needed = 0; + *r->out.type = REG_NONE; + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (!Printer) { + DEBUG(2,("_spoolss_GetPrinterDataEx: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + result = WERR_INVALID_HANDLE; + goto done; + } + + /* Is the handle to a printer or to the server? */ + + if (Printer->printer_type == SPLHND_SERVER) { + + union spoolss_PrinterData data; + + result = getprinterdata_printer_server(tmp_ctx, + r->in.value_name, + r->out.type, + &data); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + result = push_spoolss_PrinterData(tmp_ctx, &blob, + *r->out.type, &data); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + *r->out.needed = blob.length; + + if (r->in.offered >= *r->out.needed) { + memcpy(r->out.data, blob.data, blob.length); + } + + result = WERR_OK; + goto done; + } + + /* check to see if the keyname is valid */ + if (!strlen(r->in.key_name)) { + result = WERR_INVALID_PARAMETER; + goto done; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + result = WERR_INVALID_HANDLE; + goto done; + } + printer = lp_const_servicename(snum); + + result = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + /* XP sends this and wants the ChangeID value from PRINTER_INFO_0 */ + if (strequal(r->in.key_name, SPOOL_PRINTERDATA_KEY) && + strequal(r->in.value_name, "ChangeId")) { + *r->out.type = REG_DWORD; + *r->out.needed = 4; + if (r->in.offered >= *r->out.needed) { + uint32_t changeid = 0; + + result = winreg_printer_get_changeid(tmp_ctx, b, + printer, + &changeid); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + SIVAL(r->out.data, 0, changeid); + result = WERR_OK; + } + goto done; + } + + result = winreg_get_printer_dataex(tmp_ctx, b, + printer, + r->in.key_name, + r->in.value_name, + &val_type, + &val_data, + &val_size); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + *r->out.needed = val_size; + *r->out.type = val_type; + + if (r->in.offered >= *r->out.needed) { + memcpy(r->out.data, val_data, val_size); + } + +done: + /* NOTE: do not replace type when returning WERR_MORE_DATA */ + + if (W_ERROR_IS_OK(result)) { + result = SPOOLSS_BUFFER_OK(WERR_OK, WERR_MORE_DATA); + } + + talloc_free(tmp_ctx); + return result; +} + +/**************************************************************** + _spoolss_SetPrinterDataEx +****************************************************************/ + +WERROR _spoolss_SetPrinterDataEx(struct pipes_struct *p, + struct spoolss_SetPrinterDataEx *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct spoolss_PrinterInfo2 *pinfo2 = NULL; + int snum = 0; + WERROR result = WERR_OK; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + char *oid_string; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + DEBUG(4,("_spoolss_SetPrinterDataEx\n")); + + /* From MSDN documentation of SetPrinterDataEx: pass request to + SetPrinterData if key is "PrinterDriverData" */ + + if (!Printer) { + DEBUG(2,("_spoolss_SetPrinterDataEx: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (Printer->printer_type == SPLHND_SERVER) { + DEBUG(10,("_spoolss_SetPrinterDataEx: " + "Not implemented for server handles yet\n")); + return WERR_INVALID_PARAMETER; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + /* + * Access check : NT returns "access denied" if you make a + * SetPrinterData call without the necessary privilege. + * we were originally returning OK if nothing changed + * which made Win2k issue **a lot** of SetPrinterData + * when connecting to a printer --jerry + */ + + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("_spoolss_SetPrinterDataEx: " + "change denied by handle access permissions\n")); + return WERR_ACCESS_DENIED; + } + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + result = winreg_get_printer(tmp_ctx, b, + lp_servicename(talloc_tos(), lp_sub, snum), + &pinfo2); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + /* check for OID in valuename */ + + oid_string = strchr(r->in.value_name, ','); + if (oid_string) { + *oid_string = '\0'; + oid_string++; + } + + /* save the registry data */ + + result = winreg_set_printer_dataex(tmp_ctx, b, + pinfo2->sharename, + r->in.key_name, + r->in.value_name, + r->in.type, + r->in.data, + r->in.offered); + + if (W_ERROR_IS_OK(result)) { + /* save the OID if one was specified */ + if (oid_string) { + char *str = talloc_asprintf(tmp_ctx, "%s\\%s", + r->in.key_name, SPOOL_OID_KEY); + if (!str) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + /* + * I'm not checking the status here on purpose. Don't know + * if this is right, but I'm returning the status from the + * previous set_printer_dataex() call. I have no idea if + * this is right. --jerry + */ + winreg_set_printer_dataex(tmp_ctx, b, + pinfo2->sharename, + str, + r->in.value_name, + REG_SZ, + (uint8_t *) oid_string, + strlen(oid_string) + 1); + } + + result = winreg_printer_update_changeid(tmp_ctx, b, + lp_const_servicename(snum)); + + } + +done: + talloc_free(tmp_ctx); + return result; +} + +/**************************************************************** + _spoolss_DeletePrinterDataEx +****************************************************************/ + +WERROR _spoolss_DeletePrinterDataEx(struct pipes_struct *p, + struct spoolss_DeletePrinterDataEx *r) +{ + const char *printer; + int snum=0; + WERROR status = WERR_OK; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + + DEBUG(5,("_spoolss_DeletePrinterDataEx\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_DeletePrinterDataEx: " + "Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("_spoolss_DeletePrinterDataEx: " + "printer properties change denied by handle\n")); + return WERR_ACCESS_DENIED; + } + + if (!r->in.value_name || !r->in.key_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + printer = lp_const_servicename(snum); + + status = winreg_delete_printer_dataex_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + printer, + r->in.key_name, + r->in.value_name); + if (W_ERROR_IS_OK(status)) { + status = winreg_printer_update_changeid_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + printer); + } + + return status; +} + +/**************************************************************** + _spoolss_EnumPrinterKey +****************************************************************/ + +WERROR _spoolss_EnumPrinterKey(struct pipes_struct *p, + struct spoolss_EnumPrinterKey *r) +{ + uint32_t num_keys; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum = 0; + WERROR result = WERR_FILE_NOT_FOUND; + const char **array = NULL; + DATA_BLOB blob; + + DEBUG(4,("_spoolss_EnumPrinterKey\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_EnumPrinterKey: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + result = winreg_enum_printer_key_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + r->in.key_name, + &num_keys, + &array); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + if (!push_reg_multi_sz(p->mem_ctx, &blob, array)) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + *r->out._ndr_size = r->in.offered / 2; + *r->out.needed = blob.length; + + if (r->in.offered < *r->out.needed) { + result = WERR_MORE_DATA; + } else { + result = WERR_OK; + r->out.key_buffer->string_array = array; + } + + done: + if (!W_ERROR_IS_OK(result)) { + TALLOC_FREE(array); + if (!W_ERROR_EQUAL(result, WERR_MORE_DATA)) { + *r->out.needed = 0; + } + } + + return result; +} + +/**************************************************************** + _spoolss_DeletePrinterKey +****************************************************************/ + +WERROR _spoolss_DeletePrinterKey(struct pipes_struct *p, + struct spoolss_DeletePrinterKey *r) +{ + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum=0; + WERROR status; + const char *printer; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + DEBUG(5,("_spoolss_DeletePrinterKey\n")); + + if (!Printer) { + DEBUG(2,("_spoolss_DeletePrinterKey: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* if keyname == NULL, return error */ + if ( !r->in.key_name ) + return WERR_INVALID_PARAMETER; + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) { + DEBUG(3, ("_spoolss_DeletePrinterKey: " + "printer properties change denied by handle\n")); + return WERR_ACCESS_DENIED; + } + + printer = lp_const_servicename(snum); + + tmp_ctx = talloc_new(p->mem_ctx); + if (!tmp_ctx) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = winreg_printer_binding_handle(tmp_ctx, + get_session_info_system(), + p->msg_ctx, + &b); + if (!W_ERROR_IS_OK(status)) { + goto done; + } + + /* delete the key and all subkeys */ + status = winreg_delete_printer_key(tmp_ctx, b, + printer, + r->in.key_name); + if (W_ERROR_IS_OK(status)) { + status = winreg_printer_update_changeid(tmp_ctx, b, + printer); + } + +done: + talloc_free(tmp_ctx); + return status; +} + +/**************************************************************** + _spoolss_EnumPrinterDataEx +****************************************************************/ + +WERROR _spoolss_EnumPrinterDataEx(struct pipes_struct *p, + struct spoolss_EnumPrinterDataEx *r) +{ + uint32_t count = 0; + struct spoolss_PrinterEnumValues *info = NULL; + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + int snum; + WERROR result; + + DEBUG(4,("_spoolss_EnumPrinterDataEx\n")); + + *r->out.count = 0; + *r->out.needed = 0; + *r->out.info = NULL; + + if (!Printer) { + DEBUG(2,("_spoolss_EnumPrinterDataEx: Invalid handle (%s:%u:%u1<).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* + * first check for a keyname of NULL or "". Win2k seems to send + * this a lot and we should send back WERR_INVALID_PARAMETER + * no need to spend time looking up the printer in this case. + * --jerry + */ + + if (!strlen(r->in.key_name)) { + result = WERR_INVALID_PARAMETER; + goto done; + } + + if (!get_printer_snum(p, r->in.handle, &snum, NULL)) { + return WERR_INVALID_HANDLE; + } + + /* now look for a match on the key name */ + result = winreg_enum_printer_dataex_internal(p->mem_ctx, + get_session_info_system(), + p->msg_ctx, + lp_const_servicename(snum), + r->in.key_name, + &count, + &info); + if (!W_ERROR_IS_OK(result)) { + goto done; + } + + *r->out.count = count; + *r->out.info = info; + + done: + if (!W_ERROR_IS_OK(result)) { + return result; + } + + *r->out.needed = SPOOLSS_BUFFER_ARRAY(p->mem_ctx, + spoolss_EnumPrinterDataEx, + *r->out.info, + *r->out.count); + *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL); + *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, *r->out.count); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_MORE_DATA); +} + +/**************************************************************************** +****************************************************************************/ + +static WERROR getprintprocessordirectory_level_1(TALLOC_CTX *mem_ctx, + const char *servername, + const char *environment, + struct spoolss_PrintProcessorDirectoryInfo1 *r) +{ + WERROR werr; + char *path = NULL; + + werr = compose_spoolss_server_path(mem_ctx, + servername, + environment, + SPOOLSS_PRTPROCS_PATH, + &path); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + DEBUG(4,("print processor directory: [%s]\n", path)); + + r->directory_name = path; + + return WERR_OK; +} + +/**************************************************************** + _spoolss_GetPrintProcessorDirectory +****************************************************************/ + +WERROR _spoolss_GetPrintProcessorDirectory(struct pipes_struct *p, + struct spoolss_GetPrintProcessorDirectory *r) +{ + WERROR result; + char *prnproc_share = NULL; + bool prnproc_share_exists = false; + int snum; + + /* that's an [in out] buffer */ + + if (!r->in.buffer && (r->in.offered != 0)) { + result = WERR_INVALID_PARAMETER; + goto err_info_free; + } + + DEBUG(5,("_spoolss_GetPrintProcessorDirectory: level %d\n", + r->in.level)); + + *r->out.needed = 0; + + /* r->in.level is ignored */ + + /* We always should reply with a local print processor directory so that + * users are not forced to have a [prnproc$] share on the Samba spoolss + * server, if users decide to do so, lets announce it though - Guenther */ + + snum = find_service(talloc_tos(), "prnproc$", &prnproc_share); + if (!prnproc_share) { + result = WERR_NOT_ENOUGH_MEMORY; + goto err_info_free; + } + if (snum != -1) { + prnproc_share_exists = true; + } + + result = getprintprocessordirectory_level_1(p->mem_ctx, + prnproc_share_exists ? r->in.server : NULL, + r->in.environment, + &r->out.info->info1); + if (!W_ERROR_IS_OK(result)) { + goto err_info_free; + } + + *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_PrintProcessorDirectoryInfo, + r->out.info, r->in.level); + r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL); + + return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER); + +err_info_free: + TALLOC_FREE(r->out.info); + return result; +} + +/******************************************************************* + ********************************************************************/ + +static bool push_monitorui_buf(TALLOC_CTX *mem_ctx, DATA_BLOB *buf, + const char *dllname) +{ + enum ndr_err_code ndr_err; + struct spoolss_MonitorUi ui; + + ui.dll_name = dllname; + + ndr_err = ndr_push_struct_blob(buf, mem_ctx, &ui, + (ndr_push_flags_fn_t)ndr_push_spoolss_MonitorUi); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) { + NDR_PRINT_DEBUG(spoolss_MonitorUi, &ui); + } + return NDR_ERR_CODE_IS_SUCCESS(ndr_err); +} + +/******************************************************************* + Streams the monitor UI DLL name in UNICODE +*******************************************************************/ + +static WERROR xcvtcp_monitorui(TALLOC_CTX *mem_ctx, + struct security_token *token, DATA_BLOB *in, + DATA_BLOB *out, uint32_t *needed) +{ + const char *dllname = "tcpmonui.dll"; + + *needed = (strlen(dllname)+1) * 2; + + if (out->length < *needed) { + return WERR_INSUFFICIENT_BUFFER; + } + + if (!push_monitorui_buf(mem_ctx, out, dllname)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/******************************************************************* + ********************************************************************/ + +static bool pull_port_data_1(TALLOC_CTX *mem_ctx, + struct spoolss_PortData1 *port1, + const DATA_BLOB *buf) +{ + enum ndr_err_code ndr_err; + ndr_err = ndr_pull_struct_blob(buf, mem_ctx, port1, + (ndr_pull_flags_fn_t)ndr_pull_spoolss_PortData1); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) { + NDR_PRINT_DEBUG(spoolss_PortData1, port1); + } + return NDR_ERR_CODE_IS_SUCCESS(ndr_err); +} + +/******************************************************************* + ********************************************************************/ + +static bool pull_port_data_2(TALLOC_CTX *mem_ctx, + struct spoolss_PortData2 *port2, + const DATA_BLOB *buf) +{ + enum ndr_err_code ndr_err; + ndr_err = ndr_pull_struct_blob(buf, mem_ctx, port2, + (ndr_pull_flags_fn_t)ndr_pull_spoolss_PortData2); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) { + NDR_PRINT_DEBUG(spoolss_PortData2, port2); + } + return NDR_ERR_CODE_IS_SUCCESS(ndr_err); +} + +/******************************************************************* + Create a new TCP/IP port +*******************************************************************/ + +static WERROR xcvtcp_addport(TALLOC_CTX *mem_ctx, + struct security_token *token, DATA_BLOB *in, + DATA_BLOB *out, uint32_t *needed) +{ + struct spoolss_PortData1 port1; + struct spoolss_PortData2 port2; + char *device_uri = NULL; + uint32_t version; + + const char *portname; + const char *hostaddress; + const char *queue; + uint32_t port_number; + uint32_t protocol; + + /* peek for spoolss_PortData version */ + + if (!in || (in->length < (128 + 4))) { + return WERR_GEN_FAILURE; + } + + version = IVAL(in->data, 128); + + switch (version) { + case 1: + ZERO_STRUCT(port1); + + if (!pull_port_data_1(mem_ctx, &port1, in)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + portname = port1.portname; + hostaddress = port1.hostaddress; + queue = port1.queue; + protocol = port1.protocol; + port_number = port1.port_number; + + break; + case 2: + ZERO_STRUCT(port2); + + if (!pull_port_data_2(mem_ctx, &port2, in)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + portname = port2.portname; + hostaddress = port2.hostaddress; + queue = port2.queue; + protocol = port2.protocol; + port_number = port2.port_number; + + break; + default: + DEBUG(1,("xcvtcp_addport: " + "unknown version of port_data: %d\n", version)); + return WERR_UNKNOWN_PORT; + } + + /* create the device URI and call the add_port_hook() */ + + switch (protocol) { + case PROTOCOL_RAWTCP_TYPE: + device_uri = talloc_asprintf(mem_ctx, + "socket://%s:%d/", hostaddress, + port_number); + break; + + case PROTOCOL_LPR_TYPE: + device_uri = talloc_asprintf(mem_ctx, + "lpr://%s/%s", hostaddress, queue ); + break; + + default: + return WERR_UNKNOWN_PORT; + } + + if (!device_uri) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return add_port_hook(mem_ctx, token, portname, device_uri); +} + +/******************************************************************* +*******************************************************************/ + +struct xcv_api_table xcvtcp_cmds[] = { + { "MonitorUI", xcvtcp_monitorui }, + { "AddPort", xcvtcp_addport}, + { NULL, NULL } +}; + +static WERROR process_xcvtcp_command(TALLOC_CTX *mem_ctx, + struct security_token *token, const char *command, + DATA_BLOB *inbuf, + DATA_BLOB *outbuf, + uint32_t *needed ) +{ + int i; + + DEBUG(10,("process_xcvtcp_command: Received command \"%s\"\n", command)); + + for ( i=0; xcvtcp_cmds[i].name; i++ ) { + if ( strcmp( command, xcvtcp_cmds[i].name ) == 0 ) + return xcvtcp_cmds[i].fn(mem_ctx, token, inbuf, outbuf, needed); + } + + return WERR_INVALID_FUNCTION; +} + +/******************************************************************* +*******************************************************************/ +#if 0 /* don't support management using the "Local Port" monitor */ + +static WERROR xcvlocal_monitorui(TALLOC_CTX *mem_ctx, + struct security_token *token, DATA_BLOB *in, + DATA_BLOB *out, uint32_t *needed) +{ + const char *dllname = "localui.dll"; + + *needed = (strlen(dllname)+1) * 2; + + if (out->length < *needed) { + return WERR_INSUFFICIENT_BUFFER; + } + + if (!push_monitorui_buf(mem_ctx, out, dllname)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/******************************************************************* +*******************************************************************/ + +struct xcv_api_table xcvlocal_cmds[] = { + { "MonitorUI", xcvlocal_monitorui }, + { NULL, NULL } +}; +#else +struct xcv_api_table xcvlocal_cmds[] = { + { NULL, NULL } +}; +#endif + + + +/******************************************************************* +*******************************************************************/ + +static WERROR process_xcvlocal_command(TALLOC_CTX *mem_ctx, + struct security_token *token, const char *command, + DATA_BLOB *inbuf, DATA_BLOB *outbuf, + uint32_t *needed) +{ + int i; + + DEBUG(10,("process_xcvlocal_command: Received command \"%s\"\n", command)); + + for ( i=0; xcvlocal_cmds[i].name; i++ ) { + if ( strcmp( command, xcvlocal_cmds[i].name ) == 0 ) + return xcvlocal_cmds[i].fn(mem_ctx, token, inbuf, outbuf, needed); + } + return WERR_INVALID_FUNCTION; +} + +/**************************************************************** + _spoolss_XcvData +****************************************************************/ + +WERROR _spoolss_XcvData(struct pipes_struct *p, + struct spoolss_XcvData *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle); + DATA_BLOB out_data = data_blob_null; + WERROR werror; + + if (!Printer) { + DEBUG(2,("_spoolss_XcvData: Invalid handle (%s:%u:%u).\n", + OUR_HANDLE(r->in.handle))); + return WERR_INVALID_HANDLE; + } + + /* Has to be a handle to the TCP/IP port monitor */ + + if ( !(Printer->printer_type & (SPLHND_PORTMON_LOCAL|SPLHND_PORTMON_TCP)) ) { + DEBUG(2,("_spoolss_XcvData: Call only valid for Port Monitors\n")); + return WERR_INVALID_HANDLE; + } + + /* requires administrative access to the server */ + + if ( !(Printer->access_granted & SERVER_ACCESS_ADMINISTER) ) { + DEBUG(2,("_spoolss_XcvData: denied by handle permissions.\n")); + return WERR_ACCESS_DENIED; + } + + /* Allocate the outgoing buffer */ + + if (r->in.out_data_size) { + out_data = data_blob_talloc_zero(p->mem_ctx, r->in.out_data_size); + if (out_data.data == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + switch ( Printer->printer_type ) { + case SPLHND_PORTMON_TCP: + werror = process_xcvtcp_command(p->mem_ctx, + session_info->security_token, + r->in.function_name, + &r->in.in_data, &out_data, + r->out.needed); + break; + case SPLHND_PORTMON_LOCAL: + werror = process_xcvlocal_command(p->mem_ctx, + session_info->security_token, + r->in.function_name, + &r->in.in_data, &out_data, + r->out.needed); + break; + default: + werror = WERR_INVALID_PRINT_MONITOR; + } + + if (!W_ERROR_IS_OK(werror)) { + return werror; + } + + *r->out.status_code = 0; + + if (r->out.out_data && out_data.data && r->in.out_data_size && out_data.length) { + memcpy(r->out.out_data, out_data.data, + MIN(r->in.out_data_size, out_data.length)); + } + + return WERR_OK; +} + +/**************************************************************** + _spoolss_AddPrintProcessor +****************************************************************/ + +WERROR _spoolss_AddPrintProcessor(struct pipes_struct *p, + struct spoolss_AddPrintProcessor *r) +{ + /* for now, just indicate success and ignore the add. We'll + automatically set the winprint processor for printer + entries later. Used to debug the LexMark Optra S 1855 PCL + driver --jerry */ + + return WERR_OK; +} + +/**************************************************************** + _spoolss_AddPort +****************************************************************/ + +WERROR _spoolss_AddPort(struct pipes_struct *p, + struct spoolss_AddPort *r) +{ + /* do what w2k3 does */ + + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_GetPrinterDriver +****************************************************************/ + +WERROR _spoolss_GetPrinterDriver(struct pipes_struct *p, + struct spoolss_GetPrinterDriver *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_ReadPrinter +****************************************************************/ + +WERROR _spoolss_ReadPrinter(struct pipes_struct *p, + struct spoolss_ReadPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_WaitForPrinterChange +****************************************************************/ + +WERROR _spoolss_WaitForPrinterChange(struct pipes_struct *p, + struct spoolss_WaitForPrinterChange *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_ConfigurePort +****************************************************************/ + +WERROR _spoolss_ConfigurePort(struct pipes_struct *p, + struct spoolss_ConfigurePort *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePort +****************************************************************/ + +WERROR _spoolss_DeletePort(struct pipes_struct *p, + struct spoolss_DeletePort *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_CreatePrinterIC +****************************************************************/ + +WERROR _spoolss_CreatePrinterIC(struct pipes_struct *p, + struct spoolss_CreatePrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_PlayGDIScriptOnPrinterIC +****************************************************************/ + +WERROR _spoolss_PlayGDIScriptOnPrinterIC(struct pipes_struct *p, + struct spoolss_PlayGDIScriptOnPrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePrinterIC +****************************************************************/ + +WERROR _spoolss_DeletePrinterIC(struct pipes_struct *p, + struct spoolss_DeletePrinterIC *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_AddPrinterConnection +****************************************************************/ + +WERROR _spoolss_AddPrinterConnection(struct pipes_struct *p, + struct spoolss_AddPrinterConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePrinterConnection +****************************************************************/ + +WERROR _spoolss_DeletePrinterConnection(struct pipes_struct *p, + struct spoolss_DeletePrinterConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_PrinterMessageBox +****************************************************************/ + +WERROR _spoolss_PrinterMessageBox(struct pipes_struct *p, + struct spoolss_PrinterMessageBox *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_AddMonitor +****************************************************************/ + +WERROR _spoolss_AddMonitor(struct pipes_struct *p, + struct spoolss_AddMonitor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeleteMonitor +****************************************************************/ + +WERROR _spoolss_DeleteMonitor(struct pipes_struct *p, + struct spoolss_DeleteMonitor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePrintProcessor +****************************************************************/ + +WERROR _spoolss_DeletePrintProcessor(struct pipes_struct *p, + struct spoolss_DeletePrintProcessor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_AddPrintProvidor +****************************************************************/ + +WERROR _spoolss_AddPrintProvidor(struct pipes_struct *p, + struct spoolss_AddPrintProvidor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePrintProvidor +****************************************************************/ + +WERROR _spoolss_DeletePrintProvidor(struct pipes_struct *p, + struct spoolss_DeletePrintProvidor *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_FindFirstPrinterChangeNotification +****************************************************************/ + +WERROR _spoolss_FindFirstPrinterChangeNotification(struct pipes_struct *p, + struct spoolss_FindFirstPrinterChangeNotification *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_FindNextPrinterChangeNotification +****************************************************************/ + +WERROR _spoolss_FindNextPrinterChangeNotification(struct pipes_struct *p, + struct spoolss_FindNextPrinterChangeNotification *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_RouterFindFirstPrinterChangeNotificationOld +****************************************************************/ + +WERROR _spoolss_RouterFindFirstPrinterChangeNotificationOld(struct pipes_struct *p, + struct spoolss_RouterFindFirstPrinterChangeNotificationOld *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_ReplyOpenPrinter +****************************************************************/ + +WERROR _spoolss_ReplyOpenPrinter(struct pipes_struct *p, + struct spoolss_ReplyOpenPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_RouterReplyPrinter +****************************************************************/ + +WERROR _spoolss_RouterReplyPrinter(struct pipes_struct *p, + struct spoolss_RouterReplyPrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_ReplyClosePrinter +****************************************************************/ + +WERROR _spoolss_ReplyClosePrinter(struct pipes_struct *p, + struct spoolss_ReplyClosePrinter *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_AddPortEx +****************************************************************/ + +WERROR _spoolss_AddPortEx(struct pipes_struct *p, + struct spoolss_AddPortEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_RouterFindFirstPrinterChangeNotification +****************************************************************/ + +WERROR _spoolss_RouterFindFirstPrinterChangeNotification(struct pipes_struct *p, + struct spoolss_RouterFindFirstPrinterChangeNotification *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_SpoolerInit +****************************************************************/ + +WERROR _spoolss_SpoolerInit(struct pipes_struct *p, + struct spoolss_SpoolerInit *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_ResetPrinterEx +****************************************************************/ + +WERROR _spoolss_ResetPrinterEx(struct pipes_struct *p, + struct spoolss_ResetPrinterEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_RouterReplyPrinterEx +****************************************************************/ + +WERROR _spoolss_RouterReplyPrinterEx(struct pipes_struct *p, + struct spoolss_RouterReplyPrinterEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_44 +****************************************************************/ + +WERROR _spoolss_44(struct pipes_struct *p, + struct spoolss_44 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_SetPort +****************************************************************/ + +WERROR _spoolss_SetPort(struct pipes_struct *p, + struct spoolss_SetPort *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_4a +****************************************************************/ + +WERROR _spoolss_4a(struct pipes_struct *p, + struct spoolss_4a *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_4b +****************************************************************/ + +WERROR _spoolss_4b(struct pipes_struct *p, + struct spoolss_4b *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_4c +****************************************************************/ + +WERROR _spoolss_4c(struct pipes_struct *p, + struct spoolss_4c *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_53 +****************************************************************/ + +WERROR _spoolss_53(struct pipes_struct *p, + struct spoolss_53 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_AddPerMachineConnection +****************************************************************/ + +WERROR _spoolss_AddPerMachineConnection(struct pipes_struct *p, + struct spoolss_AddPerMachineConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeletePerMachineConnection +****************************************************************/ + +WERROR _spoolss_DeletePerMachineConnection(struct pipes_struct *p, + struct spoolss_DeletePerMachineConnection *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_EnumPerMachineConnections +****************************************************************/ + +WERROR _spoolss_EnumPerMachineConnections(struct pipes_struct *p, + struct spoolss_EnumPerMachineConnections *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5a +****************************************************************/ + +WERROR _spoolss_5a(struct pipes_struct *p, + struct spoolss_5a *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5b +****************************************************************/ + +WERROR _spoolss_5b(struct pipes_struct *p, + struct spoolss_5b *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5c +****************************************************************/ + +WERROR _spoolss_5c(struct pipes_struct *p, + struct spoolss_5c *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5d +****************************************************************/ + +WERROR _spoolss_5d(struct pipes_struct *p, + struct spoolss_5d *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5e +****************************************************************/ + +WERROR _spoolss_5e(struct pipes_struct *p, + struct spoolss_5e *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_5f +****************************************************************/ + +WERROR _spoolss_5f(struct pipes_struct *p, + struct spoolss_5f *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_60 +****************************************************************/ + +WERROR _spoolss_60(struct pipes_struct *p, + struct spoolss_60 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_SendRecvBidiData +****************************************************************/ + +WERROR _spoolss_SendRecvBidiData(struct pipes_struct *p, + struct spoolss_SendRecvBidiData *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_62 +****************************************************************/ + +WERROR _spoolss_62(struct pipes_struct *p, + struct spoolss_62 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_63 +****************************************************************/ + +WERROR _spoolss_63(struct pipes_struct *p, + struct spoolss_63 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_64 +****************************************************************/ + +WERROR _spoolss_64(struct pipes_struct *p, + struct spoolss_64 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_65 +****************************************************************/ + +WERROR _spoolss_65(struct pipes_struct *p, + struct spoolss_65 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_GetCorePrinterDrivers +****************************************************************/ + +HRESULT _spoolss_GetCorePrinterDrivers(struct pipes_struct *p, + struct spoolss_GetCorePrinterDrivers *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_67 +****************************************************************/ + +WERROR _spoolss_67(struct pipes_struct *p, + struct spoolss_67 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_GetPrinterDriverPackagePath +****************************************************************/ + +HRESULT _spoolss_GetPrinterDriverPackagePath(struct pipes_struct *p, + struct spoolss_GetPrinterDriverPackagePath *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return HRES_ERROR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_69 +****************************************************************/ + +WERROR _spoolss_69(struct pipes_struct *p, + struct spoolss_69 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_6a +****************************************************************/ + +WERROR _spoolss_6a(struct pipes_struct *p, + struct spoolss_6a *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_6b +****************************************************************/ + +WERROR _spoolss_6b(struct pipes_struct *p, + struct spoolss_6b *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_6c +****************************************************************/ + +WERROR _spoolss_6c(struct pipes_struct *p, + struct spoolss_6c *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_6d +****************************************************************/ + +WERROR _spoolss_6d(struct pipes_struct *p, + struct spoolss_6d *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_GetJobNamedPropertyValue +****************************************************************/ + +WERROR _spoolss_GetJobNamedPropertyValue(struct pipes_struct *p, + struct spoolss_GetJobNamedPropertyValue *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_SetJobNamedProperty +****************************************************************/ + +WERROR _spoolss_SetJobNamedProperty(struct pipes_struct *p, + struct spoolss_SetJobNamedProperty *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_DeleteJobNamedProperty +****************************************************************/ + +WERROR _spoolss_DeleteJobNamedProperty(struct pipes_struct *p, + struct spoolss_DeleteJobNamedProperty *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_EnumJobNamedProperties +****************************************************************/ + +WERROR _spoolss_EnumJobNamedProperties(struct pipes_struct *p, + struct spoolss_EnumJobNamedProperties *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_72 +****************************************************************/ + +WERROR _spoolss_72(struct pipes_struct *p, + struct spoolss_72 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_73 +****************************************************************/ + +WERROR _spoolss_73(struct pipes_struct *p, + struct spoolss_73 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _spoolss_RpcLogJobInfoForBranchOffice +****************************************************************/ + +WERROR _spoolss_LogJobInfoForBranchOffice(struct pipes_struct *p, + struct spoolss_LogJobInfoForBranchOffice *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +static NTSTATUS spoolss__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +static NTSTATUS spoolss__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_SPOOLSS_INIT_SERVER \ + spoolss_init_server + +#define DCESRV_INTERFACE_SPOOLSS_SHUTDOWN_SERVER \ + spoolss_shutdown_server + +static NTSTATUS spoolss_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + bool ok; + + /* + * Migrate the printers first. + */ + ok = nt_printing_tdb_migrate(msg_ctx); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + return spoolss__op_init_server(dce_ctx, ep_server); +} + +static NTSTATUS spoolss_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + srv_spoolss_cleanup(); + + return spoolss__op_shutdown_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_spoolss_scompat.c" diff --git a/source3/rpc_server/spoolss/srv_spoolss_nt.h b/source3/rpc_server/spoolss/srv_spoolss_nt.h new file mode 100644 index 0000000..d6d141a --- /dev/null +++ b/source3/rpc_server/spoolss/srv_spoolss_nt.h @@ -0,0 +1,40 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-2000, + * Copyright (C) Luke Kenneth Casson Leighton 1996-2000, + * Copyright (C) Jean François Micouleau 1998-2000, + * Copyright (C) Jeremy Allison 2001-2002, + * Copyright (C) Gerald Carter 2000-2004, + * Copyright (C) Tim Potter 2001-2002. + * Copyright (C) Guenther Deschner 2009-2010. + * Copyright (C) Andreas Schneider 2010. + * + * 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 _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_ +#define _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_ + +/* The following definitions come from rpc_server/srv_spoolss_nt.c */ +void srv_spoolss_cleanup(void); + +void do_drv_upgrade_printer(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void update_monitored_printq_cache(struct messaging_context *msg_ctx); + +#endif /* _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_ */ diff --git a/source3/rpc_server/spoolss/srv_spoolss_util.c b/source3/rpc_server/spoolss/srv_spoolss_util.c new file mode 100644 index 0000000..be3c8fc --- /dev/null +++ b/source3/rpc_server/spoolss/srv_spoolss_util.c @@ -0,0 +1,917 @@ +/* + * Unix SMB/CIFS implementation. + * + * SPOOLSS RPC Pipe server / winreg client routines + * + * Copyright (c) 2010 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 "rpc_server/rpc_ncacn_np.h" +#include "../lib/tsocket/tsocket.h" +#include "../librpc/gen_ndr/ndr_spoolss.h" +#include "../librpc/gen_ndr/ndr_winreg.h" +#include "srv_spoolss_util.h" +#include "rpc_client/cli_winreg_spoolss.h" + +WERROR winreg_printer_binding_handle(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct dcerpc_binding_handle **winreg_binding_handle) +{ + struct tsocket_address *local; + NTSTATUS status; + int rc; + + rc = tsocket_address_inet_from_strings(mem_ctx, + "ip", + "127.0.0.1", + 0, + &local); + if (rc < 0) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = rpcint_binding_handle(mem_ctx, + &ndr_table_winreg, + local, + NULL, + session_info, + msg_ctx, + winreg_binding_handle); + talloc_free(local); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("winreg_printer_binding_handle: Could not connect to winreg pipe: %s\n", + nt_errstr(status))); + return ntstatus_to_werror(status); + } + + return WERR_OK; +} + +WERROR winreg_delete_printer_key_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_delete_printer_key(tmp_ctx, + b, + printer, + key); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_update_changeid_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_update_changeid(mem_ctx, + b, + printer); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_get_changeid_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + uint32_t *pchangeid) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_get_changeid(mem_ctx, + b, + printer, + pchangeid); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + struct spoolss_PrinterInfo2 **pinfo2) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_printer(mem_ctx, + b, + printer, + pinfo2); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_create_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_create_printer(mem_ctx, + b, + sharename); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_update_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + uint32_t info2_mask, + struct spoolss_SetPrinterInfo2 *info2, + struct spoolss_DeviceMode *devmode, + struct security_descriptor *secdesc) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_update_printer(mem_ctx, + b, + sharename, + info2_mask, + info2, + devmode, + secdesc); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_set_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value, + enum winreg_Type type, + uint8_t *data, + uint32_t data_size) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_set_printer_dataex(mem_ctx, + b, + printer, + key, + value, + type, + data, + data_size); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_enum_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + uint32_t *pnum_values, + struct spoolss_PrinterEnumValues **penum_values) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_enum_printer_dataex(mem_ctx, + b, + printer, + key, + pnum_values, + penum_values); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value, + enum winreg_Type *type, + uint8_t **data, + uint32_t *data_size) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_printer_dataex(mem_ctx, + b, + printer, + key, + value, + type, + data, + data_size); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_delete_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_delete_printer_dataex(mem_ctx, + b, + printer, + key, + value); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const char *driver_name, + uint32_t driver_version, + struct spoolss_DriverInfo8 **_info8) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_driver(mem_ctx, + b, + architecture, + driver_name, + driver_version, + _info8); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_driver_list_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + uint32_t version, + uint32_t *num_drivers, + const char ***drivers_p) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_driver_list(mem_ctx, + b, + architecture, + version, + num_drivers, + drivers_p); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_del_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_DriverInfo8 *info8, + uint32_t version) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_del_driver(mem_ctx, + b, + info8, + version); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_add_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_AddDriverInfoCtr *r, + const char **driver_name, + uint32_t *driver_version) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_add_driver(mem_ctx, + b, + r, + driver_name, + driver_version); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_core_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const struct GUID *core_driver_guid, + struct spoolss_CorePrinterDriver **core_printer_driver) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_core_driver(mem_ctx, + b, + architecture, + core_driver_guid, + core_printer_driver); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_add_core_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const struct spoolss_CorePrinterDriver *core_printer_driver) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_add_core_driver(mem_ctx, + b, + architecture, + core_printer_driver); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_add_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture, + const char *driver_store_path, + const char *cab_path) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_add_driver_package(mem_ctx, + b, + package_id, + architecture, + driver_store_path, + cab_path); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture, + const char **driver_store_path, + const char **cab_path) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_driver_package(mem_ctx, + b, + package_id, + architecture, + driver_store_path, + cab_path); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_del_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_del_driver_package(mem_ctx, + b, + package_id, + architecture); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_get_printer_secdesc_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + struct spoolss_security_descriptor **psecdesc) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_get_printer_secdesc(mem_ctx, + b, + sharename, + psecdesc); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_set_printer_secdesc_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + const struct spoolss_security_descriptor *secdesc) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_set_printer_secdesc(mem_ctx, + b, + sharename, + secdesc); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_enumforms1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t *pnum_info, + union spoolss_FormInfo **pinfo) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_enumforms1(mem_ctx, + b, + pnum_info, + pinfo); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_getform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name, + struct spoolss_FormInfo1 *r) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_getform1(mem_ctx, + b, + form_name, + r); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_addform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_AddFormInfo1 *form) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_addform1(mem_ctx, + b, + form); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_setform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name, + struct spoolss_AddFormInfo1 *form) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_setform1(mem_ctx, + b, + form_name, + form); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_printer_deleteform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_printer_deleteform1(mem_ctx, + b, + form_name); + + talloc_free(tmp_ctx); + return result; +} + +WERROR winreg_enum_printer_key_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + uint32_t *pnum_subkeys, + const char ***psubkeys) +{ + WERROR result; + struct dcerpc_binding_handle *b; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b); + if (!W_ERROR_IS_OK(result)) { + talloc_free(tmp_ctx); + return result; + } + + result = winreg_enum_printer_key(mem_ctx, + b, + printer, + key, + pnum_subkeys, + psubkeys); + + talloc_free(tmp_ctx); + return result; +} diff --git a/source3/rpc_server/spoolss/srv_spoolss_util.h b/source3/rpc_server/spoolss/srv_spoolss_util.h new file mode 100644 index 0000000..a9b3072 --- /dev/null +++ b/source3/rpc_server/spoolss/srv_spoolss_util.h @@ -0,0 +1,190 @@ +/* + * Unix SMB/CIFS implementation. + * + * SPOOLSS RPC Pipe server / winreg client routines + * + * Copyright (c) 2010 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/>. + */ + +#ifndef _SRV_SPOOLSS_UITL_H +#define _SRV_SPOOLSS_UITL_H + +struct auth_session_info; +struct dcerpc_binding_handle; + +WERROR winreg_printer_binding_handle(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct dcerpc_binding_handle **winreg_binding_handle); + +WERROR winreg_delete_printer_key_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key); +WERROR winreg_printer_update_changeid_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer); +WERROR winreg_printer_get_changeid_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + uint32_t *pchangeid); +WERROR winreg_get_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + struct spoolss_PrinterInfo2 **pinfo2); +WERROR winreg_create_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename); +WERROR winreg_update_printer_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + uint32_t info2_mask, + struct spoolss_SetPrinterInfo2 *info2, + struct spoolss_DeviceMode *devmode, + struct security_descriptor *secdesc); +WERROR winreg_set_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value, + enum winreg_Type type, + uint8_t *data, + uint32_t data_size); +WERROR winreg_enum_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + uint32_t *pnum_values, + struct spoolss_PrinterEnumValues **penum_values); +WERROR winreg_get_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value, + enum winreg_Type *type, + uint8_t **data, + uint32_t *data_size); +WERROR winreg_delete_printer_dataex_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + const char *value); +WERROR winreg_get_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const char *driver_name, + uint32_t driver_version, + struct spoolss_DriverInfo8 **_info8); +WERROR winreg_get_driver_list_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + uint32_t version, + uint32_t *num_drivers, + const char ***drivers_p); +WERROR winreg_del_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_DriverInfo8 *info8, + uint32_t version); +WERROR winreg_add_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_AddDriverInfoCtr *r, + const char **driver_name, + uint32_t *driver_version); +WERROR winreg_get_printer_secdesc_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + struct spoolss_security_descriptor **psecdesc); +WERROR winreg_set_printer_secdesc_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *sharename, + const struct spoolss_security_descriptor *secdesc); +WERROR winreg_printer_enumforms1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + uint32_t *pnum_info, + union spoolss_FormInfo **pinfo); +WERROR winreg_printer_getform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name, + struct spoolss_FormInfo1 *r); +WERROR winreg_printer_addform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + struct spoolss_AddFormInfo1 *form); +WERROR winreg_printer_setform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name, + struct spoolss_AddFormInfo1 *form); +WERROR winreg_printer_deleteform1_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *form_name); +WERROR winreg_enum_printer_key_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *printer, + const char *key, + uint32_t *pnum_subkeys, + const char ***psubkeys); +WERROR winreg_get_core_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const struct GUID *core_driver_guid, + struct spoolss_CorePrinterDriver **core_printer_driver); +WERROR winreg_add_core_driver_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *architecture, + const struct spoolss_CorePrinterDriver *core_printer_driver); +WERROR winreg_add_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture, + const char *driver_store_path, + const char *cab_path); +WERROR winreg_get_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture, + const char **driver_store_path, + const char **cab_path); +WERROR winreg_del_driver_package_internal(TALLOC_CTX *mem_ctx, + const struct auth_session_info *session_info, + struct messaging_context *msg_ctx, + const char *package_id, + const char *architecture); +#endif /* _SRV_SPOOLSS_UITL_H */ diff --git a/source3/rpc_server/srv_access_check.c b/source3/rpc_server/srv_access_check.c new file mode 100644 index 0000000..23d9252 --- /dev/null +++ b/source3/rpc_server/srv_access_check.c @@ -0,0 +1,168 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Paul Ashton 1997, + * Copyright (C) Marc Jacobsen 1999, + * Copyright (C) Jeremy Allison 2001-2008, + * Copyright (C) Jean François Micouleau 1998-2001, + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002, + * Copyright (C) Gerald (Jerry) Carter 2003-2004, + * Copyright (C) Simo Sorce 2003. + * Copyright (C) Volker Lendecke 2005. + * Copyright (C) Guenther Deschner 2008. + * + * 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 "system/passwd.h" /* uid_wrapper */ +#include "rpc_server/srv_access_check.h" +#include "../libcli/security/security.h" +#include "passdb/machine_sid.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +/******************************************************************* + Checks if access to an object should be granted, and returns that + level of access for further checks. + + If the user has either of needed_priv_1 or needed_priv_2 then they + get the rights in rights_mask in addition to any calculated rights. + + This handles the unusual case where we need to allow two different + privileges to obtain exactly the same rights, which occurs only in + SAMR. +********************************************************************/ + +NTSTATUS access_check_object( struct security_descriptor *psd, struct security_token *token, + enum sec_privilege needed_priv_1, enum sec_privilege needed_priv_2, + uint32_t rights_mask, + uint32_t des_access, uint32_t *acc_granted, + const char *debug ) +{ + NTSTATUS status = NT_STATUS_ACCESS_DENIED; + uint32_t saved_mask = 0; + bool priv_granted = false; + bool is_system = false; + bool is_root = false; + + /* Check if we are are the system token */ + if (security_token_is_system(token) && + security_token_system_privilege(token)) { + is_system = true; + } + + /* Check if we are root */ + if (root_mode()) { + is_root = true; + } + + /* check privileges; certain SAM access bits should be overridden + by privileges (mostly having to do with creating/modifying/deleting + users and groups) */ + + if ((needed_priv_1 != SEC_PRIV_INVALID && security_token_has_privilege(token, needed_priv_1)) || + (needed_priv_2 != SEC_PRIV_INVALID && security_token_has_privilege(token, needed_priv_2))) { + priv_granted = true; + saved_mask = (des_access & rights_mask); + des_access &= ~saved_mask; + + DEBUG(4,("access_check_object: user rights access mask [0x%x]\n", + rights_mask)); + } + + + /* check the security descriptor first */ + status = se_access_check(psd, token, des_access, acc_granted); + if (NT_STATUS_IS_OK(status)) { + goto done; + } + + if (is_system || is_root) { + DEBUG(4, + ("%s: ACCESS should be DENIED (requested: %#010x)\n" + "but overritten by %s\n", + debug, + des_access, + is_root ? "euid == initial uid" : "system token")); + + priv_granted = true; + *acc_granted = des_access; + + status = NT_STATUS_OK; + goto done; + } + + +done: + if (priv_granted) { + /* add in any bits saved during the privilege check (only + matters if status is ok) */ + + *acc_granted |= rights_mask; + } + + DEBUG(4,("%s: access %s (requested: 0x%08x, granted: 0x%08x)\n", + debug, NT_STATUS_IS_OK(status) ? "GRANTED" : "DENIED", + des_access, *acc_granted)); + + return status; +} + + +/******************************************************************* + Map any MAXIMUM_ALLOWED_ACCESS request to a valid access set. +********************************************************************/ + +void map_max_allowed_access(const struct security_token *nt_token, + const struct security_unix_token *unix_token, + uint32_t *pacc_requested) +{ + if (!((*pacc_requested) & MAXIMUM_ALLOWED_ACCESS)) { + return; + } + *pacc_requested &= ~MAXIMUM_ALLOWED_ACCESS; + + /* At least try for generic read|execute - Everyone gets that. */ + *pacc_requested |= GENERIC_READ_ACCESS|GENERIC_EXECUTE_ACCESS; + + /* root gets anything. */ + if (unix_token->uid == sec_initial_uid()) { + *pacc_requested |= GENERIC_ALL_ACCESS; + return; + } + + /* Full Access for 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */ + + if (security_token_has_sid(nt_token, &global_sid_Builtin_Administrators) || + security_token_has_sid(nt_token, &global_sid_Builtin_Account_Operators)) { + *pacc_requested |= GENERIC_ALL_ACCESS; + return; + } + + /* Full access for DOMAIN\Domain Admins. */ + if ( IS_DC ) { + struct dom_sid domadmin_sid; + sid_compose(&domadmin_sid, get_global_sam_sid(), + DOMAIN_RID_ADMINS); + if (security_token_has_sid(nt_token, &domadmin_sid)) { + *pacc_requested |= GENERIC_ALL_ACCESS; + return; + } + } + /* TODO ! Check privileges. */ +} diff --git a/source3/rpc_server/srv_access_check.h b/source3/rpc_server/srv_access_check.h new file mode 100644 index 0000000..4f30989 --- /dev/null +++ b/source3/rpc_server/srv_access_check.h @@ -0,0 +1,44 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Luke Kenneth Casson Leighton 1996-1997, + * Copyright (C) Paul Ashton 1997, + * Copyright (C) Marc Jacobsen 1999, + * Copyright (C) Jeremy Allison 2001-2008, + * Copyright (C) Jean François Micouleau 1998-2001, + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002, + * Copyright (C) Gerald (Jerry) Carter 2003-2004, + * Copyright (C) Simo Sorce 2003. + * Copyright (C) Volker Lendecke 2005. + * Copyright (C) Guenther Deschner 2008. + * + * 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 _RPC_SERVER_SRV_ACCESS_CHECK_H_ +#define _RPC_SERVER_SRV_ACCESS_CHECK_H_ + +/* The following definitions come from rpc_server/srv_access_check.c */ + +NTSTATUS access_check_object( struct security_descriptor *psd, struct security_token *token, + enum sec_privilege needed_priv_1, enum sec_privilege needed_priv_2, + uint32_t rights_mask, + uint32_t des_access, uint32_t *acc_granted, + const char *debug ); +void map_max_allowed_access(const struct security_token *nt_token, + const struct security_unix_token *unix_token, + uint32_t *pacc_requested); + +#endif /* _RPC_SERVER_SRV_ACCESS_CHECK_H_ */ diff --git a/source3/rpc_server/srv_pipe_hnd.c b/source3/rpc_server/srv_pipe_hnd.c new file mode 100644 index 0000000..c73a4c7 --- /dev/null +++ b/source3/rpc_server/srv_pipe_hnd.c @@ -0,0 +1,368 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1998, + * Largely re-written : 2005 + * Copyright (C) Jeremy Allison 1998 - 2005 + * + * 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 "fake_file.h" +#include "rpc_dce.h" +#include "ntdomain.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "rpc_server/srv_pipe_hnd.h" +#include "rpc_client/local_np.h" +#include "rpc_server/rpc_server.h" +#include "rpc_server/rpc_config.h" +#include "../lib/tsocket/tsocket.h" +#include "../lib/util/tevent_ntstatus.h" +#include "librpc/ndr/ndr_table.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +bool fsp_is_np(struct files_struct *fsp) +{ + enum FAKE_FILE_TYPE type; + + if ((fsp == NULL) || (fsp->fake_file_handle == NULL)) { + return false; + } + + type = fsp->fake_file_handle->type; + + return (type == FAKE_FILE_TYPE_NAMED_PIPE_PROXY); +} + +NTSTATUS np_open(TALLOC_CTX *mem_ctx, const char *name, + const struct tsocket_address *remote_client_address, + const struct tsocket_address *local_server_address, + struct auth_session_info *session_info, + struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx, + struct dcesrv_context *dce_ctx, + struct fake_file_handle **phandle) +{ + struct fake_file_handle *handle; + struct npa_state *npa = NULL; + int ret; + + handle = talloc(mem_ctx, struct fake_file_handle); + if (handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + npa = npa_state_init(handle); + if (npa == NULL) { + TALLOC_FREE(handle); + return NT_STATUS_NO_MEMORY; + } + *handle = (struct fake_file_handle) { + .type = FAKE_FILE_TYPE_NAMED_PIPE_PROXY, + .private_data = npa, + }; + + ret = local_np_connect( + name, + NCACN_NP, + NULL, + remote_client_address, + NULL, + local_server_address, + session_info, + false, + npa, + &npa->stream); + if (ret != 0) { + DBG_DEBUG("local_np_connect failed: %s\n", + strerror(ret)); + TALLOC_FREE(handle); + return map_nt_error_from_unix(ret); + } + + *phandle = handle; + + return NT_STATUS_OK; +} + +bool np_read_in_progress(struct fake_file_handle *handle) +{ + if (handle->type == FAKE_FILE_TYPE_NAMED_PIPE_PROXY) { + struct npa_state *p = + talloc_get_type_abort(handle->private_data, + struct npa_state); + size_t read_count; + + read_count = tevent_queue_length(p->read_queue); + if (read_count > 0) { + return true; + } + + return false; + } + + return false; +} + +struct np_write_state { + struct tevent_context *ev; + struct npa_state *p; + struct iovec iov; + ssize_t nwritten; +}; + +static void np_write_done(struct tevent_req *subreq); + +struct tevent_req *np_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct fake_file_handle *handle, + const uint8_t *data, size_t len) +{ + struct tevent_req *req; + struct np_write_state *state; + struct npa_state *p = NULL; + struct tevent_req *subreq = NULL; + + DBG_INFO("len: %zu\n", len); + dump_data(50, data, len); + + req = tevent_req_create(mem_ctx, &state, struct np_write_state); + if (req == NULL) { + return NULL; + } + + if (handle->type != FAKE_FILE_TYPE_NAMED_PIPE_PROXY) { + tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE); + return tevent_req_post(req, ev); + } + + if (len == 0) { + state->nwritten = 0; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + p = talloc_get_type_abort(handle->private_data, struct npa_state); + + state->ev = ev; + state->p = p; + state->iov.iov_base = discard_const_p(void, data); + state->iov.iov_len = len; + + subreq = tstream_writev_queue_send( + state, ev, p->stream, p->write_queue, &state->iov, 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, np_write_done, req); + return req; +} + +static void np_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct np_write_state *state = tevent_req_data( + req, struct np_write_state); + ssize_t received; + int err; + + received = tstream_writev_queue_recv(subreq, &err); + if (received < 0) { + tevent_req_nterror(req, map_nt_error_from_unix(err)); + return; + } + state->nwritten = received; + tevent_req_done(req); +} + +NTSTATUS np_write_recv(struct tevent_req *req, ssize_t *pnwritten) +{ + struct np_write_state *state = tevent_req_data( + req, struct np_write_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *pnwritten = state->nwritten; + return NT_STATUS_OK; +} + +struct np_ipc_readv_next_vector_state { + uint8_t *buf; + size_t len; + off_t ofs; + size_t remaining; +}; + +static void np_ipc_readv_next_vector_init(struct np_ipc_readv_next_vector_state *s, + uint8_t *buf, size_t len) +{ + ZERO_STRUCTP(s); + + s->buf = buf; + s->len = MIN(len, UINT16_MAX); +} + +static int np_ipc_readv_next_vector(struct tstream_context *stream, + void *private_data, + TALLOC_CTX *mem_ctx, + struct iovec **_vector, + size_t *count) +{ + struct np_ipc_readv_next_vector_state *state = + (struct np_ipc_readv_next_vector_state *)private_data; + struct iovec *vector; + ssize_t pending; + size_t wanted; + + if (state->ofs == state->len) { + *_vector = NULL; + *count = 0; + return 0; + } + + pending = tstream_pending_bytes(stream); + if (pending == -1) { + return -1; + } + + if (pending == 0 && state->ofs != 0) { + /* return a short read */ + *_vector = NULL; + *count = 0; + return 0; + } + + if (pending == 0) { + /* we want at least one byte and recheck again */ + wanted = 1; + } else { + size_t missing = state->len - state->ofs; + if (pending > missing) { + /* there's more available */ + state->remaining = pending - missing; + wanted = missing; + } else { + /* read what we can get and recheck in the next cycle */ + wanted = pending; + } + } + + vector = talloc_array(mem_ctx, struct iovec, 1); + if (!vector) { + return -1; + } + + vector[0].iov_base = state->buf + state->ofs; + vector[0].iov_len = wanted; + + state->ofs += wanted; + + *_vector = vector; + *count = 1; + return 0; +} + +struct np_read_state { + struct npa_state *p; + struct np_ipc_readv_next_vector_state next_vector; + + ssize_t nread; + bool is_data_outstanding; +}; + +static void np_read_done(struct tevent_req *subreq); + +struct tevent_req *np_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct fake_file_handle *handle, + uint8_t *data, size_t len) +{ + struct tevent_req *req; + struct np_read_state *state; + struct npa_state *p = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, struct np_read_state); + if (req == NULL) { + return NULL; + } + + if (handle->type != FAKE_FILE_TYPE_NAMED_PIPE_PROXY) { + tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE); + return tevent_req_post(req, ev); + } + + p = talloc_get_type_abort(handle->private_data, struct npa_state); + + np_ipc_readv_next_vector_init(&state->next_vector, data, len); + + subreq = tstream_readv_pdu_queue_send( + state, + ev, + p->stream, + p->read_queue, + np_ipc_readv_next_vector, + &state->next_vector); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, np_read_done, req); + return req; +} + +static void np_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct np_read_state *state = tevent_req_data( + req, struct np_read_state); + ssize_t ret; + int err; + + ret = tstream_readv_pdu_queue_recv(subreq, &err); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_nterror(req, map_nt_error_from_unix(err)); + return; + } + + state->nread = ret; + state->is_data_outstanding = (state->next_vector.remaining > 0); + + tevent_req_done(req); + return; +} + +NTSTATUS np_read_recv(struct tevent_req *req, ssize_t *nread, + bool *is_data_outstanding) +{ + struct np_read_state *state = tevent_req_data( + req, struct np_read_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + + DEBUG(10, ("Received %d bytes. There is %smore data outstanding\n", + (int)state->nread, state->is_data_outstanding?"":"no ")); + + *nread = state->nread; + *is_data_outstanding = state->is_data_outstanding; + return NT_STATUS_OK; +} diff --git a/source3/rpc_server/srv_pipe_hnd.h b/source3/rpc_server/srv_pipe_hnd.h new file mode 100644 index 0000000..ba35135 --- /dev/null +++ b/source3/rpc_server/srv_pipe_hnd.h @@ -0,0 +1,50 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1998, + * Largely re-written : 2005 + * Copyright (C) Jeremy Allison 1998 - 2005 + * + * 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 _RPC_SERVER_SRV_PIPE_HND_H_ +#define _RPC_SERVER_SRV_PIPE_HND_H_ + +struct tsocket_address; +struct pipes_struct; + +/* The following definitions come from rpc_server/srv_pipe_hnd.c */ + +bool fsp_is_np(struct files_struct *fsp); +NTSTATUS np_open(TALLOC_CTX *mem_ctx, const char *name, + const struct tsocket_address *remote_client_address, + const struct tsocket_address *local_server_address, + struct auth_session_info *session_info, + struct tevent_context *ev_ctx, + struct messaging_context *msg_ctx, + struct dcesrv_context *dce_ctx, + struct fake_file_handle **phandle); +bool np_read_in_progress(struct fake_file_handle *handle); +struct tevent_req *np_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct fake_file_handle *handle, + const uint8_t *data, size_t len); +NTSTATUS np_write_recv(struct tevent_req *req, ssize_t *pnwritten); +struct tevent_req *np_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct fake_file_handle *handle, + uint8_t *data, size_t len); +NTSTATUS np_read_recv(struct tevent_req *req, ssize_t *nread, + bool *is_data_outstanding); + +#endif /* _RPC_SERVER_SRV_PIPE_HND_H_ */ diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c new file mode 100644 index 0000000..29d224c --- /dev/null +++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c @@ -0,0 +1,3202 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Jeremy Allison 2001. + * Copyright (C) Nigel Williams 2001. + * Copyright (C) Gerald (Jerry) Carter 2006. + * Copyright (C) Guenther Deschner 2008. + * + * 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/>. + */ + +/* This is the implementation of the srvsvc pipe. */ + +#include "includes.h" +#include "system/passwd.h" +#include "lib/util/server_id.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_srvsvc.h" +#include "librpc/gen_ndr/ndr_srvsvc_scompat.h" +#include "../libcli/security/security.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "../librpc/gen_ndr/open_files.h" +#include "dbwrap/dbwrap.h" +#include "session.h" +#include "../lib/util/util_pw.h" +#include "locking/share_mode_lock.h" +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "auth.h" +#include "messages.h" +#include "serverid.h" +#include "lib/global_contexts.h" +#include "source3/lib/substitute.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/rpc/dcesrv_core.h" + +extern const struct generic_mapping file_generic_mapping; + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +#define MAX_SERVER_DISK_ENTRIES 15 + +/* Use for enumerating connections, pipes, & files */ + +struct file_enum_count { + TALLOC_CTX *ctx; + const char *username; + struct srvsvc_NetFileCtr3 *ctr3; + struct file_id *fids; +}; + +struct sess_file_info { + struct srvsvc_NetSessCtr1 *ctr; + struct sessionid *session_list; + uint32_t resume_handle; + uint32_t num_entries; +}; + +struct share_file_stat { + struct srvsvc_NetConnInfo1 *netconn_arr; + struct server_id *svrid_arr; + const char *in_sharepath; + uint32_t resp_entries; + uint32_t total_entries; +}; + +struct share_conn_stat { + TALLOC_CTX *ctx; + const char *sharename; + struct server_id *svrid_arr; + int count; +}; + +/******************************************************************* +********************************************************************/ + +static int enum_file_fn(struct file_id id, + const struct share_mode_data *d, + const struct share_mode_entry *e, + void *private_data) +{ + struct file_enum_count *fenum = + (struct file_enum_count *)private_data; + struct srvsvc_NetFileCtr3 *ctr3 = fenum->ctr3; + struct srvsvc_NetFileInfo3 *f; + struct file_id *fids = NULL; + char *fullpath = NULL; + uint32_t permissions; + const char *username; + + /* If the pid was not found delete the entry from connections.tdb */ + + if ( !process_exists(e->pid) ) { + return 0; + } + + username = uidtoname(e->uid); + + if ((fenum->username != NULL) + && !strequal(username, fenum->username)) { + return 0; + } + + f = talloc_realloc( + fenum->ctx, + ctr3->array, + struct srvsvc_NetFileInfo3, + ctr3->count+1); + if ( !f ) { + DBG_ERR("realloc failed for %"PRIu32" items\n", ctr3->count+1); + return 0; + } + ctr3->array = f; + + fids = talloc_realloc( + fenum->ctx, fenum->fids, struct file_id, ctr3->count+1); + if (fids == NULL) { + DBG_ERR("realloc failed for %"PRIu32" items\n", ctr3->count+1); + return 0; + } + fids[ctr3->count] = id; + fenum->fids = fids; + + if ( strcmp(d->base_name, "." ) == 0 ) { + fullpath = talloc_asprintf( + fenum->ctx, + "C:%s", + d->servicepath); + } else { + fullpath = talloc_asprintf( + fenum->ctx, + "C:%s/%s%s", + d->servicepath, + d->base_name, + (d->stream_name != NULL) ? d->stream_name : ""); + } + if (!fullpath) { + return 0; + } + string_replace( fullpath, '/', '\\' ); + + /* mask out create (what ever that is) */ + permissions = e->access_mask & (FILE_READ_DATA|FILE_WRITE_DATA); + + /* now fill in the srvsvc_NetFileInfo3 struct */ + + ctr3->array[ctr3->count] = (struct srvsvc_NetFileInfo3) { + .fid = (((uint32_t)(procid_to_pid(&e->pid))<<16) | + e->share_file_id), + .permissions = permissions, + .path = fullpath, + .user = username, + }; + + ctr3->count++; + + return 0; +} + +/******************************************************************* +********************************************************************/ + +static WERROR net_enum_files(TALLOC_CTX *ctx, + const char *username, + struct srvsvc_NetFileCtr3 **ctr3, + uint32_t resume) +{ + struct file_enum_count f_enum_cnt = { + .ctx = ctx, .username = username, .ctr3 = *ctr3, + }; + uint32_t i; + + share_entry_forall(enum_file_fn, (void *)&f_enum_cnt ); + + *ctr3 = f_enum_cnt.ctr3; + + /* need to count the number of locks on a file */ + + for (i=0; i<(*ctr3)->count; i++) { + struct files_struct fsp = { .file_id = f_enum_cnt.fids[i], }; + struct byte_range_lock *brl = NULL; + + brl = brl_get_locks(ctx, &fsp); + if (brl == NULL) { + continue; + } + + (*ctr3)->array[i].num_locks = brl_num_locks(brl); + + TALLOC_FREE(brl); + } + + return WERR_OK; +} + +/******************************************************************* + Utility function to get the 'type' of a share from an snum. + ********************************************************************/ +static enum srvsvc_ShareType get_share_type(int snum) +{ + /* work out the share type */ + enum srvsvc_ShareType type = STYPE_DISKTREE; + + if (lp_printable(snum)) { + type = lp_administrative_share(snum) + ? STYPE_PRINTQ_HIDDEN : STYPE_PRINTQ; + } + if (strequal(lp_fstype(snum), "IPC")) { + type = lp_administrative_share(snum) + ? STYPE_IPC_HIDDEN : STYPE_IPC; + } + return type; +} + +/******************************************************************* + Fill in a share info level 0 structure. + ********************************************************************/ + +static void init_srv_share_info_0(struct pipes_struct *p, + struct srvsvc_NetShareInfo0 *r, int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + r->name = lp_servicename(talloc_tos(), lp_sub, snum); +} + +/******************************************************************* + Fill in a share info level 1 structure. + ********************************************************************/ + +static void init_srv_share_info_1(struct pipes_struct *p, + struct srvsvc_NetShareInfo1 *r, + int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *net_name = lp_servicename(talloc_tos(), lp_sub, snum); + char *remark = lp_comment(p->mem_ctx, lp_sub, snum); + + if (remark) { + remark = talloc_sub_full( + p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), + get_current_username(), lp_path(talloc_tos(), lp_sub, snum), + session_info->unix_token->uid, get_current_username(), + "", remark); + } + + r->name = net_name; + r->type = get_share_type(snum); + r->comment = remark ? remark : ""; +} + +/******************************************************************* + Fill in a share info level 2 structure. + ********************************************************************/ + +static void init_srv_share_info_2(struct pipes_struct *p, + struct srvsvc_NetShareInfo2 *r, + int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *remark = NULL; + char *path = NULL; + int max_connections = lp_max_connections(snum); + uint32_t max_uses = UINT32_MAX; + char *net_name = lp_servicename(talloc_tos(), lp_sub, snum); + + if (max_connections > 0) { + max_uses = MIN(max_connections, UINT32_MAX); + } + + remark = lp_comment(p->mem_ctx, lp_sub, snum); + if (remark) { + remark = talloc_sub_full( + p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), + get_current_username(), lp_path(talloc_tos(), lp_sub, snum), + session_info->unix_token->uid, get_current_username(), + "", remark); + } + path = talloc_asprintf(p->mem_ctx, + "C:%s", lp_path(talloc_tos(), lp_sub, snum)); + + if (path) { + /* + * Change / to \\ so that win2k will see it as a valid path. + * This was added to enable use of browsing in win2k add + * share dialog. + */ + + string_replace(path, '/', '\\'); + } + + r->name = net_name; + r->type = get_share_type(snum); + r->comment = remark ? remark : ""; + r->permissions = 0; + r->max_users = max_uses; + r->current_users = 0; /* computed later */ + r->path = path ? path : ""; + r->password = ""; +} + +/******************************************************************* + Map any generic bits to file specific bits. +********************************************************************/ + +static void map_generic_share_sd_bits(struct security_descriptor *psd) +{ + uint32_t i; + struct security_acl *ps_dacl = NULL; + + if (!psd) + return; + + ps_dacl = psd->dacl; + if (!ps_dacl) + return; + + for (i = 0; i < ps_dacl->num_aces; i++) { + struct security_ace *psa = &ps_dacl->aces[i]; + uint32_t orig_mask = psa->access_mask; + + se_map_generic(&psa->access_mask, &file_generic_mapping); + psa->access_mask |= orig_mask; + } +} + +/******************************************************************* + Fill in a share info level 501 structure. +********************************************************************/ + +static void init_srv_share_info_501(struct pipes_struct *p, + struct srvsvc_NetShareInfo501 *r, int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum); + char *remark = lp_comment(p->mem_ctx, lp_sub, snum); + + if (remark) { + remark = talloc_sub_full( + p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), + get_current_username(), lp_path(talloc_tos(), lp_sub, snum), + session_info->unix_token->uid, get_current_username(), + "", remark); + } + + r->name = net_name; + r->type = get_share_type(snum); + r->comment = remark ? remark : ""; + + /* + * According to [MS-SRVS] 2.2.4.25, the flags field is the same as in + * level 1005. + */ + r->csc_policy = (lp_csc_policy(snum) << SHARE_1005_CSC_POLICY_SHIFT); +} + +/******************************************************************* + Fill in a share info level 502 structure. + ********************************************************************/ + +static void init_srv_share_info_502(struct pipes_struct *p, + struct srvsvc_NetShareInfo502 *r, int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum); + char *path = NULL; + struct security_descriptor *sd = NULL; + struct sec_desc_buf *sd_buf = NULL; + size_t sd_size = 0; + TALLOC_CTX *ctx = p->mem_ctx; + char *remark = lp_comment(ctx, lp_sub, snum); + + if (remark) { + remark = talloc_sub_full( + p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), + get_current_username(), lp_path(talloc_tos(), lp_sub, snum), + session_info->unix_token->uid, get_current_username(), + "", remark); + } + path = talloc_asprintf(ctx, "C:%s", lp_path(talloc_tos(), lp_sub, snum)); + if (path) { + /* + * Change / to \\ so that win2k will see it as a valid path. This was added to + * enable use of browsing in win2k add share dialog. + */ + string_replace(path, '/', '\\'); + } + + sd = get_share_security(ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size); + + sd_buf = make_sec_desc_buf(p->mem_ctx, sd_size, sd); + + r->name = net_name; + r->type = get_share_type(snum); + r->comment = remark ? remark : ""; + r->permissions = 0; + r->max_users = (uint32_t)-1; + r->current_users = 1; /* ??? */ + r->path = path ? path : ""; + r->password = ""; + r->sd_buf = *sd_buf; +} + +/*************************************************************************** + Fill in a share info level 1004 structure. + ***************************************************************************/ + +static void init_srv_share_info_1004(struct pipes_struct *p, + struct srvsvc_NetShareInfo1004 *r, + int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *remark = lp_comment(p->mem_ctx, lp_sub, snum); + + if (remark) { + remark = talloc_sub_full( + p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), + get_current_username(), lp_path(talloc_tos(), lp_sub, snum), + session_info->unix_token->uid, get_current_username(), + "", remark); + } + + r->comment = remark ? remark : ""; +} + +/*************************************************************************** + Fill in a share info level 1005 structure. + ***************************************************************************/ + +static void init_srv_share_info_1005(struct pipes_struct *p, + struct srvsvc_NetShareInfo1005 *r, + int snum) +{ + uint32_t dfs_flags = 0; + + if (lp_host_msdfs() && lp_msdfs_root(snum)) { + dfs_flags |= SHARE_1005_IN_DFS | SHARE_1005_DFS_ROOT; + } + + dfs_flags |= lp_csc_policy(snum) << SHARE_1005_CSC_POLICY_SHIFT; + + r->dfs_flags = dfs_flags; +} + +/*************************************************************************** + Fill in a share info level 1006 structure. + ***************************************************************************/ + +static void init_srv_share_info_1006(struct pipes_struct *p, + struct srvsvc_NetShareInfo1006 *r, + int snum) +{ + r->max_users = (uint32_t)-1; +} + +/*************************************************************************** + Fill in a share info level 1007 structure. + ***************************************************************************/ + +static void init_srv_share_info_1007(struct pipes_struct *p, + struct srvsvc_NetShareInfo1007 *r, + int snum) +{ + r->flags = 0; + r->alternate_directory_name = ""; +} + +/******************************************************************* + Fill in a share info level 1501 structure. + ********************************************************************/ + +static void init_srv_share_info_1501(struct pipes_struct *p, + struct sec_desc_buf **r, + int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct security_descriptor *sd; + struct sec_desc_buf *sd_buf = NULL; + size_t sd_size; + TALLOC_CTX *ctx = p->mem_ctx; + + sd = get_share_security(ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size); + if (sd) { + sd_buf = make_sec_desc_buf(p->mem_ctx, sd_size, sd); + } + + *r = sd_buf; +} + +/******************************************************************* + True if it ends in '$'. + ********************************************************************/ + +static bool is_hidden_share(int snum) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum); + + return (net_name[strlen(net_name) - 1] == '$') ? True : False; +} + +/******************************************************************* + Verify user is allowed to view share, access based enumeration +********************************************************************/ +static bool is_enumeration_allowed(struct pipes_struct *p, + int snum) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (!lp_access_based_share_enum(snum)) { + return true; + } + + if (!user_ok_token(session_info->unix_info->unix_name, + session_info->info->domain_name, + session_info->security_token, snum)) { + return false; + } + + return share_access_check(session_info->security_token, + lp_servicename(talloc_tos(), lp_sub, snum), + FILE_READ_DATA, NULL); +} + +/**************************************************************************** + Count an entry against the respective service. +****************************************************************************/ + +static int count_for_all_fn(struct smbXsrv_tcon_global0 *tcon, void *udp) +{ + union srvsvc_NetShareCtr *ctr = udp; + + /* Only called for level2 */ + struct srvsvc_NetShareCtr2 *ctr2 = ctr->ctr2; + + uint32_t share_entries = ctr2->count; + struct srvsvc_NetShareInfo2 *info2 = ctr2->array; + uint32_t i = 0; + + for (i = 0; i < share_entries; i++, info2++) { + if (strequal(tcon->share_name, info2->name)) { + info2->current_users++; + break; + } + } + + return 0; +} + +/**************************************************************************** + Count the entries belonging to all services in the connection db. +****************************************************************************/ + +static void count_connections_for_all_shares(union srvsvc_NetShareCtr *ctr) +{ + NTSTATUS status; + status = smbXsrv_tcon_global_traverse(count_for_all_fn, ctr); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("count_connections_for_all_shares: traverse of " + "smbXsrv_tcon_global.tdb failed - %s\n", + nt_errstr(status))); + } +} + +/******************************************************************* + Fill in a share info structure. + ********************************************************************/ + +static WERROR init_srv_share_info_ctr(struct pipes_struct *p, + struct srvsvc_NetShareInfoCtr *info_ctr, + uint32_t *resume_handle_p, + uint32_t *total_entries, + bool all_shares) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t num_entries = 0; + uint32_t alloc_entries = 0; + int num_services = 0; + int snum; + TALLOC_CTX *ctx = p->mem_ctx; + uint32_t i = 0; + uint32_t valid_share_count = 0; + bool *allowed = 0; + union srvsvc_NetShareCtr ctr; + uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0; + const char *unix_name = session_info->unix_info->unix_name; + int existing_home = -1; + int added_home = -1; + WERROR ret = WERR_OK; + + DEBUG(5,("init_srv_share_info_ctr\n")); + + /* + * We need to make sure to reload the services for the connecting user. + * It is possible that we have includes with substitutions. + * + * include = /etc/samba/%U.conf + * + * We also need all printers and usershares. + * + * We need to be root in order to have access to registry shares + * and root only smb.conf files. + */ + become_root(); + lp_kill_all_services(); + lp_load_with_shares(get_dyn_CONFIGFILE()); + delete_and_reload_printers(); + load_usershare_shares(NULL, connections_snum_used); + load_registry_shares(); + existing_home = lp_servicenumber(unix_name); + if (existing_home == -1) { + added_home = register_homes_share(unix_name); + } + unbecome_root(); + + num_services = lp_numservices(); + + allowed = talloc_zero_array(ctx, bool, num_services); + if (allowed == NULL) { + goto nomem; + } + + /* Count the number of entries. */ + for (snum = 0; snum < num_services; snum++) { + if (lp_browseable(snum) && lp_snum_ok(snum) && + lp_allow_local_address(snum, local_address) && + is_enumeration_allowed(p, snum) && + (all_shares || !is_hidden_share(snum))) { + DEBUG(10, ("counting service %s\n", + lp_servicename(talloc_tos(), lp_sub, snum) ? lp_servicename(talloc_tos(), lp_sub, snum) : "(null)")); + allowed[snum] = true; + num_entries++; + } else { + DEBUG(10, ("NOT counting service %s\n", + lp_servicename(talloc_tos(), lp_sub, snum) ? lp_servicename(talloc_tos(), lp_sub, snum) : "(null)")); + } + } + + if (!num_entries || (resume_handle >= num_entries)) { + goto done; + } + + /* Calculate alloc entries. */ + alloc_entries = num_entries - resume_handle; + switch (info_ctr->level) { + case 0: + ctr.ctr0 = talloc_zero(ctx, struct srvsvc_NetShareCtr0); + if (ctr.ctr0 == NULL) { + goto nomem; + } + + ctr.ctr0->count = alloc_entries; + ctr.ctr0->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo0, alloc_entries); + if (ctr.ctr0->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_0(p, &ctr.ctr0->array[i++], snum); + } + } + + break; + + case 1: + ctr.ctr1 = talloc_zero(ctx, struct srvsvc_NetShareCtr1); + if (ctr.ctr1 == NULL) { + goto nomem; + } + + ctr.ctr1->count = alloc_entries; + ctr.ctr1->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1, alloc_entries); + if (ctr.ctr1->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_1(p, &ctr.ctr1->array[i++], snum); + } + } + + break; + + case 2: + ctr.ctr2 = talloc_zero(ctx, struct srvsvc_NetShareCtr2); + if (ctr.ctr2 == NULL) { + goto nomem; + } + + ctr.ctr2->count = alloc_entries; + ctr.ctr2->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo2, alloc_entries); + if (ctr.ctr2->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_2(p, &ctr.ctr2->array[i++], snum); + } + } + + count_connections_for_all_shares(&ctr); + break; + + case 501: + ctr.ctr501 = talloc_zero(ctx, struct srvsvc_NetShareCtr501); + if (ctr.ctr501 == NULL) { + goto nomem; + } + + ctr.ctr501->count = alloc_entries; + ctr.ctr501->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo501, alloc_entries); + if (ctr.ctr501->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_501(p, &ctr.ctr501->array[i++], snum); + } + } + + break; + + case 502: + ctr.ctr502 = talloc_zero(ctx, struct srvsvc_NetShareCtr502); + if (ctr.ctr502 == NULL) { + goto nomem; + } + + ctr.ctr502->count = alloc_entries; + ctr.ctr502->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo502, alloc_entries); + if (ctr.ctr502->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_502(p, &ctr.ctr502->array[i++], snum); + } + } + + break; + + case 1004: + ctr.ctr1004 = talloc_zero(ctx, struct srvsvc_NetShareCtr1004); + if (ctr.ctr1004 == NULL) { + goto nomem; + } + + ctr.ctr1004->count = alloc_entries; + ctr.ctr1004->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1004, alloc_entries); + if (ctr.ctr1004->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_1004(p, &ctr.ctr1004->array[i++], snum); + } + } + + break; + + case 1005: + ctr.ctr1005 = talloc_zero(ctx, struct srvsvc_NetShareCtr1005); + if (ctr.ctr1005 == NULL) { + goto nomem; + } + + ctr.ctr1005->count = alloc_entries; + ctr.ctr1005->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1005, alloc_entries); + if (ctr.ctr1005->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_1005(p, &ctr.ctr1005->array[i++], snum); + } + } + + break; + + case 1006: + ctr.ctr1006 = talloc_zero(ctx, struct srvsvc_NetShareCtr1006); + if (ctr.ctr1006 == NULL) { + goto nomem; + } + + ctr.ctr1006->count = alloc_entries; + ctr.ctr1006->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1006, alloc_entries); + if (ctr.ctr1006->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_1006(p, &ctr.ctr1006->array[i++], snum); + } + } + + break; + + case 1007: + ctr.ctr1007 = talloc_zero(ctx, struct srvsvc_NetShareCtr1007); + if (ctr.ctr1007 == NULL) { + goto nomem; + } + + ctr.ctr1007->count = alloc_entries; + ctr.ctr1007->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1007, alloc_entries); + if (ctr.ctr1007->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + init_srv_share_info_1007(p, &ctr.ctr1007->array[i++], snum); + } + } + + break; + + case 1501: + ctr.ctr1501 = talloc_zero(ctx, struct srvsvc_NetShareCtr1501); + if (ctr.ctr1501 == NULL) { + goto nomem; + } + + ctr.ctr1501->count = alloc_entries; + ctr.ctr1501->array = talloc_zero_array(ctx, struct sec_desc_buf, alloc_entries); + if (ctr.ctr1501->array == NULL) { + goto nomem; + } + + for (snum = 0; snum < num_services; snum++) { + if (allowed[snum] && + (resume_handle <= (i + valid_share_count++)) ) { + struct sec_desc_buf *sd_buf = NULL; + init_srv_share_info_1501(p, &sd_buf, snum); + ctr.ctr1501->array[i++] = *sd_buf; + } + } + + break; + + default: + DEBUG(5,("init_srv_share_info_ctr: unsupported switch value %d\n", + info_ctr->level)); + ret = WERR_INVALID_LEVEL; + goto done; + } + + *total_entries = alloc_entries; + if (resume_handle_p) { + if (all_shares) { + *resume_handle_p = (num_entries == 0) ? *resume_handle_p : 0; + } else { + *resume_handle_p = num_entries; + } + } + + info_ctr->ctr = ctr; + ret = WERR_OK; + goto done; +nomem: + ret = WERR_NOT_ENOUGH_MEMORY; +done: + if (added_home != -1) { + lp_killservice(added_home); + } + return ret; +} + +/******************************************************************* + fill in a sess info level 0 structure. + ********************************************************************/ + +static WERROR init_srv_sess_info_0(struct pipes_struct *p, + struct srvsvc_NetSessCtr0 *ctr0, + uint32_t *resume_handle_p, + uint32_t *total_entries) +{ + struct sessionid *session_list; + uint32_t num_entries = 0; + uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0; + *total_entries = list_sessions(p->mem_ctx, &session_list); + + DEBUG(5,("init_srv_sess_info_0\n")); + + if (ctr0 == NULL) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + for (; resume_handle < *total_entries; resume_handle++) { + + ctr0->array = talloc_realloc(p->mem_ctx, + ctr0->array, + struct srvsvc_NetSessInfo0, + num_entries+1); + W_ERROR_HAVE_NO_MEMORY(ctr0->array); + + ctr0->array[num_entries].client = + session_list[resume_handle].remote_machine; + + num_entries++; + } + + ctr0->count = num_entries; + + if (resume_handle_p) { + if (*resume_handle_p >= *total_entries) { + *resume_handle_p = 0; + } else { + *resume_handle_p = resume_handle; + } + } + + return WERR_OK; +} + +/*********************************************************************** + * find out the session on which this file is open and bump up its count + **********************************************************************/ + +static int count_sess_files_fn(struct file_id fid, + const struct share_mode_data *d, + const struct share_mode_entry *e, + void *data) +{ + struct sess_file_info *info = data; + uint32_t rh = info->resume_handle; + uint32_t i; + + for (i=0; i < info->num_entries; i++) { + /* rh+info->num_entries is safe, as we've + ensured that: + *total_entries > resume_handle && + info->num_entries = *total_entries - resume_handle; + inside init_srv_sess_info_1() below. + */ + struct sessionid *sess = &info->session_list[rh + i]; + if ((e->uid == sess->uid) && + server_id_equal(&e->pid, &sess->pid)) { + + info->ctr->array[i].num_open++; + return 0; + } + } + return 0; +} + +/******************************************************************* + * count the num of open files on all sessions + *******************************************************************/ + +static void net_count_files_for_all_sess(struct srvsvc_NetSessCtr1 *ctr1, + struct sessionid *session_list, + uint32_t resume_handle, + uint32_t num_entries) +{ + struct sess_file_info s_file_info; + + s_file_info.ctr = ctr1; + s_file_info.session_list = session_list; + s_file_info.resume_handle = resume_handle; + s_file_info.num_entries = num_entries; + + share_entry_forall(count_sess_files_fn, &s_file_info); +} + +/******************************************************************* + fill in a sess info level 1 structure. + ********************************************************************/ + +static WERROR init_srv_sess_info_1(struct pipes_struct *p, + struct srvsvc_NetSessCtr1 *ctr1, + uint32_t *resume_handle_p, + uint32_t *total_entries) +{ + struct sessionid *session_list; + uint32_t num_entries = 0; + time_t now = time(NULL); + uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0; + + ZERO_STRUCTP(ctr1); + + if (ctr1 == NULL) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + *total_entries = list_sessions(p->mem_ctx, &session_list); + + if (resume_handle >= *total_entries) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + /* We know num_entries must be positive, due to + the check resume_handle >= *total_entries above. */ + + num_entries = *total_entries - resume_handle; + + ctr1->array = talloc_zero_array(p->mem_ctx, + struct srvsvc_NetSessInfo1, + num_entries); + + W_ERROR_HAVE_NO_MEMORY(ctr1->array); + + for (num_entries = 0; resume_handle < *total_entries; num_entries++, resume_handle++) { + uint32_t connect_time; + bool guest; + + connect_time = (uint32_t)(now - session_list[resume_handle].connect_start); + guest = strequal( session_list[resume_handle].username, lp_guest_account() ); + + ctr1->array[num_entries].client = session_list[resume_handle].remote_machine; + ctr1->array[num_entries].user = session_list[resume_handle].username; + ctr1->array[num_entries].num_open = 0;/* computed later */ + ctr1->array[num_entries].time = connect_time; + ctr1->array[num_entries].idle_time = 0; + ctr1->array[num_entries].user_flags = guest; + } + + ctr1->count = num_entries; + + /* count open files on all sessions in single tdb traversal */ + net_count_files_for_all_sess(ctr1, session_list, + resume_handle_p ? *resume_handle_p : 0, + num_entries); + + if (resume_handle_p) { + if (*resume_handle_p >= *total_entries) { + *resume_handle_p = 0; + } else { + *resume_handle_p = resume_handle; + } + } + + return WERR_OK; +} + +/******************************************************************* + find the share connection on which this open exists. + ********************************************************************/ + +static int share_file_fn(struct file_id fid, + const struct share_mode_data *d, + const struct share_mode_entry *e, + void *data) +{ + struct share_file_stat *sfs = data; + uint32_t i; + uint32_t offset = sfs->total_entries - sfs->resp_entries; + + if (strequal(d->servicepath, sfs->in_sharepath)) { + for (i=0; i < sfs->resp_entries; i++) { + if (server_id_equal( + &e->pid, &sfs->svrid_arr[offset + i])) { + sfs->netconn_arr[i].num_open ++; + return 0; + } + } + } + return 0; +} + +/******************************************************************* + count number of open files on given share connections. + ********************************************************************/ + +static void count_share_opens(struct srvsvc_NetConnInfo1 *arr, + struct server_id *svrid_arr, char *sharepath, + uint32_t resp_entries, uint32_t total_entries) +{ + struct share_file_stat sfs; + + sfs.netconn_arr = arr; + sfs.svrid_arr = svrid_arr; + sfs.in_sharepath = sharepath; + sfs.resp_entries = resp_entries; + sfs.total_entries = total_entries; + + share_entry_forall(share_file_fn, &sfs); +} + +/**************************************************************************** + process an entry from the connection db. +****************************************************************************/ + +static int share_conn_fn(struct smbXsrv_tcon_global0 *tcon, + void *data) +{ + struct share_conn_stat *scs = data; + + if (!process_exists(tcon->server_id)) { + return 0; + } + + if (strequal(tcon->share_name, scs->sharename)) { + scs->svrid_arr = talloc_realloc(scs->ctx, scs->svrid_arr, + struct server_id, + scs->count + 1); + if (!scs->svrid_arr) { + return 0; + } + + scs->svrid_arr[scs->count] = tcon->server_id; + scs->count++; + } + + return 0; +} + +/**************************************************************************** + Count the connections to a share. Build an array of serverid's owning these + connections. +****************************************************************************/ + +static uint32_t count_share_conns(TALLOC_CTX *ctx, const char *sharename, + struct server_id **arr) +{ + struct share_conn_stat scs; + NTSTATUS status; + + scs.ctx = ctx; + scs.sharename = sharename; + scs.svrid_arr = NULL; + scs.count = 0; + + status = smbXsrv_tcon_global_traverse(share_conn_fn, &scs); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("count_share_conns: traverse of " + "smbXsrv_tcon_global.tdb failed - %s\n", + nt_errstr(status))); + return 0; + } + + *arr = scs.svrid_arr; + return scs.count; +} + +/******************************************************************* + fill in a conn info level 0 structure. + ********************************************************************/ + +static WERROR init_srv_conn_info_0(struct srvsvc_NetConnCtr0 *ctr0, + uint32_t *resume_handle_p, + uint32_t *total_entries) +{ + uint32_t num_entries = 0; + uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0; + + DEBUG(5,("init_srv_conn_info_0\n")); + + if (ctr0 == NULL) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + *total_entries = 1; + + ZERO_STRUCTP(ctr0); + + for (; resume_handle < *total_entries; resume_handle++) { + + ctr0->array = talloc_realloc(talloc_tos(), + ctr0->array, + struct srvsvc_NetConnInfo0, + num_entries+1); + if (!ctr0->array) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ctr0->array[num_entries].conn_id = *total_entries; + + /* move on to creating next connection */ + num_entries++; + } + + ctr0->count = num_entries; + *total_entries = num_entries; + + if (resume_handle_p) { + if (*resume_handle_p >= *total_entries) { + *resume_handle_p = 0; + } else { + *resume_handle_p = resume_handle; + } + } + + return WERR_OK; +} + +/******************************************************************* + fill in a conn info level 1 structure. + ********************************************************************/ + +static WERROR init_srv_conn_info_1(const char *name, + struct srvsvc_NetConnCtr1 *ctr1, + uint32_t *resume_handle_p, + uint32_t *total_entries) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + uint32_t num_entries = 0; + int snum = 0; + uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0; + char *share_name = NULL; + struct server_id *svrid_arr = NULL; + + DEBUG(5,("init_srv_conn_info_1\n")); + + if (ctr1 == NULL) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + /* check if this is a server name or a share name */ + if (name && (strlen(name) > 2) && (name[0] == '\\') && + (name[1] == '\\')) { + + /* 'name' is a server name - this part is unimplemented */ + *total_entries = 1; + } else { + /* 'name' is a share name */ + snum = find_service(talloc_tos(), name, &share_name); + + if (!share_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (snum < 0) { + return WERR_INVALID_NAME; + } + + /* + * count the num of connections to this share. Also, + * build a list of serverid's that own these + * connections. The serverid list is used later to + * identify the share connection on which an open exists. + */ + + *total_entries = count_share_conns(talloc_tos(), + share_name, + &svrid_arr); + } + + if (resume_handle >= *total_entries) { + if (resume_handle_p) { + *resume_handle_p = 0; + } + return WERR_OK; + } + + /* + * We know num_entries must be positive, due to + * the check resume_handle >= *total_entries above. + */ + + num_entries = *total_entries - resume_handle; + + ZERO_STRUCTP(ctr1); + + ctr1->array = talloc_zero_array(talloc_tos(), + struct srvsvc_NetConnInfo1, + num_entries); + + W_ERROR_HAVE_NO_MEMORY(ctr1->array); + + for (num_entries = 0; resume_handle < *total_entries; + num_entries++, resume_handle++) { + + ctr1->array[num_entries].conn_id = *total_entries; + ctr1->array[num_entries].conn_type = 0x3; + + /* + * if these are connections to a share, we are going to + * compute the opens on them later. If it's for the server, + * it's unimplemented. + */ + + if (!share_name) { + ctr1->array[num_entries].num_open = 1; + } + + ctr1->array[num_entries].num_users = 1; + ctr1->array[num_entries].conn_time = 3; + ctr1->array[num_entries].user = "dummy_user"; + ctr1->array[num_entries].share = "IPC$"; + } + + /* now compute open files on the share connections */ + + if (share_name) { + + /* + * the locking tdb, which has the open files information, + * does not store share name or share (service) number, but + * just the share path. So, we can compute open files only + * on the share path. If more than one shares are defined + * on a share path, open files on all of them are included + * in the count. + * + * To have the correct behavior in case multiple shares + * are defined on the same path, changes to tdb records + * would be required. That would be lot more effort, so + * this seems a good stopgap fix. + */ + + count_share_opens(ctr1->array, svrid_arr, + lp_path(talloc_tos(), lp_sub, snum), + num_entries, *total_entries); + + } + + ctr1->count = num_entries; + *total_entries = num_entries; + + if (resume_handle_p) { + *resume_handle_p = resume_handle; + } + + return WERR_OK; +} + +/******************************************************************* + _srvsvc_NetFileEnum +*******************************************************************/ + +WERROR _srvsvc_NetFileEnum(struct pipes_struct *p, + struct srvsvc_NetFileEnum *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + TALLOC_CTX *ctx = NULL; + struct srvsvc_NetFileCtr3 *ctr3; + uint32_t resume_hnd = 0; + WERROR werr; + + switch (r->in.info_ctr->level) { + case 3: + break; + default: + return WERR_INVALID_LEVEL; + } + + if (!nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + DEBUG(1, ("Enumerating files only allowed for " + "administrators\n")); + return WERR_ACCESS_DENIED; + } + + ctx = talloc_tos(); + ctr3 = r->in.info_ctr->ctr.ctr3; + if (!ctr3) { + werr = WERR_INVALID_PARAMETER; + goto done; + } + + /* TODO -- Windows enumerates + (b) active pipes + (c) open directories and files */ + + werr = net_enum_files(ctx, r->in.user, &ctr3, resume_hnd); + if (!W_ERROR_IS_OK(werr)) { + goto done; + } + + *r->out.totalentries = ctr3->count; + r->out.info_ctr->ctr.ctr3->array = ctr3->array; + r->out.info_ctr->ctr.ctr3->count = ctr3->count; + + werr = WERR_OK; + + done: + return werr; +} + +/******************************************************************* + _srvsvc_NetSrvGetInfo +********************************************************************/ + +WERROR _srvsvc_NetSrvGetInfo(struct pipes_struct *p, + struct srvsvc_NetSrvGetInfo *r) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + WERROR status = WERR_OK; + + DEBUG(5,("_srvsvc_NetSrvGetInfo: %d\n", __LINE__)); + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to _srvsvc_NetSrvGetInfo\n")); + return WERR_ACCESS_DENIED; + } + + switch (r->in.level) { + + /* Technically level 102 should only be available to + Administrators but there isn't anything super-secret + here, as most of it is made up. */ + + case 102: { + struct srvsvc_NetSrvInfo102 *info102; + + info102 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo102); + if (!info102) { + return WERR_NOT_ENOUGH_MEMORY; + } + + info102->platform_id = PLATFORM_ID_NT; + info102->server_name = lp_netbios_name(); + info102->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION; + info102->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION; + info102->server_type = lp_default_server_announce(); + info102->comment = + string_truncate(lp_server_string(info102, lp_sub), + MAX_SERVER_STRING_LENGTH); + info102->users = 0xffffffff; + info102->disc = 0xf; + info102->hidden = 0; + info102->announce = 240; + info102->anndelta = 3000; + info102->licenses = 100000; + info102->userpath = "C:\\"; + + r->out.info->info102 = info102; + break; + } + case 101: { + struct srvsvc_NetSrvInfo101 *info101; + + info101 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo101); + if (!info101) { + return WERR_NOT_ENOUGH_MEMORY; + } + + info101->platform_id = PLATFORM_ID_NT; + info101->server_name = lp_netbios_name(); + info101->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION; + info101->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION; + info101->server_type = lp_default_server_announce(); + info101->comment = + string_truncate(lp_server_string(info101, lp_sub), + MAX_SERVER_STRING_LENGTH); + + r->out.info->info101 = info101; + break; + } + case 100: { + struct srvsvc_NetSrvInfo100 *info100; + + info100 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo100); + if (!info100) { + return WERR_NOT_ENOUGH_MEMORY; + } + + info100->platform_id = PLATFORM_ID_NT; + info100->server_name = lp_netbios_name(); + + r->out.info->info100 = info100; + + break; + } + default: + status = WERR_INVALID_LEVEL; + break; + } + + DEBUG(5,("_srvsvc_NetSrvGetInfo: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _srvsvc_NetSrvSetInfo +********************************************************************/ + +WERROR _srvsvc_NetSrvSetInfo(struct pipes_struct *p, + struct srvsvc_NetSrvSetInfo *r) +{ + WERROR status = WERR_OK; + + DEBUG(5,("_srvsvc_NetSrvSetInfo: %d\n", __LINE__)); + + /* Set up the net server set info structure. */ + + DEBUG(5,("_srvsvc_NetSrvSetInfo: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _srvsvc_NetConnEnum +********************************************************************/ + +WERROR _srvsvc_NetConnEnum(struct pipes_struct *p, + struct srvsvc_NetConnEnum *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR werr; + + DEBUG(5,("_srvsvc_NetConnEnum: %d\n", __LINE__)); + + if (!nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + DEBUG(1, ("Enumerating connections only allowed for " + "administrators\n")); + return WERR_ACCESS_DENIED; + } + + switch (r->in.info_ctr->level) { + case 0: + werr = init_srv_conn_info_0(r->in.info_ctr->ctr.ctr0, + r->in.resume_handle, + r->out.totalentries); + break; + case 1: + werr = init_srv_conn_info_1(r->in.path, + r->in.info_ctr->ctr.ctr1, + r->in.resume_handle, + r->out.totalentries); + break; + default: + return WERR_INVALID_LEVEL; + } + + DEBUG(5,("_srvsvc_NetConnEnum: %d\n", __LINE__)); + + return werr; +} + +/******************************************************************* + _srvsvc_NetSessEnum +********************************************************************/ + +WERROR _srvsvc_NetSessEnum(struct pipes_struct *p, + struct srvsvc_NetSessEnum *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR werr; + + DEBUG(5,("_srvsvc_NetSessEnum: %d\n", __LINE__)); + + if (!nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + DEBUG(1, ("Enumerating sessions only allowed for " + "administrators\n")); + return WERR_ACCESS_DENIED; + } + + switch (r->in.info_ctr->level) { + case 0: + werr = init_srv_sess_info_0(p, + r->in.info_ctr->ctr.ctr0, + r->in.resume_handle, + r->out.totalentries); + break; + case 1: + werr = init_srv_sess_info_1(p, + r->in.info_ctr->ctr.ctr1, + r->in.resume_handle, + r->out.totalentries); + break; + default: + return WERR_INVALID_LEVEL; + } + + DEBUG(5,("_srvsvc_NetSessEnum: %d\n", __LINE__)); + + return werr; +} + +/******************************************************************* + _srvsvc_NetSessDel +********************************************************************/ + +WERROR _srvsvc_NetSessDel(struct pipes_struct *p, + struct srvsvc_NetSessDel *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct sessionid *session_list; + int num_sessions, snum; + const char *username; + const char *machine; + bool not_root = False; + WERROR werr; + + DEBUG(5,("_srvsvc_NetSessDel: %d\n", __LINE__)); + + werr = WERR_ACCESS_DENIED; + + /* fail out now if you are not root or not a domain admin */ + + if ((session_info->unix_token->uid != sec_initial_uid()) && + ( ! nt_token_check_domain_rid(session_info->security_token, + DOMAIN_RID_ADMINS))) { + + goto done; + } + + username = r->in.user; + machine = r->in.client; + + /* strip leading backslashes if any */ + if (machine && machine[0] == '\\' && machine[1] == '\\') { + machine += 2; + } + + num_sessions = find_sessions(p->mem_ctx, username, machine, + &session_list); + + for (snum = 0; snum < num_sessions; snum++) { + + NTSTATUS ntstat; + + if (session_info->unix_token->uid != sec_initial_uid()) { + not_root = True; + become_root(); + } + + ntstat = messaging_send(p->msg_ctx, + session_list[snum].pid, + MSG_SHUTDOWN, &data_blob_null); + + if (NT_STATUS_IS_OK(ntstat)) + werr = WERR_OK; + + if (not_root) + unbecome_root(); + } + + DEBUG(5,("_srvsvc_NetSessDel: %d\n", __LINE__)); + +done: + + return werr; +} + +/******************************************************************* + _srvsvc_NetShareEnumAll +********************************************************************/ + +WERROR _srvsvc_NetShareEnumAll(struct pipes_struct *p, + struct srvsvc_NetShareEnumAll *r) +{ + WERROR werr; + + DEBUG(5,("_srvsvc_NetShareEnumAll: %d\n", __LINE__)); + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to _srvsvc_NetShareEnumAll\n")); + return WERR_ACCESS_DENIED; + } + + /* Create the list of shares for the response. */ + werr = init_srv_share_info_ctr(p, + r->in.info_ctr, + r->in.resume_handle, + r->out.totalentries, + true); + + DEBUG(5,("_srvsvc_NetShareEnumAll: %d\n", __LINE__)); + + return werr; +} + +/******************************************************************* + _srvsvc_NetShareEnum +********************************************************************/ + +WERROR _srvsvc_NetShareEnum(struct pipes_struct *p, + struct srvsvc_NetShareEnum *r) +{ + WERROR werr; + + DEBUG(5,("_srvsvc_NetShareEnum: %d\n", __LINE__)); + + if (!pipe_access_check(p)) { + DEBUG(3, ("access denied to _srvsvc_NetShareEnum\n")); + return WERR_ACCESS_DENIED; + } + + /* Create the list of shares for the response. */ + werr = init_srv_share_info_ctr(p, + r->in.info_ctr, + r->in.resume_handle, + r->out.totalentries, + false); + + DEBUG(5,("_srvsvc_NetShareEnum: %d\n", __LINE__)); + + return werr; +} + +/******************************************************************* + _srvsvc_NetShareGetInfo +********************************************************************/ + +WERROR _srvsvc_NetShareGetInfo(struct pipes_struct *p, + struct srvsvc_NetShareGetInfo *r) +{ + WERROR status = WERR_OK; + char *share_name = NULL; + int snum; + union srvsvc_NetShareInfo *info = r->out.info; + + DEBUG(5,("_srvsvc_NetShareGetInfo: %d\n", __LINE__)); + + if (!r->in.share_name) { + return WERR_INVALID_NAME; + } + + snum = find_service(talloc_tos(), r->in.share_name, &share_name); + if (!share_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (snum < 0) { + return WERR_INVALID_NAME; + } + + switch (r->in.level) { + case 0: + info->info0 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo0); + W_ERROR_HAVE_NO_MEMORY(info->info0); + init_srv_share_info_0(p, info->info0, snum); + break; + case 1: + info->info1 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1); + W_ERROR_HAVE_NO_MEMORY(info->info1); + init_srv_share_info_1(p, info->info1, snum); + break; + case 2: + info->info2 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo2); + W_ERROR_HAVE_NO_MEMORY(info->info2); + init_srv_share_info_2(p, info->info2, snum); + info->info2->current_users = + count_current_connections(info->info2->name, false); + break; + case 501: + info->info501 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo501); + W_ERROR_HAVE_NO_MEMORY(info->info501); + init_srv_share_info_501(p, info->info501, snum); + break; + case 502: + info->info502 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo502); + W_ERROR_HAVE_NO_MEMORY(info->info502); + init_srv_share_info_502(p, info->info502, snum); + break; + case 1004: + info->info1004 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1004); + W_ERROR_HAVE_NO_MEMORY(info->info1004); + init_srv_share_info_1004(p, info->info1004, snum); + break; + case 1005: + info->info1005 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1005); + W_ERROR_HAVE_NO_MEMORY(info->info1005); + init_srv_share_info_1005(p, info->info1005, snum); + break; + case 1006: + info->info1006 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1006); + W_ERROR_HAVE_NO_MEMORY(info->info1006); + init_srv_share_info_1006(p, info->info1006, snum); + break; + case 1007: + info->info1007 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1007); + W_ERROR_HAVE_NO_MEMORY(info->info1007); + init_srv_share_info_1007(p, info->info1007, snum); + break; + case 1501: + init_srv_share_info_1501(p, &info->info1501, snum); + break; + default: + DEBUG(5,("_srvsvc_NetShareGetInfo: unsupported switch value %d\n", + r->in.level)); + status = WERR_INVALID_LEVEL; + break; + } + + DEBUG(5,("_srvsvc_NetShareGetInfo: %d\n", __LINE__)); + + return status; +} + +/******************************************************************* + _srvsvc_NetShareSetInfo. Modify share details. +********************************************************************/ + +WERROR _srvsvc_NetShareSetInfo(struct pipes_struct *p, + struct srvsvc_NetShareSetInfo *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *command = NULL; + char *share_name = NULL; + char *comment = NULL; + const char *pathname = NULL; + int type; + int snum; + int ret; + char *path = NULL; + struct security_descriptor *psd = NULL; + bool is_disk_op = False; + const char *csc_policy = NULL; + bool csc_policy_changed = false; + const char *csc_policies[] = {"manual", "documents", "programs", + "disable"}; + uint32_t client_csc_policy; + int max_connections = 0; + TALLOC_CTX *ctx = p->mem_ctx; + union srvsvc_NetShareInfo *info = r->in.info; + + DEBUG(5,("_srvsvc_NetShareSetInfo: %d\n", __LINE__)); + + if (!r->in.share_name) { + return WERR_INVALID_NAME; + } + + if (r->out.parm_error) { + *r->out.parm_error = 0; + } + + if ( strequal(r->in.share_name,"IPC$") + || ( lp_enable_asu_support() && strequal(r->in.share_name,"ADMIN$") ) + || strequal(r->in.share_name,"global") ) + { + DEBUG(5,("_srvsvc_NetShareSetInfo: share %s cannot be " + "modified by a remote user.\n", + r->in.share_name )); + return WERR_ACCESS_DENIED; + } + + snum = find_service(talloc_tos(), r->in.share_name, &share_name); + if (!share_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Does this share exist ? */ + if (snum < 0) + return WERR_NERR_NETNAMENOTFOUND; + + /* No change to printer shares. */ + if (lp_printable(snum)) + return WERR_ACCESS_DENIED; + + is_disk_op = security_token_has_privilege( + session_info->security_token, SEC_PRIV_DISK_OPERATOR); + + /* fail out now if you are not root and not a disk op */ + + if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) { + DEBUG(2,("_srvsvc_NetShareSetInfo: uid %u doesn't have the " + "SeDiskOperatorPrivilege privilege needed to modify " + "share %s\n", + (unsigned int)session_info->unix_token->uid, + share_name )); + return WERR_ACCESS_DENIED; + } + + max_connections = lp_max_connections(snum); + csc_policy = csc_policies[lp_csc_policy(snum)]; + + switch (r->in.level) { + case 1: + pathname = lp_path(ctx, lp_sub, snum); + comment = talloc_strdup(ctx, info->info1->comment); + type = info->info1->type; + psd = NULL; + break; + case 2: + comment = talloc_strdup(ctx, info->info2->comment); + pathname = info->info2->path; + type = info->info2->type; + max_connections = (info->info2->max_users == (uint32_t)-1) ? + 0 : info->info2->max_users; + psd = NULL; + break; +#if 0 + /* not supported on set but here for completeness */ + case 501: + comment = talloc_strdup(ctx, info->info501->comment); + type = info->info501->type; + psd = NULL; + break; +#endif + case 502: + comment = talloc_strdup(ctx, info->info502->comment); + pathname = info->info502->path; + type = info->info502->type; + psd = info->info502->sd_buf.sd; + map_generic_share_sd_bits(psd); + break; + case 1004: + pathname = lp_path(ctx, lp_sub, snum); + comment = talloc_strdup(ctx, info->info1004->comment); + type = STYPE_DISKTREE; + break; + case 1005: + /* XP re-sets the csc policy even if it wasn't changed by the + user, so we must compare it to see if it's what is set in + smb.conf, so that we can continue other ops like setting + ACLs on a share */ + client_csc_policy = (info->info1005->dfs_flags & + SHARE_1005_CSC_POLICY_MASK) >> + SHARE_1005_CSC_POLICY_SHIFT; + + if (client_csc_policy == (uint32_t)lp_csc_policy(snum)) { + return WERR_OK; + } + + csc_policy = csc_policies[client_csc_policy]; + csc_policy_changed = true; + + pathname = lp_path(ctx, lp_sub, snum); + comment = lp_comment(ctx, lp_sub, snum); + type = STYPE_DISKTREE; + break; + case 1006: + case 1007: + return WERR_ACCESS_DENIED; + case 1501: + pathname = lp_path(ctx, lp_sub, snum); + comment = lp_comment(ctx, lp_sub, snum); + psd = info->info1501->sd; + map_generic_share_sd_bits(psd); + type = STYPE_DISKTREE; + break; + default: + DEBUG(5,("_srvsvc_NetShareSetInfo: unsupported switch value %d\n", + r->in.level)); + return WERR_INVALID_LEVEL; + } + + /* We can only modify disk shares. */ + if (type != STYPE_DISKTREE) { + DEBUG(5,("_srvsvc_NetShareSetInfo: share %s is not a " + "disk share\n", + share_name )); + return WERR_ACCESS_DENIED; + } + + if (comment == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Check if the pathname is valid. */ + if (!(path = valid_share_pathname(p->mem_ctx, pathname ))) { + DEBUG(5,("_srvsvc_NetShareSetInfo: invalid pathname %s\n", + pathname )); + return WERR_BAD_PATHNAME; + } + + /* Ensure share name, pathname and comment don't contain '"' characters. */ + string_replace(share_name, '"', ' '); + string_replace(path, '"', ' '); + string_replace(comment, '"', ' '); + + DEBUG(10,("_srvsvc_NetShareSetInfo: change share command = %s\n", + lp_change_share_command(talloc_tos(), lp_sub) ? lp_change_share_command(talloc_tos(), lp_sub) : "NULL" )); + + /* Only call modify function if something changed. */ + + if (strcmp(path, lp_path(talloc_tos(), lp_sub, snum)) + || strcmp(comment, lp_comment(talloc_tos(), lp_sub, snum)) + || (lp_max_connections(snum) != max_connections) + || csc_policy_changed) { + + if (!lp_change_share_command(talloc_tos(), lp_sub) || !*lp_change_share_command(talloc_tos(), lp_sub)) { + DEBUG(10,("_srvsvc_NetShareSetInfo: No change share command\n")); + return WERR_ACCESS_DENIED; + } + + command = talloc_asprintf(p->mem_ctx, + "%s \"%s\" \"%s\" \"%s\" \"%s\" %d \"%s\"", + lp_change_share_command(talloc_tos(), lp_sub), + get_dyn_CONFIGFILE(), + share_name, + path, + comment, + max_connections, + csc_policy); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + + DEBUG(10,("_srvsvc_NetShareSetInfo: Running [%s]\n", command )); + + /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/ + + if (is_disk_op) + become_root(); + + ret = smbrun(command, NULL, NULL); + if (ret == 0) { + reload_services(NULL, NULL, false); + + /* Tell everyone we updated smb.conf. */ + messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, + NULL, 0); + } + + if ( is_disk_op ) + unbecome_root(); + + /********* END SeDiskOperatorPrivilege BLOCK *********/ + + DEBUG(3,("_srvsvc_NetShareSetInfo: Running [%s] returned (%d)\n", + command, ret )); + + TALLOC_FREE(command); + + if ( ret != 0 ) + return WERR_ACCESS_DENIED; + } else { + DEBUG(10,("_srvsvc_NetShareSetInfo: No change to share name (%s)\n", + share_name )); + } + + /* Replace SD if changed. */ + if (psd) { + struct security_descriptor *old_sd; + size_t sd_size; + NTSTATUS status; + + old_sd = get_share_security(p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size); + + if (old_sd && !security_descriptor_equal(old_sd, psd)) { + status = set_share_security(share_name, psd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("_srvsvc_NetShareSetInfo: Failed to change security info in share %s.\n", + share_name )); + } + } + } + + DEBUG(5,("_srvsvc_NetShareSetInfo: %d\n", __LINE__)); + + return WERR_OK; +} + +/******************************************************************* + _srvsvc_NetShareAdd. + Call 'add_share_command "sharename" "pathname" + "comment" "max connections = " +********************************************************************/ + +WERROR _srvsvc_NetShareAdd(struct pipes_struct *p, + struct srvsvc_NetShareAdd *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + char *command = NULL; + char *share_name_in = NULL; + char *share_name = NULL; + char *comment = NULL; + char *pathname = NULL; + int type; + int snum; + int ret; + char *path; + struct security_descriptor *psd = NULL; + bool is_disk_op; + int max_connections = 0; + SMB_STRUCT_STAT st; + TALLOC_CTX *ctx = p->mem_ctx; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(5,("_srvsvc_NetShareAdd: %d\n", __LINE__)); + + if (r->out.parm_error) { + *r->out.parm_error = 0; + } + + is_disk_op = security_token_has_privilege( + session_info->security_token, SEC_PRIV_DISK_OPERATOR); + + if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) { + return WERR_ACCESS_DENIED; + } + + if (!lp_add_share_command(talloc_tos(), lp_sub) || !*lp_add_share_command(talloc_tos(), lp_sub)) { + DBG_WARNING("_srvsvc_NetShareAdd: No \"add share command\" parameter set in smb.conf.\n"); + return WERR_ACCESS_DENIED; + } + + switch (r->in.level) { + case 0: + /* No path. Not enough info in a level 0 to do anything. */ + return WERR_ACCESS_DENIED; + case 1: + /* Not enough info in a level 1 to do anything. */ + return WERR_ACCESS_DENIED; + case 2: + share_name_in = talloc_strdup(ctx, r->in.info->info2->name); + comment = talloc_strdup(ctx, r->in.info->info2->comment); + pathname = talloc_strdup(ctx, r->in.info->info2->path); + max_connections = (r->in.info->info2->max_users == (uint32_t)-1) ? + 0 : r->in.info->info2->max_users; + type = r->in.info->info2->type; + break; + case 501: + /* No path. Not enough info in a level 501 to do anything. */ + return WERR_ACCESS_DENIED; + case 502: + share_name_in = talloc_strdup(ctx, r->in.info->info502->name); + comment = talloc_strdup(ctx, r->in.info->info502->comment); + pathname = talloc_strdup(ctx, r->in.info->info502->path); + max_connections = (r->in.info->info502->max_users == (uint32_t)-1) ? + 0 : r->in.info->info502->max_users; + type = r->in.info->info502->type; + psd = r->in.info->info502->sd_buf.sd; + map_generic_share_sd_bits(psd); + break; + + /* none of the following contain share names. NetShareAdd does not have a separate parameter for the share name */ + + case 1004: + case 1005: + case 1006: + case 1007: + return WERR_ACCESS_DENIED; + case 1501: + /* DFS only level. */ + return WERR_ACCESS_DENIED; + default: + DEBUG(5,("_srvsvc_NetShareAdd: unsupported switch value %d\n", + r->in.level)); + return WERR_INVALID_LEVEL; + } + + /* check for invalid share names */ + + if (!share_name_in || !validate_net_name(share_name_in, + INVALID_SHARENAME_CHARS, + strlen(share_name_in))) { + DEBUG(5,("_srvsvc_NetShareAdd: Bad sharename \"%s\"\n", + share_name_in ? share_name_in : "")); + return WERR_INVALID_NAME; + } + + if (strequal(share_name_in,"IPC$") || strequal(share_name_in,"global") + || (lp_enable_asu_support() && + strequal(share_name_in,"ADMIN$"))) { + return WERR_ACCESS_DENIED; + } + + snum = find_service(ctx, share_name_in, &share_name); + if (!share_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Share already exists. */ + if (snum >= 0) { + return WERR_FILE_EXISTS; + } + + /* We can only add disk shares. */ + if (type != STYPE_DISKTREE) { + return WERR_ACCESS_DENIED; + } + + /* Check if the pathname is valid. */ + if (!(path = valid_share_pathname(p->mem_ctx, pathname))) { + return WERR_BAD_PATHNAME; + } + + ret = sys_lstat(path, &st, false); + if (ret == -1 && (errno != EACCES)) { + /* + * If path has any other than permission + * problem, return WERR_FILE_NOT_FOUND (as Windows + * does. + */ + return WERR_FILE_NOT_FOUND; + } + + /* Ensure share name, pathname and comment don't contain '"' characters. */ + string_replace(share_name_in, '"', ' '); + string_replace(share_name, '"', ' '); + string_replace(path, '"', ' '); + if (comment) { + string_replace(comment, '"', ' '); + } + + command = talloc_asprintf(ctx, + "%s \"%s\" \"%s\" \"%s\" \"%s\" %d", + lp_add_share_command(talloc_tos(), lp_sub), + get_dyn_CONFIGFILE(), + share_name_in, + path, + comment ? comment : "", + max_connections); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + + DEBUG(10,("_srvsvc_NetShareAdd: Running [%s]\n", command )); + + /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/ + + if ( is_disk_op ) + become_root(); + + /* FIXME: use libnetconf here - gd */ + + ret = smbrun(command, NULL, NULL); + if (ret == 0) { + /* Tell everyone we updated smb.conf. */ + messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + } + + if ( is_disk_op ) + unbecome_root(); + + /********* END SeDiskOperatorPrivilege BLOCK *********/ + + DEBUG(3,("_srvsvc_NetShareAdd: Running [%s] returned (%d)\n", + command, ret )); + + TALLOC_FREE(command); + + if ( ret != 0 ) + return WERR_ACCESS_DENIED; + + if (psd) { + NTSTATUS status; + /* Note we use share_name here, not share_name_in as + we need a canonicalized name for setting security. */ + status = set_share_security(share_name, psd); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("_srvsvc_NetShareAdd: Failed to add security info to share %s.\n", + share_name )); + } + } + + /* + * We don't call reload_services() here, the message will + * cause this to be done before the next packet is read + * from the client. JRA. + */ + + DEBUG(5,("_srvsvc_NetShareAdd: %d\n", __LINE__)); + + return WERR_OK; +} + +/******************************************************************* + _srvsvc_NetShareDel + Call "delete share command" with the share name as + a parameter. +********************************************************************/ + +WERROR _srvsvc_NetShareDel(struct pipes_struct *p, + struct srvsvc_NetShareDel *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + char *command = NULL; + char *share_name = NULL; + int ret; + int snum; + bool is_disk_op; + TALLOC_CTX *ctx = p->mem_ctx; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + DEBUG(5,("_srvsvc_NetShareDel: %d\n", __LINE__)); + + if (!r->in.share_name) { + return WERR_NERR_NETNAMENOTFOUND; + } + + if ( strequal(r->in.share_name,"IPC$") + || ( lp_enable_asu_support() && strequal(r->in.share_name,"ADMIN$") ) + || strequal(r->in.share_name,"global") ) + { + return WERR_ACCESS_DENIED; + } + + snum = find_service(talloc_tos(), r->in.share_name, &share_name); + if (!share_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (snum < 0) { + return WERR_BAD_NET_NAME; + } + + /* No change to printer shares. */ + if (lp_printable(snum)) + return WERR_ACCESS_DENIED; + + is_disk_op = security_token_has_privilege( + session_info->security_token, SEC_PRIV_DISK_OPERATOR); + + if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) { + return WERR_ACCESS_DENIED; + } + + if (!lp_delete_share_command(talloc_tos(), lp_sub) || !*lp_delete_share_command(talloc_tos(), lp_sub)) { + DBG_WARNING("_srvsvc_NetShareDel: No \"delete share command\" parameter set in smb.conf.\n"); + return WERR_ACCESS_DENIED; + } + + command = talloc_asprintf(ctx, + "%s \"%s\" \"%s\"", + lp_delete_share_command(talloc_tos(), lp_sub), + get_dyn_CONFIGFILE(), + share_name); + if (!command) { + return WERR_NOT_ENOUGH_MEMORY; + } + + DEBUG(10,("_srvsvc_NetShareDel: Running [%s]\n", command )); + + /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/ + + if ( is_disk_op ) + become_root(); + + ret = smbrun(command, NULL, NULL); + if (ret == 0) { + /* Tell everyone we updated smb.conf. */ + messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0); + } + + if ( is_disk_op ) + unbecome_root(); + + /********* END SeDiskOperatorPrivilege BLOCK *********/ + + DEBUG(3,("_srvsvc_NetShareDel: Running [%s] returned (%d)\n", command, ret )); + + if ( ret != 0 ) + return WERR_ACCESS_DENIED; + + /* Delete the SD in the database. */ + delete_share_security(share_name); + + lp_killservice(snum); + + return WERR_OK; +} + +/******************************************************************* + _srvsvc_NetShareDelSticky +********************************************************************/ + +WERROR _srvsvc_NetShareDelSticky(struct pipes_struct *p, + struct srvsvc_NetShareDelSticky *r) +{ + struct srvsvc_NetShareDel q; + + DEBUG(5,("_srvsvc_NetShareDelSticky: %d\n", __LINE__)); + + q.in.server_unc = r->in.server_unc; + q.in.share_name = r->in.share_name; + q.in.reserved = r->in.reserved; + + return _srvsvc_NetShareDel(p, &q); +} + +/******************************************************************* + _srvsvc_NetRemoteTOD +********************************************************************/ + +WERROR _srvsvc_NetRemoteTOD(struct pipes_struct *p, + struct srvsvc_NetRemoteTOD *r) +{ + struct srvsvc_NetRemoteTODInfo *tod; + struct tm *t; + time_t unixdate = time(NULL); + + /* We do this call first as if we do it *after* the gmtime call + it overwrites the pointed-to values. JRA */ + + uint32_t zone = get_time_zone(unixdate)/60; + + DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__)); + + if ( !(tod = talloc_zero(p->mem_ctx, struct srvsvc_NetRemoteTODInfo)) ) + return WERR_NOT_ENOUGH_MEMORY; + + *r->out.info = tod; + + DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__)); + + t = gmtime(&unixdate); + + /* set up the */ + tod->elapsed = unixdate; + tod->msecs = 0; + tod->hours = t->tm_hour; + tod->mins = t->tm_min; + tod->secs = t->tm_sec; + tod->hunds = 0; + tod->timezone = zone; + tod->tinterval = 10000; + tod->day = t->tm_mday; + tod->month = t->tm_mon + 1; + tod->year = 1900+t->tm_year; + tod->weekday = t->tm_wday; + + DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__)); + + return WERR_OK; +} + +/*********************************************************************************** + _srvsvc_NetGetFileSecurity + Win9x NT tools get security descriptor. +***********************************************************************************/ + +WERROR _srvsvc_NetGetFileSecurity(struct pipes_struct *p, + struct srvsvc_NetGetFileSecurity *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + TALLOC_CTX *frame = talloc_stackframe(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct smb_filename *smb_fname = NULL; + size_t sd_size; + char *servicename = NULL; + SMB_STRUCT_STAT st; + NTSTATUS nt_status; + WERROR werr; + struct conn_struct_tos *c = NULL; + connection_struct *conn = NULL; + struct sec_desc_buf *sd_buf = NULL; + struct files_struct *dirfsp = NULL; + files_struct *fsp = NULL; + int snum; + uint32_t ucf_flags = 0; + NTTIME twrp = 0; + + ZERO_STRUCT(st); + + if (!r->in.share) { + werr = WERR_NERR_NETNAMENOTFOUND; + goto error_exit; + } + snum = find_service(frame, r->in.share, &servicename); + if (!servicename) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto error_exit; + } + if (snum == -1) { + DEBUG(10, ("Could not find service %s\n", servicename)); + werr = WERR_NERR_NETNAMENOTFOUND; + goto error_exit; + } + + nt_status = create_conn_struct_tos_cwd(global_messaging_context(), + snum, + lp_path(frame, lp_sub, snum), + session_info, + &c); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(10, ("create_conn_struct failed: %s\n", + nt_errstr(nt_status))); + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + conn = c->conn; + + nt_status = filename_convert_dirfsp(frame, + conn, + r->in.file, + ucf_flags, + twrp, + &dirfsp, + &smb_fname); + if (!NT_STATUS_IS_OK(nt_status)) { + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + + nt_status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + NULL, /* req */ + dirfsp, /* dirfsp */ + smb_fname, /* fname */ + FILE_READ_ATTRIBUTES, /* access_mask */ + FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */ + FILE_OPEN, /* create_disposition*/ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("_srvsvc_NetGetFileSecurity: can't open %s\n", + smb_fname_str_dbg(smb_fname))); + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + + sd_buf = talloc_zero(p->mem_ctx, struct sec_desc_buf); + if (!sd_buf) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto error_exit; + } + + nt_status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp), + (SECINFO_OWNER + |SECINFO_GROUP + |SECINFO_DACL), sd_buf, &sd_buf->sd); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("_srvsvc_NetGetFileSecurity: Unable to get NT ACL " + "for file %s\n", smb_fname_str_dbg(smb_fname))); + werr = ntstatus_to_werror(nt_status); + TALLOC_FREE(sd_buf); + goto error_exit; + } + + if (sd_buf->sd->dacl) { + sd_buf->sd->dacl->revision = NT4_ACL_REVISION; + } + + sd_size = ndr_size_security_descriptor(sd_buf->sd, 0); + + sd_buf->sd_size = sd_size; + + *r->out.sd_buf = sd_buf; + + werr = WERR_OK; + +error_exit: + + if (fsp) { + close_file_free(NULL, &fsp, NORMAL_CLOSE); + } + + TALLOC_FREE(frame); + return werr; +} + +/*********************************************************************************** + _srvsvc_NetSetFileSecurity + Win9x NT tools set security descriptor. +***********************************************************************************/ + +WERROR _srvsvc_NetSetFileSecurity(struct pipes_struct *p, + struct srvsvc_NetSetFileSecurity *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + TALLOC_CTX *frame = talloc_stackframe(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + struct smb_filename *smb_fname = NULL; + char *servicename = NULL; + struct files_struct *dirfsp = NULL; + files_struct *fsp = NULL; + SMB_STRUCT_STAT st; + NTSTATUS nt_status; + WERROR werr; + struct conn_struct_tos *c = NULL; + connection_struct *conn = NULL; + int snum; + struct security_descriptor *psd = NULL; + uint32_t security_info_sent = 0; + uint32_t ucf_flags = 0; + NTTIME twrp = 0; + + ZERO_STRUCT(st); + + if (!r->in.share) { + werr = WERR_NERR_NETNAMENOTFOUND; + goto error_exit; + } + + snum = find_service(frame, r->in.share, &servicename); + if (!servicename) { + werr = WERR_NOT_ENOUGH_MEMORY; + goto error_exit; + } + + if (snum == -1) { + DEBUG(10, ("Could not find service %s\n", servicename)); + werr = WERR_NERR_NETNAMENOTFOUND; + goto error_exit; + } + + nt_status = create_conn_struct_tos_cwd(global_messaging_context(), + snum, + lp_path(frame, lp_sub, snum), + session_info, + &c); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(10, ("create_conn_struct failed: %s\n", + nt_errstr(nt_status))); + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + conn = c->conn; + + nt_status = filename_convert_dirfsp(frame, + conn, + r->in.file, + ucf_flags, + twrp, + &dirfsp, + &smb_fname); + if (!NT_STATUS_IS_OK(nt_status)) { + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + + nt_status = SMB_VFS_CREATE_FILE( + conn, /* conn */ + NULL, /* req */ + dirfsp, /* dirfsp */ + smb_fname, /* fname */ + FILE_WRITE_ATTRIBUTES, /* access_mask */ + FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */ + FILE_OPEN, /* create_disposition*/ + 0, /* create_options */ + 0, /* file_attributes */ + INTERNAL_OPEN_ONLY, /* oplock_request */ + NULL, /* lease */ + 0, /* allocation_size */ + 0, /* private_flags */ + NULL, /* sd */ + NULL, /* ea_list */ + &fsp, /* result */ + NULL, /* pinfo */ + NULL, NULL); /* create context */ + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("_srvsvc_NetSetFileSecurity: can't open %s\n", + smb_fname_str_dbg(smb_fname))); + werr = ntstatus_to_werror(nt_status); + goto error_exit; + } + + psd = r->in.sd_buf->sd; + security_info_sent = r->in.securityinformation; + + nt_status = set_sd(fsp, psd, security_info_sent); + + if (!NT_STATUS_IS_OK(nt_status) ) { + DEBUG(3,("_srvsvc_NetSetFileSecurity: Unable to set NT ACL " + "on file %s\n", r->in.share)); + werr = WERR_ACCESS_DENIED; + goto error_exit; + } + + werr = WERR_OK; + +error_exit: + + if (fsp) { + close_file_free(NULL, &fsp, NORMAL_CLOSE); + } + + TALLOC_FREE(frame); + return werr; +} + +/*********************************************************************************** + It may be that we want to limit users to creating shares on certain areas of the UNIX file area. + We could define areas by mapping Windows style disks to points on the UNIX directory hierarchy. + These disks would the disks listed by this function. + Users could then create shares relative to these disks. Watch out for moving these disks around. + "Nigel Williams" <nigel@veritas.com>. +***********************************************************************************/ + +static const char *server_disks[] = {"C:"}; + +static uint32_t get_server_disk_count(void) +{ + return sizeof(server_disks)/sizeof(server_disks[0]); +} + +static uint32_t init_server_disk_enum(uint32_t *resume) +{ + uint32_t server_disk_count = get_server_disk_count(); + + /*resume can be an offset into the list for now*/ + + if(*resume & 0x80000000) + *resume = 0; + + if(*resume > server_disk_count) + *resume = server_disk_count; + + return server_disk_count - *resume; +} + +static const char *next_server_disk_enum(uint32_t *resume) +{ + const char *disk; + + if(init_server_disk_enum(resume) == 0) + return NULL; + + disk = server_disks[*resume]; + + (*resume)++; + + DEBUG(10, ("next_server_disk_enum: reporting disk %s. resume handle %d.\n", disk, *resume)); + + return disk; +} + +/******************************************************************** + _srvsvc_NetDiskEnum +********************************************************************/ + +WERROR _srvsvc_NetDiskEnum(struct pipes_struct *p, + struct srvsvc_NetDiskEnum *r) +{ + uint32_t i; + const char *disk_name; + TALLOC_CTX *ctx = p->mem_ctx; + WERROR werr; + uint32_t resume = r->in.resume_handle ? *r->in.resume_handle : 0; + + werr = WERR_OK; + + *r->out.totalentries = init_server_disk_enum(&resume); + + r->out.info->disks = talloc_zero_array(ctx, struct srvsvc_NetDiskInfo0, + MAX_SERVER_DISK_ENTRIES); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks); + + /*allow one struct srvsvc_NetDiskInfo0 for null terminator*/ + + r->out.info->count = 0; + + for(i = 0; i < MAX_SERVER_DISK_ENTRIES -1 && (disk_name = next_server_disk_enum(&resume)); i++) { + + r->out.info->count++; + + /*copy disk name into a unicode string*/ + + r->out.info->disks[i].disk = talloc_strdup(ctx, disk_name); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[i].disk); + } + + /* add a terminating null string. Is this there if there is more data to come? */ + + r->out.info->count++; + + r->out.info->disks[i].disk = talloc_strdup(ctx, ""); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[i].disk); + + if (r->out.resume_handle) { + *r->out.resume_handle = resume; + } + + return werr; +} + +/******************************************************************** + _srvsvc_NetNameValidate +********************************************************************/ + +WERROR _srvsvc_NetNameValidate(struct pipes_struct *p, + struct srvsvc_NetNameValidate *r) +{ + switch (r->in.name_type) { + case 0x9: + if (!validate_net_name(r->in.name, INVALID_SHARENAME_CHARS, + strlen_m(r->in.name))) + { + DEBUG(5,("_srvsvc_NetNameValidate: Bad sharename \"%s\"\n", + r->in.name)); + return WERR_INVALID_NAME; + } + break; + + default: + return WERR_INVALID_LEVEL; + } + + return WERR_OK; +} + +/******************************************************************* +********************************************************************/ + +struct enum_file_close_state { + struct srvsvc_NetFileClose *r; + struct messaging_context *msg_ctx; +}; + +static int enum_file_close_fn(struct file_id id, + const struct share_mode_data *d, + const struct share_mode_entry *e, + void *private_data) +{ + char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE]; + struct enum_file_close_state *state = + (struct enum_file_close_state *)private_data; + uint32_t fid = (((uint32_t)(procid_to_pid(&e->pid))<<16) | e->share_file_id); + + if (fid != state->r->in.fid) { + return 0; /* Not this file. */ + } + + if (!process_exists(e->pid) ) { + return 0; + } + + /* Ok - send the close message. */ + DBG_DEBUG("request to close file %s, %s\n", d->servicepath, + share_mode_str(talloc_tos(), 0, &id, e)); + + share_mode_entry_to_message(msg, &id, e); + + state->r->out.result = ntstatus_to_werror( + messaging_send_buf(state->msg_ctx, + e->pid, MSG_SMB_CLOSE_FILE, + (uint8_t *)msg, sizeof(msg))); + + return 0; +} + +/******************************************************************** + Close a file given a 32-bit file id. +********************************************************************/ + +WERROR _srvsvc_NetFileClose(struct pipes_struct *p, + struct srvsvc_NetFileClose *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct enum_file_close_state state; + bool is_disk_op; + + DEBUG(5,("_srvsvc_NetFileClose: %d\n", __LINE__)); + + is_disk_op = security_token_has_privilege( + session_info->security_token, SEC_PRIV_DISK_OPERATOR); + + if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) { + return WERR_ACCESS_DENIED; + } + + /* enum_file_close_fn sends the close message to + * the relevant smbd process. */ + + r->out.result = WERR_FILE_NOT_FOUND; + state.r = r; + state.msg_ctx = p->msg_ctx; + share_entry_forall(enum_file_close_fn, &state); + return r->out.result; +} + +/******************************************************************** +********************************************************************/ + +WERROR _srvsvc_NetCharDevEnum(struct pipes_struct *p, + struct srvsvc_NetCharDevEnum *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevGetInfo(struct pipes_struct *p, + struct srvsvc_NetCharDevGetInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevControl(struct pipes_struct *p, + struct srvsvc_NetCharDevControl *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevQEnum(struct pipes_struct *p, + struct srvsvc_NetCharDevQEnum *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevQGetInfo(struct pipes_struct *p, + struct srvsvc_NetCharDevQGetInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevQSetInfo(struct pipes_struct *p, + struct srvsvc_NetCharDevQSetInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevQPurge(struct pipes_struct *p, + struct srvsvc_NetCharDevQPurge *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetCharDevQPurgeSelf(struct pipes_struct *p, + struct srvsvc_NetCharDevQPurgeSelf *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetFileGetInfo(struct pipes_struct *p, + struct srvsvc_NetFileGetInfo *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetShareCheck(struct pipes_struct *p, + struct srvsvc_NetShareCheck *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetServerStatisticsGet(struct pipes_struct *p, + struct srvsvc_NetServerStatisticsGet *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetTransportAdd(struct pipes_struct *p, + struct srvsvc_NetTransportAdd *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetTransportEnum(struct pipes_struct *p, + struct srvsvc_NetTransportEnum *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetTransportDel(struct pipes_struct *p, + struct srvsvc_NetTransportDel *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetSetServiceBits(struct pipes_struct *p, + struct srvsvc_NetSetServiceBits *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetPathType(struct pipes_struct *p, + struct srvsvc_NetPathType *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetPathCanonicalize(struct pipes_struct *p, + struct srvsvc_NetPathCanonicalize *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetPathCompare(struct pipes_struct *p, + struct srvsvc_NetPathCompare *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRPRNAMECANONICALIZE(struct pipes_struct *p, + struct srvsvc_NETRPRNAMECANONICALIZE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetPRNameCompare(struct pipes_struct *p, + struct srvsvc_NetPRNameCompare *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetShareDelStart(struct pipes_struct *p, + struct srvsvc_NetShareDelStart *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetShareDelCommit(struct pipes_struct *p, + struct srvsvc_NetShareDelCommit *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetServerTransportAddEx(struct pipes_struct *p, + struct srvsvc_NetServerTransportAddEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NetServerSetServiceBitsEx(struct pipes_struct *p, + struct srvsvc_NetServerSetServiceBitsEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSGETVERSION(struct pipes_struct *p, + struct srvsvc_NETRDFSGETVERSION *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSCREATELOCALPARTITION(struct pipes_struct *p, + struct srvsvc_NETRDFSCREATELOCALPARTITION *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSDELETELOCALPARTITION(struct pipes_struct *p, + struct srvsvc_NETRDFSDELETELOCALPARTITION *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSSETLOCALVOLUMESTATE(struct pipes_struct *p, + struct srvsvc_NETRDFSSETLOCALVOLUMESTATE *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSSETSERVERINFO(struct pipes_struct *p, + struct srvsvc_NETRDFSSETSERVERINFO *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSCREATEEXITPOINT(struct pipes_struct *p, + struct srvsvc_NETRDFSCREATEEXITPOINT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSDELETEEXITPOINT(struct pipes_struct *p, + struct srvsvc_NETRDFSDELETEEXITPOINT *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSMODIFYPREFIX(struct pipes_struct *p, + struct srvsvc_NETRDFSMODIFYPREFIX *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSFIXLOCALVOLUME(struct pipes_struct *p, + struct srvsvc_NETRDFSFIXLOCALVOLUME *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRDFSMANAGERREPORTSITEINFO(struct pipes_struct *p, + struct srvsvc_NETRDFSMANAGERREPORTSITEINFO *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _srvsvc_NETRSERVERTRANSPORTDELEX(struct pipes_struct *p, + struct srvsvc_NETRSERVERTRANSPORTDELEX *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_srvsvc_scompat.c" diff --git a/source3/rpc_server/svcctl/srv_svcctl_nt.c b/source3/rpc_server/svcctl/srv_svcctl_nt.c new file mode 100644 index 0000000..c1df2f6 --- /dev/null +++ b/source3/rpc_server/svcctl/srv_svcctl_nt.c @@ -0,0 +1,1497 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * + * Copyright (C) Marcin Krzysztof Porwit 2005. + * + * Largely Rewritten (Again) by: + * Copyright (C) Gerald (Jerry) Carter 2005. + * Copyright (C) Guenther Deschner 2008,2009. + * + * 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 "system/passwd.h" /* uid_wrapper */ +#include "ntdomain.h" +#include "../libcli/security/security.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "services/services.h" +#include "services/svc_winreg_glue.h" +#include "auth.h" +#include "rpc_server/svcctl/srv_svcctl_nt.h" + +#include "rpc_server/rpc_server.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_svcctl.h" +#include "librpc/gen_ndr/ndr_svcctl_scompat.h" +#include "srv_svcctl_reg.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +struct service_control_op { + const char *name; + SERVICE_CONTROL_OPS *ops; +}; + +/* handle external services */ +extern SERVICE_CONTROL_OPS rcinit_svc_ops; + +/* builtin services (see service_db.c and services/svc_*.c */ +extern SERVICE_CONTROL_OPS spoolss_svc_ops; +extern SERVICE_CONTROL_OPS netlogon_svc_ops; +extern SERVICE_CONTROL_OPS winreg_svc_ops; +extern SERVICE_CONTROL_OPS wins_svc_ops; + +/* make sure this number patches the number of builtin + SERVICE_CONTROL_OPS structure listed above */ + +#define SVCCTL_NUM_INTERNAL_SERVICES 4 + +struct service_control_op *svcctl_ops; + +static const struct generic_mapping scm_generic_map = + { SC_MANAGER_READ_ACCESS, SC_MANAGER_WRITE_ACCESS, SC_MANAGER_EXECUTE_ACCESS, SC_MANAGER_ALL_ACCESS }; +static const struct generic_mapping svc_generic_map = + { SERVICE_READ_ACCESS, SERVICE_WRITE_ACCESS, SERVICE_EXECUTE_ACCESS, SERVICE_ALL_ACCESS }; + + +/******************************************************************** +********************************************************************/ + +bool init_service_op_table( void ) +{ + const char **service_list = lp_svcctl_list(); + int num_services = SVCCTL_NUM_INTERNAL_SERVICES + str_list_length( service_list ); + int i; + + if ( !(svcctl_ops = talloc_array( NULL, struct service_control_op, num_services+1)) ) { + DEBUG(0,("init_service_op_table: talloc() failed!\n")); + return False; + } + + /* services listed in smb.conf get the rc.init interface */ + + for ( i=0; service_list && service_list[i]; i++ ) { + svcctl_ops[i].name = talloc_strdup( svcctl_ops, service_list[i] ); + svcctl_ops[i].ops = &rcinit_svc_ops; + } + + /* add builtin services */ + + svcctl_ops[i].name = talloc_strdup( svcctl_ops, "Spooler" ); + svcctl_ops[i].ops = &spoolss_svc_ops; + i++; + + svcctl_ops[i].name = talloc_strdup( svcctl_ops, "NETLOGON" ); + svcctl_ops[i].ops = &netlogon_svc_ops; + i++; + + svcctl_ops[i].name = talloc_strdup( svcctl_ops, "RemoteRegistry" ); + svcctl_ops[i].ops = &winreg_svc_ops; + i++; + + svcctl_ops[i].name = talloc_strdup( svcctl_ops, "WINS" ); + svcctl_ops[i].ops = &wins_svc_ops; + i++; + + /* NULL terminate the array */ + + svcctl_ops[i].name = NULL; + svcctl_ops[i].ops = NULL; + + return True; +} + +bool shutdown_service_op_table(void) +{ + TALLOC_FREE(svcctl_ops); + + return true; +} + +/******************************************************************** +********************************************************************/ + +static struct service_control_op* find_service_by_name( const char *name ) +{ + int i; + + for ( i=0; svcctl_ops[i].name; i++ ) { + if ( strequal( name, svcctl_ops[i].name ) ) + return &svcctl_ops[i]; + } + + return NULL; +} +/******************************************************************** +********************************************************************/ + +static NTSTATUS svcctl_access_check( struct security_descriptor *sec_desc, struct security_token *token, + uint32_t access_desired, uint32_t *access_granted ) +{ + NTSTATUS status; + if ( geteuid() == sec_initial_uid() ) { + DEBUG(5,("svcctl_access_check: using root's token\n")); + status = get_root_nt_token(&token); + if(!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return se_access_check( sec_desc, token, access_desired, access_granted); +} + +/******************************************************************** +********************************************************************/ + +static struct security_descriptor* construct_scm_sd( TALLOC_CTX *ctx ) +{ + struct security_ace ace[2]; + size_t i = 0; + struct security_descriptor *sd; + struct security_acl *theacl; + size_t sd_size; + + /* basic access for Everyone */ + + init_sec_ace(&ace[i++], &global_sid_World, + SEC_ACE_TYPE_ACCESS_ALLOWED, SC_MANAGER_READ_ACCESS, 0); + + /* Full Access 'BUILTIN\Administrators' */ + + init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators, + SEC_ACE_TYPE_ACCESS_ALLOWED, SC_MANAGER_ALL_ACCESS, 0); + + + /* create the security descriptor */ + + if ( !(theacl = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace)) ) + return NULL; + + if ( !(sd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1, + SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL, + theacl, &sd_size)) ) + return NULL; + + return sd; +} + +/****************************************************************** + Find a registry key handle and return a SERVICE_INFO + *****************************************************************/ + +static SERVICE_INFO *find_service_info_by_hnd(struct pipes_struct *p, + struct policy_handle *hnd) +{ + SERVICE_INFO *service_info = NULL; + NTSTATUS status; + + service_info = find_policy_by_hnd(p, + hnd, + DCESRV_HANDLE_ANY, + SERVICE_INFO, + &status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2,("find_service_info_by_hnd: handle not found\n")); + return NULL; + } + + return service_info; +} + +/****************************************************************** + *****************************************************************/ + +static WERROR create_open_service_handle(struct pipes_struct *p, + struct policy_handle *handle, + uint32_t type, + const char *service, + uint32_t access_granted) +{ + SERVICE_INFO *info = NULL; + WERROR result = WERR_OK; + struct service_control_op *s_op; + + if ( !(info = talloc_zero( NULL, SERVICE_INFO )) ) + return WERR_NOT_ENOUGH_MEMORY; + + /* the Service Manager has a NULL name */ + + info->type = SVC_HANDLE_IS_SCM; + + switch ( type ) { + case SVC_HANDLE_IS_SCM: + info->type = SVC_HANDLE_IS_SCM; + break; + + case SVC_HANDLE_IS_DBLOCK: + info->type = SVC_HANDLE_IS_DBLOCK; + break; + + case SVC_HANDLE_IS_SERVICE: + info->type = SVC_HANDLE_IS_SERVICE; + + /* lookup the SERVICE_CONTROL_OPS */ + + if ( !(s_op = find_service_by_name( service )) ) { + result = WERR_SERVICE_DOES_NOT_EXIST; + goto done; + } + + info->ops = s_op->ops; + + if ( !(info->name = talloc_strdup( info, s_op->name )) ) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + break; + + default: + result = WERR_SERVICE_DOES_NOT_EXIST; + goto done; + } + + info->access_granted = access_granted; + + /* store the SERVICE_INFO and create an open handle */ + + if ( !create_policy_hnd( p, handle, 0, info ) ) { + result = WERR_ACCESS_DENIED; + goto done; + } + +done: + if ( !W_ERROR_IS_OK(result) ) + TALLOC_FREE(info); + + return result; +} + +/******************************************************************** + _svcctl_OpenSCManagerW +********************************************************************/ + +WERROR _svcctl_OpenSCManagerW(struct pipes_struct *p, + struct svcctl_OpenSCManagerW *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *sec_desc; + uint32_t access_granted = 0; + NTSTATUS status; + + /* perform access checks */ + + if ( !(sec_desc = construct_scm_sd( p->mem_ctx )) ) + return WERR_NOT_ENOUGH_MEMORY; + + se_map_generic( &r->in.access_mask, &scm_generic_map ); + status = svcctl_access_check( sec_desc, session_info->security_token, + r->in.access_mask, &access_granted ); + if ( !NT_STATUS_IS_OK(status) ) + return ntstatus_to_werror( status ); + + return create_open_service_handle( p, r->out.handle, SVC_HANDLE_IS_SCM, NULL, access_granted ); +} + +/******************************************************************** + _svcctl_OpenServiceW +********************************************************************/ + +WERROR _svcctl_OpenServiceW(struct pipes_struct *p, + struct svcctl_OpenServiceW *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct security_descriptor *sec_desc; + uint32_t access_granted = 0; + NTSTATUS status; + const char *service = NULL; + WERROR err; + + service = r->in.ServiceName; + if (!service) { + return WERR_NOT_ENOUGH_MEMORY; + } + DEBUG(5, ("_svcctl_OpenServiceW: Attempting to open Service [%s], \n", service)); + + /* based on my tests you can open a service if you have a valid scm handle */ + + if ( !find_service_info_by_hnd( p, r->in.scmanager_handle) ) + return WERR_INVALID_HANDLE; + + /* + * Perform access checks. Use the system session_info in order to ensure + * that we retrieve the security descriptor + */ + err = svcctl_get_secdesc(p->msg_ctx, + get_session_info_system(), + service, + p->mem_ctx, + &sec_desc); + if (W_ERROR_EQUAL(err, WERR_FILE_NOT_FOUND)) { + DBG_NOTICE("service %s does not exist\n", service); + return WERR_SERVICE_DOES_NOT_EXIST; + } + if (!W_ERROR_IS_OK(err)) { + DBG_NOTICE("Failed to get a valid secdesc for %s: %s\n", + service, win_errstr(err)); + return err; + } + + se_map_generic( &r->in.access_mask, &svc_generic_map ); + status = svcctl_access_check( sec_desc, session_info->security_token, + r->in.access_mask, &access_granted ); + if ( !NT_STATUS_IS_OK(status) ) + return ntstatus_to_werror( status ); + + return create_open_service_handle( p, r->out.handle, SVC_HANDLE_IS_SERVICE, service, access_granted ); +} + +/******************************************************************** + _svcctl_CloseServiceHandle +********************************************************************/ + +WERROR _svcctl_CloseServiceHandle(struct pipes_struct *p, + struct svcctl_CloseServiceHandle *r) +{ + if ( !close_policy_hnd( p, r->in.handle ) ) + return WERR_INVALID_HANDLE; + + ZERO_STRUCTP(r->out.handle); + + return WERR_OK; +} + +/******************************************************************** + _svcctl_GetServiceDisplayNameW +********************************************************************/ + +WERROR _svcctl_GetServiceDisplayNameW(struct pipes_struct *p, + struct svcctl_GetServiceDisplayNameW *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *service; + const char *display_name; + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + + /* can only use an SCM handle here */ + + if ( !info || (info->type != SVC_HANDLE_IS_SCM) ) + return WERR_INVALID_HANDLE; + + service = r->in.service_name; + + display_name = svcctl_lookup_dispname(p->mem_ctx, + p->msg_ctx, + session_info, + service); + if (!display_name) { + display_name = ""; + } + + *r->out.display_name = display_name; + *r->out.display_name_length = strlen(display_name); + + return WERR_OK; +} + +/******************************************************************** + _svcctl_QueryServiceStatus +********************************************************************/ + +WERROR _svcctl_QueryServiceStatus(struct pipes_struct *p, + struct svcctl_QueryServiceStatus *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) ) + return WERR_ACCESS_DENIED; + + /* try the service specific status call */ + + return info->ops->service_status( info->name, r->out.service_status ); +} + +/******************************************************************** +********************************************************************/ + +static int enumerate_status(TALLOC_CTX *ctx, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + struct ENUM_SERVICE_STATUSW **status) +{ + int num_services = 0; + int i; + struct ENUM_SERVICE_STATUSW *st; + const char *display_name; + + /* just count */ + while ( svcctl_ops[num_services].name ) + num_services++; + + if ( !(st = talloc_array( ctx, struct ENUM_SERVICE_STATUSW, num_services )) ) { + DEBUG(0,("enumerate_status: talloc() failed!\n")); + return -1; + } + + for ( i=0; i<num_services; i++ ) { + st[i].service_name = talloc_strdup(st, svcctl_ops[i].name ); + + display_name = svcctl_lookup_dispname(ctx, + msg_ctx, + session_info, + svcctl_ops[i].name); + st[i].display_name = talloc_strdup(st, display_name ? display_name : ""); + + svcctl_ops[i].ops->service_status( svcctl_ops[i].name, &st[i].status ); + } + + *status = st; + + return num_services; +} + +/******************************************************************** + _svcctl_EnumServicesStatusW +********************************************************************/ + +WERROR _svcctl_EnumServicesStatusW(struct pipes_struct *p, + struct svcctl_EnumServicesStatusW *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct ENUM_SERVICE_STATUSW *services = NULL; + int num_services; + int i = 0; + size_t buffer_size = 0; + WERROR result = WERR_OK; + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + DATA_BLOB blob = data_blob_null; + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SCM) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_MGR_ENUMERATE_SERVICE) ) { + return WERR_ACCESS_DENIED; + } + + num_services = enumerate_status(p->mem_ctx, + p->msg_ctx, + session_info, + &services); + if (num_services == -1 ) { + return WERR_NOT_ENOUGH_MEMORY; + } + + for ( i=0; i<num_services; i++ ) { + buffer_size += ndr_size_ENUM_SERVICE_STATUSW(&services[i], 0); + } + + buffer_size += buffer_size % 4; + + if (buffer_size > r->in.offered) { + num_services = 0; + result = WERR_MORE_DATA; + } + + if ( W_ERROR_IS_OK(result) ) { + + enum ndr_err_code ndr_err; + struct ndr_push *ndr; + + ndr = ndr_push_init_ctx(p->mem_ctx); + if (ndr == NULL) { + return WERR_INVALID_PARAMETER; + } + + ndr_err = ndr_push_ENUM_SERVICE_STATUSW_array( + ndr, num_services, services); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ntstatus_to_werror(ndr_map_error2ntstatus(ndr_err)); + } + blob = ndr_push_blob(ndr); + memcpy(r->out.service, blob.data, MIN(blob.length, r->in.offered)); + } + + *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered; + *r->out.services_returned = (uint32_t)num_services; + if (r->out.resume_handle) { + *r->out.resume_handle = 0; + } + + return result; +} + +/******************************************************************** + _svcctl_StartServiceW +********************************************************************/ + +WERROR _svcctl_StartServiceW(struct pipes_struct *p, + struct svcctl_StartServiceW *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_START) ) + return WERR_ACCESS_DENIED; + + return info->ops->start_service( info->name ); +} + +/******************************************************************** + _svcctl_ControlService +********************************************************************/ + +WERROR _svcctl_ControlService(struct pipes_struct *p, + struct svcctl_ControlService *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + switch ( r->in.control ) { + case SVCCTL_CONTROL_STOP: + if ( !(info->access_granted & SC_RIGHT_SVC_STOP) ) + return WERR_ACCESS_DENIED; + + return info->ops->stop_service( info->name, + r->out.service_status ); + + case SVCCTL_CONTROL_INTERROGATE: + if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) ) + return WERR_ACCESS_DENIED; + + return info->ops->service_status( info->name, + r->out.service_status ); + default: + return WERR_INVALID_PARAMETER; + } +} + +/******************************************************************** + _svcctl_EnumDependentServicesW +********************************************************************/ + +WERROR _svcctl_EnumDependentServicesW(struct pipes_struct *p, + struct svcctl_EnumDependentServicesW *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.service ); + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_ENUMERATE_DEPENDENTS) ) + return WERR_ACCESS_DENIED; + + switch (r->in.state) { + case SERVICE_STATE_ACTIVE: + case SERVICE_STATE_INACTIVE: + case SERVICE_STATE_ALL: + break; + default: + return WERR_INVALID_PARAMETER; + } + + /* we have to set the outgoing buffer size to the same as the + incoming buffer size (even in the case of failure */ + /* this is done in the autogenerated server already - gd */ + + *r->out.needed = r->in.offered; + + /* no dependent services...basically a stub function */ + *r->out.services_returned = 0; + + return WERR_OK; +} + +/******************************************************************** + _svcctl_QueryServiceStatusEx +********************************************************************/ + +WERROR _svcctl_QueryServiceStatusEx(struct pipes_struct *p, + struct svcctl_QueryServiceStatusEx *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + uint32_t buffer_size; + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) ) + return WERR_ACCESS_DENIED; + + /* we have to set the outgoing buffer size to the same as the + incoming buffer size (even in the case of failure) */ + *r->out.needed = r->in.offered; + + switch ( r->in.info_level ) { + case SVC_STATUS_PROCESS_INFO: + { + struct SERVICE_STATUS_PROCESS svc_stat_proc; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + + /* Get the status of the service.. */ + info->ops->service_status( info->name, &svc_stat_proc.status ); + svc_stat_proc.process_id = getpid(); + svc_stat_proc.service_flags = 0x0; + + ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &svc_stat_proc, + (ndr_push_flags_fn_t)ndr_push_SERVICE_STATUS_PROCESS); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + r->out.buffer = blob.data; + buffer_size = sizeof(struct SERVICE_STATUS_PROCESS); + break; + } + + default: + return WERR_INVALID_LEVEL; + } + + + buffer_size += buffer_size % 4; + *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered; + + if (buffer_size > r->in.offered ) { + return WERR_INSUFFICIENT_BUFFER; + } + + return WERR_OK; +} + +/******************************************************************** +********************************************************************/ + +static WERROR fill_svc_config(TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx, + struct auth_session_info *session_info, + const char *name, + struct QUERY_SERVICE_CONFIG *config) +{ + const char *result = NULL; + + /* now fill in the individual values */ + + ZERO_STRUCTP(config); + + config->displayname = svcctl_lookup_dispname(mem_ctx, + msg_ctx, + session_info, + name); + + result = svcctl_get_string_value(mem_ctx, + msg_ctx, + session_info, + name, + "ObjectName"); + if (result != NULL) { + config->startname = result; + } + + result = svcctl_get_string_value(mem_ctx, + msg_ctx, + session_info, + name, + "ImagePath"); + if (result != NULL) { + config->executablepath = result; + } + + /* a few hard coded values */ + /* loadordergroup and dependencies are empty */ + + config->tag_id = 0x00000000; /* unassigned loadorder group */ + config->service_type = SERVICE_TYPE_WIN32_OWN_PROCESS; + config->error_control = SVCCTL_SVC_ERROR_NORMAL; + + /* set the start type. NetLogon and WINS are disabled to prevent + the client from showing the "Start" button (if of course the services + are not running */ + + if ( strequal( name, "NETLOGON" ) && ( lp_servicenumber(name) == -1 ) ) + config->start_type = SVCCTL_DISABLED; + else if ( strequal( name, "WINS" ) && ( !lp_we_are_a_wins_server() )) + config->start_type = SVCCTL_DISABLED; + else + config->start_type = SVCCTL_DEMAND_START; + + return WERR_OK; +} + +/******************************************************************** + _svcctl_QueryServiceConfigW +********************************************************************/ + +WERROR _svcctl_QueryServiceConfigW(struct pipes_struct *p, + struct svcctl_QueryServiceConfigW *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + uint32_t buffer_size; + WERROR wresult; + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_CONFIG) ) + return WERR_ACCESS_DENIED; + + /* we have to set the outgoing buffer size to the same as the + incoming buffer size (even in the case of failure */ + + *r->out.needed = r->in.offered; + + wresult = fill_svc_config(p->mem_ctx, + p->msg_ctx, + session_info, + info->name, + r->out.query); + if ( !W_ERROR_IS_OK(wresult) ) + return wresult; + + buffer_size = ndr_size_QUERY_SERVICE_CONFIG(r->out.query, 0); + *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered; + + if (buffer_size > r->in.offered ) { + ZERO_STRUCTP(r->out.query); + return WERR_INSUFFICIENT_BUFFER; + } + + return WERR_OK; +} + +/******************************************************************** + _svcctl_QueryServiceConfig2W +********************************************************************/ + +WERROR _svcctl_QueryServiceConfig2W(struct pipes_struct *p, + struct svcctl_QueryServiceConfig2W *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + uint32_t buffer_size; + DATA_BLOB blob = data_blob_null; + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_CONFIG) ) + return WERR_ACCESS_DENIED; + + /* we have to set the outgoing buffer size to the same as the + incoming buffer size (even in the case of failure */ + *r->out.needed = r->in.offered; + + switch ( r->in.info_level ) { + case SERVICE_CONFIG_DESCRIPTION: + { + struct SERVICE_DESCRIPTION desc_buf; + const char *description; + enum ndr_err_code ndr_err; + + description = svcctl_lookup_description(p->mem_ctx, + p->msg_ctx, + session_info, + info->name); + + desc_buf.description = description; + + ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &desc_buf, + (ndr_push_flags_fn_t)ndr_push_SERVICE_DESCRIPTION); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + break; + } + break; + case SERVICE_CONFIG_FAILURE_ACTIONS: + { + struct SERVICE_FAILURE_ACTIONSW actions; + enum ndr_err_code ndr_err; + + /* nothing to say...just service the request */ + + ZERO_STRUCT( actions ); + + ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &actions, + (ndr_push_flags_fn_t)ndr_push_SERVICE_FAILURE_ACTIONSW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + break; + } + break; + + default: + return WERR_INVALID_LEVEL; + } + + buffer_size = blob.length; + buffer_size += buffer_size % 4; + *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered; + + if (buffer_size > r->in.offered) + return WERR_INSUFFICIENT_BUFFER; + + memcpy(r->out.buffer, blob.data, blob.length); + + return WERR_OK; +} + +/******************************************************************** + _svcctl_LockServiceDatabase +********************************************************************/ + +WERROR _svcctl_LockServiceDatabase(struct pipes_struct *p, + struct svcctl_LockServiceDatabase *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + + /* perform access checks */ + + if ( !info || (info->type != SVC_HANDLE_IS_SCM) ) + return WERR_INVALID_HANDLE; + + if ( !(info->access_granted & SC_RIGHT_MGR_LOCK) ) + return WERR_ACCESS_DENIED; + + /* Just open a handle. Doesn't actually lock anything */ + + return create_open_service_handle( p, r->out.lock, SVC_HANDLE_IS_DBLOCK, NULL, 0 ); +} + +/******************************************************************** + _svcctl_UnlockServiceDatabase +********************************************************************/ + +WERROR _svcctl_UnlockServiceDatabase(struct pipes_struct *p, + struct svcctl_UnlockServiceDatabase *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.lock ); + + + if ( !info || (info->type != SVC_HANDLE_IS_DBLOCK) ) + return WERR_INVALID_HANDLE; + + return close_policy_hnd( p, r->out.lock) ? WERR_OK : WERR_INVALID_HANDLE; +} + +/******************************************************************** + _svcctl_QueryServiceObjectSecurity +********************************************************************/ + +WERROR _svcctl_QueryServiceObjectSecurity(struct pipes_struct *p, + struct svcctl_QueryServiceObjectSecurity *r) +{ + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + struct security_descriptor *sec_desc; + NTSTATUS status; + uint8_t *buffer = NULL; + size_t len = 0; + WERROR err; + + + /* only support the SCM and individual services */ + + if ( !info || !(info->type & (SVC_HANDLE_IS_SERVICE|SVC_HANDLE_IS_SCM)) ) + return WERR_INVALID_HANDLE; + + /* check access reights (according to MSDN) */ + + if ( !(info->access_granted & SEC_STD_READ_CONTROL) ) + return WERR_ACCESS_DENIED; + + /* TODO: handle something besides SECINFO_DACL */ + + if ( (r->in.security_flags & SECINFO_DACL) != SECINFO_DACL ) + return WERR_INVALID_PARAMETER; + + /* Lookup the security descriptor and marshall it up for a reply */ + err = svcctl_get_secdesc(p->msg_ctx, + get_session_info_system(), + info->name, + p->mem_ctx, + &sec_desc); + if (W_ERROR_EQUAL(err, WERR_FILE_NOT_FOUND)) { + DBG_NOTICE("service %s does not exist\n", info->name); + return WERR_SERVICE_DOES_NOT_EXIST; + } + if (!W_ERROR_IS_OK(err)) { + DBG_NOTICE("Failed to get a valid secdesc for %s: %s\n", + info->name, win_errstr(err)); + return err; + } + + *r->out.needed = ndr_size_security_descriptor(sec_desc, 0); + + if ( *r->out.needed > r->in.offered) { + return WERR_INSUFFICIENT_BUFFER; + } + + status = marshall_sec_desc(p->mem_ctx, sec_desc, &buffer, &len); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + *r->out.needed = len; + memcpy(r->out.buffer, buffer, len); + + return WERR_OK; +} + +/******************************************************************** + _svcctl_SetServiceObjectSecurity +********************************************************************/ + +WERROR _svcctl_SetServiceObjectSecurity(struct pipes_struct *p, + struct svcctl_SetServiceObjectSecurity *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle ); + struct security_descriptor *sec_desc = NULL; + uint32_t required_access; + NTSTATUS status; + + if ( !info || !(info->type & (SVC_HANDLE_IS_SERVICE|SVC_HANDLE_IS_SCM)) ) + return WERR_INVALID_HANDLE; + + /* can't set the security de4scriptor on the ServiceControlManager */ + + if ( info->type == SVC_HANDLE_IS_SCM ) + return WERR_ACCESS_DENIED; + + /* check the access on the open handle */ + + switch ( r->in.security_flags ) { + case SECINFO_DACL: + required_access = SEC_STD_WRITE_DAC; + break; + + case SECINFO_OWNER: + case SECINFO_GROUP: + required_access = SEC_STD_WRITE_OWNER; + break; + + case SECINFO_SACL: + return WERR_INVALID_PARAMETER; + default: + return WERR_INVALID_PARAMETER; + } + + if ( !(info->access_granted & required_access) ) + return WERR_ACCESS_DENIED; + + /* read the security descfriptor */ + + status = unmarshall_sec_desc(p->mem_ctx, + r->in.buffer, + r->in.offered, + &sec_desc); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* store the new SD */ + + if (!svcctl_set_secdesc(p->msg_ctx, session_info, info->name, sec_desc)) + return WERR_ACCESS_DENIED; + + return WERR_OK; +} + + +WERROR _svcctl_DeleteService(struct pipes_struct *p, + struct svcctl_DeleteService *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_SetServiceStatus(struct pipes_struct *p, + struct svcctl_SetServiceStatus *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_NotifyBootConfigStatus(struct pipes_struct *p, + struct svcctl_NotifyBootConfigStatus *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_SCSetServiceBitsW(struct pipes_struct *p, + struct svcctl_SCSetServiceBitsW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_ChangeServiceConfigW(struct pipes_struct *p, + struct svcctl_ChangeServiceConfigW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_CreateServiceW(struct pipes_struct *p, + struct svcctl_CreateServiceW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_QueryServiceLockStatusW(struct pipes_struct *p, + struct svcctl_QueryServiceLockStatusW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_GetServiceKeyNameW(struct pipes_struct *p, + struct svcctl_GetServiceKeyNameW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_SCSetServiceBitsA(struct pipes_struct *p, + struct svcctl_SCSetServiceBitsA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_ChangeServiceConfigA(struct pipes_struct *p, + struct svcctl_ChangeServiceConfigA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_CreateServiceA(struct pipes_struct *p, + struct svcctl_CreateServiceA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_EnumDependentServicesA(struct pipes_struct *p, + struct svcctl_EnumDependentServicesA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_EnumServicesStatusA(struct pipes_struct *p, + struct svcctl_EnumServicesStatusA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_OpenSCManagerA(struct pipes_struct *p, + struct svcctl_OpenSCManagerA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_OpenServiceA(struct pipes_struct *p, + struct svcctl_OpenServiceA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_QueryServiceConfigA(struct pipes_struct *p, + struct svcctl_QueryServiceConfigA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_QueryServiceLockStatusA(struct pipes_struct *p, + struct svcctl_QueryServiceLockStatusA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_StartServiceA(struct pipes_struct *p, + struct svcctl_StartServiceA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_GetServiceDisplayNameA(struct pipes_struct *p, + struct svcctl_GetServiceDisplayNameA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_GetServiceKeyNameA(struct pipes_struct *p, + struct svcctl_GetServiceKeyNameA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_GetCurrentGroupeStateW(struct pipes_struct *p, + struct svcctl_GetCurrentGroupeStateW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_EnumServiceGroupW(struct pipes_struct *p, + struct svcctl_EnumServiceGroupW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_ChangeServiceConfig2A(struct pipes_struct *p, + struct svcctl_ChangeServiceConfig2A *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_ChangeServiceConfig2W(struct pipes_struct *p, + struct svcctl_ChangeServiceConfig2W *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_QueryServiceConfig2A(struct pipes_struct *p, + struct svcctl_QueryServiceConfig2A *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_EnumServicesStatusExA(struct pipes_struct *p, + struct svcctl_EnumServicesStatusExA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_EnumServicesStatusExW(struct pipes_struct *p, + struct svcctl_EnumServicesStatusExW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +WERROR _svcctl_SCSendTSMessage(struct pipes_struct *p, + struct svcctl_SCSendTSMessage *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/**************************************************************** + _svcctl_CreateServiceWOW64A +****************************************************************/ + +WERROR _svcctl_CreateServiceWOW64A(struct pipes_struct *p, + struct svcctl_CreateServiceWOW64A *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_CreateServiceWOW64W +****************************************************************/ + +WERROR _svcctl_CreateServiceWOW64W(struct pipes_struct *p, + struct svcctl_CreateServiceWOW64W *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _Opnum46NotUsedOnWire +****************************************************************/ + +void _Opnum46NotUsedOnWire(struct pipes_struct *p, + struct Opnum46NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _svcctl_NotifyServiceStatusChange +****************************************************************/ + +WERROR _svcctl_NotifyServiceStatusChange(struct pipes_struct *p, + struct svcctl_NotifyServiceStatusChange *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_GetNotifyResults +****************************************************************/ + +WERROR _svcctl_GetNotifyResults(struct pipes_struct *p, + struct svcctl_GetNotifyResults *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_CloseNotifyHandle +****************************************************************/ + +WERROR _svcctl_CloseNotifyHandle(struct pipes_struct *p, + struct svcctl_CloseNotifyHandle *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_ControlServiceExA +****************************************************************/ + +WERROR _svcctl_ControlServiceExA(struct pipes_struct *p, + struct svcctl_ControlServiceExA *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_ControlServiceExW +****************************************************************/ + +WERROR _svcctl_ControlServiceExW(struct pipes_struct *p, + struct svcctl_ControlServiceExW *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _Opnum52NotUsedOnWire +****************************************************************/ + +void _Opnum52NotUsedOnWire(struct pipes_struct *p, + struct Opnum52NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _Opnum53NotUsedOnWire +****************************************************************/ + +void _Opnum53NotUsedOnWire(struct pipes_struct *p, + struct Opnum53NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _Opnum54NotUsedOnWire +****************************************************************/ + +void _Opnum54NotUsedOnWire(struct pipes_struct *p, + struct Opnum54NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _Opnum55NotUsedOnWire +****************************************************************/ + +void _Opnum55NotUsedOnWire(struct pipes_struct *p, + struct Opnum55NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _svcctl_QueryServiceConfigEx +****************************************************************/ + +WERROR _svcctl_QueryServiceConfigEx(struct pipes_struct *p, + struct svcctl_QueryServiceConfigEx *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _Opnum57NotUsedOnWire +****************************************************************/ + +void _Opnum57NotUsedOnWire(struct pipes_struct *p, + struct Opnum57NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _Opnum58NotUsedOnWire +****************************************************************/ + +void _Opnum58NotUsedOnWire(struct pipes_struct *p, + struct Opnum58NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _Opnum59NotUsedOnWire +****************************************************************/ + +void _Opnum59NotUsedOnWire(struct pipes_struct *p, + struct Opnum59NotUsedOnWire *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; +} + + +/**************************************************************** + _svcctl_CreateWowService +****************************************************************/ + +WERROR _svcctl_CreateWowService(struct pipes_struct *p, + struct svcctl_CreateWowService *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + + +/**************************************************************** + _svcctl_OpenSCManager2 +****************************************************************/ + +WERROR _svcctl_OpenSCManager2(struct pipes_struct *p, + struct svcctl_OpenSCManager2 *r) +{ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +static NTSTATUS svcctl__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +static NTSTATUS svcctl__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); + +#define DCESRV_INTERFACE_SVCCTL_INIT_SERVER \ + svcctl_init_server + +#define DCESRV_INTERFACE_SVCCTL_SHUTDOWN_SERVER \ + svcctl_shutdown_server + +static NTSTATUS svcctl_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + bool ok; + + /* initialize the control hooks */ + init_service_op_table(); + + ok = svcctl_init_winreg(msg_ctx); + if (!ok) { + return NT_STATUS_UNSUCCESSFUL; + } + + return svcctl__op_init_server(dce_ctx, ep_server); +} + +static NTSTATUS svcctl_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + shutdown_service_op_table(); + + return svcctl__op_shutdown_server(dce_ctx, ep_server); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_svcctl_scompat.c" diff --git a/source3/rpc_server/svcctl/srv_svcctl_nt.h b/source3/rpc_server/svcctl/srv_svcctl_nt.h new file mode 100644 index 0000000..dd04927 --- /dev/null +++ b/source3/rpc_server/svcctl/srv_svcctl_nt.h @@ -0,0 +1,33 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * + * Copyright (C) Marcin Krzysztof Porwit 2005. + * + * Largely Rewritten (Again) by: + * Copyright (C) Gerald (Jerry) Carter 2005. + * Copyright (C) Guenther Deschner 2008,2009. + * + * 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 _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_ +#define _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_ + +/* The following definitions come from rpc_server/srv_svcctl_nt.c */ + +bool init_service_op_table( void ); +bool shutdown_service_op_table(void); + +#endif /* _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_ */ diff --git a/source3/rpc_server/svcctl/srv_svcctl_reg.c b/source3/rpc_server/svcctl/srv_svcctl_reg.c new file mode 100644 index 0000000..78f6096 --- /dev/null +++ b/source3/rpc_server/svcctl/srv_svcctl_reg.c @@ -0,0 +1,678 @@ +/* + * Unix SMB/CIFS implementation. + * + * SVCCTL RPC server keys initialization + * + * Copyright (c) 2005 Marcin Krzysztof Porwit + * Copyright (c) 2005 Gerald (Jerry) Carter + * 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 "system/filesys.h" +#include "services/services.h" +#include "services/svc_winreg_glue.h" +#include "../librpc/gen_ndr/ndr_winreg_c.h" +#include "rpc_client/cli_winreg_int.h" +#include "rpc_client/cli_winreg.h" +#include "rpc_server/svcctl/srv_svcctl_reg.h" +#include "auth.h" +#include "registry/reg_backend_db.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_REGISTRY + +#define TOP_LEVEL_SERVICES_KEY "SYSTEM\\CurrentControlSet\\Services" + +struct rcinit_file_information { + char *description; +}; + +struct service_display_info { + const char *servicename; + const char *daemon; + const char *dispname; + const char *description; +}; + +static struct service_display_info builtin_svcs[] = { + { + "Spooler", + "smbd", + "Print Spooler", + "Internal service for spooling files to print devices" + }, + { + "NETLOGON", + "smbd", + "Net Logon", + "File service providing access to policy and profile data (not" + "remotely manageable)" + }, + { + "RemoteRegistry", + "smbd", + "Remote Registry Service", + "Internal service providing remote access to the Samba registry" + }, + { + "WINS", + "nmbd", + "Windows Internet Name Service (WINS)", + "Internal service providing a NetBIOS point-to-point name server" + "(not remotely manageable)" + }, + { NULL, NULL, NULL, NULL } +}; + +static struct service_display_info common_unix_svcs[] = { + { "cups", NULL, "Common Unix Printing System","Provides unified printing support for all operating systems" }, + { "postfix", NULL, "Internet Mail Service", "Provides support for sending and receiving electonic mail" }, + { "sendmail", NULL, "Internet Mail Service", "Provides support for sending and receiving electonic mail" }, + { "portmap", NULL, "TCP Port to RPC PortMapper",NULL }, + { "xinetd", NULL, "Internet Meta-Daemon", NULL }, + { "inet", NULL, "Internet Meta-Daemon", NULL }, + { "xntpd", NULL, "Network Time Service", NULL }, + { "ntpd", NULL, "Network Time Service", NULL }, + { "lpd", NULL, "BSD Print Spooler", NULL }, + { "nfsserver", NULL, "Network File Service", NULL }, + { "cron", NULL, "Scheduling Service", NULL }, + { "at", NULL, "Scheduling Service", NULL }, + { "nscd", NULL, "Name Service Cache Daemon", NULL }, + { "slapd", NULL, "LDAP Directory Service", NULL }, + { "ldap", NULL, "LDAP DIrectory Service", NULL }, + { "ypbind", NULL, "NIS Directory Service", NULL }, + { "courier-imap", NULL, "IMAP4 Mail Service", NULL }, + { "courier-pop3", NULL, "POP3 Mail Service", NULL }, + { "named", NULL, "Domain Name Service", NULL }, + { "bind", NULL, "Domain Name Service", NULL }, + { "httpd", NULL, "HTTP Server", NULL }, + { "apache", NULL, "HTTP Server", "Provides s highly scalable and flexible web server " + "capable of implementing various protocols including " + "but not limited to HTTP" }, + { "autofs", NULL, "Automounter", NULL }, + { "squid", NULL, "Web Cache Proxy ", NULL }, + { "perfcountd", NULL, "Performance Monitoring Daemon", NULL }, + { "pgsql", NULL, "PgSQL Database Server", "Provides service for SQL database from Postgresql.org" }, + { "arpwatch", NULL, "ARP Tables watcher", "Provides service for monitoring ARP tables for changes" }, + { "dhcpd", NULL, "DHCP Server", "Provides service for dynamic host configuration and IP assignment" }, + { "nwserv", NULL, "NetWare Server Emulator", "Provides service for emulating Novell NetWare 3.12 server" }, + { "proftpd", NULL, "Professional FTP Server", "Provides high configurable service for FTP connection and " + "file transferring" }, + { "ssh2", NULL, "SSH Secure Shell", "Provides service for secure connection for remote administration" }, + { "sshd", NULL, "SSH Secure Shell", "Provides service for secure connection for remote administration" }, + { NULL, NULL, NULL, NULL } +}; + +/******************************************************************** + This is where we do the dirty work of filling in things like the + Display name, Description, etc... +********************************************************************/ +static char *svcctl_get_common_service_dispname(TALLOC_CTX *mem_ctx, + const char *servicename) +{ + uint32_t i; + + for (i = 0; common_unix_svcs[i].servicename; i++) { + if (strequal(servicename, common_unix_svcs[i].servicename)) { + char *dispname; + dispname = talloc_asprintf(mem_ctx, "%s (%s)", + common_unix_svcs[i].dispname, + common_unix_svcs[i].servicename); + if (dispname == NULL) { + return NULL; + } + return dispname; + } + } + + return talloc_strdup(mem_ctx, servicename); +} + +/******************************************************************** +********************************************************************/ +static char *svcctl_cleanup_string(TALLOC_CTX *mem_ctx, + const char *string) +{ + char *clean = NULL; + char *begin, *end; + + clean = talloc_strdup(mem_ctx, string); + if (clean == NULL) { + return NULL; + } + begin = clean; + + /* trim any beginning whilespace */ + while (isspace(*begin)) { + begin++; + } + + if (*begin == '\0') { + return NULL; + } + + /* trim any trailing whitespace or carriage returns. + Start at the end and move backwards */ + + end = begin + strlen(begin) - 1; + + while (isspace(*end) || *end=='\n' || *end=='\r') { + *end = '\0'; + end--; + } + + return begin; +} + +/******************************************************************** +********************************************************************/ +static bool read_init_file(TALLOC_CTX *mem_ctx, + const char *servicename, + struct rcinit_file_information **service_info) +{ + struct rcinit_file_information *info = NULL; + char *filepath = NULL; + char str[1024]; + FILE *f = NULL; + char *p = NULL; + + info = talloc_zero(mem_ctx, struct rcinit_file_information); + if (info == NULL) { + return false; + } + + /* attempt the file open */ + + filepath = talloc_asprintf(mem_ctx, + "%s/%s/%s", + get_dyn_MODULESDIR(), + SVCCTL_SCRIPT_DIR, + servicename); + if (filepath == NULL) { + return false; + } + f = fopen( filepath, "r" ); + if (f == NULL) { + DEBUG(0,("read_init_file: failed to open [%s]\n", filepath)); + return false; + } + + while ((fgets(str, sizeof(str) - 1, f)) != NULL) { + /* ignore everything that is not a full line + comment starting with a '#' */ + + if (str[0] != '#') { + continue; + } + + /* Look for a line like '^#.*Description:' */ + + p = strstr(str, "Description:"); + if (p != NULL) { + char *desc; + size_t len = strlen(p); + + if (len <= 12) { + break; + } + + desc = svcctl_cleanup_string(mem_ctx, p + 12); + if (desc != NULL) { + info->description = talloc_strdup(info, desc); + } + } + } + + fclose(f); + + if (info->description == NULL) { + info->description = talloc_strdup(info, + "External Unix Service"); + if (info->description == NULL) { + return false; + } + } + + *service_info = info; + + return true; +} + +static bool svcctl_add_service(TALLOC_CTX *mem_ctx, + struct dcerpc_binding_handle *h, + struct policy_handle *hive_hnd, + const char *key, + uint32_t access_mask, + const char *name) +{ + enum winreg_CreateAction action = REG_ACTION_NONE; + struct security_descriptor *sd = NULL; + struct policy_handle key_hnd; + struct winreg_String wkey; + struct winreg_String wkeyclass; + char *description = NULL; + char *dname = NULL; + char *ipath = NULL; + bool ok = false; + uint32_t i; + NTSTATUS status; + WERROR result = WERR_OK; + + ZERO_STRUCT(key_hnd); + + ZERO_STRUCT(wkey); + wkey.name = talloc_asprintf(mem_ctx, "%s\\%s", key, name); + if (wkey.name == NULL) { + goto done; + } + + ZERO_STRUCT(wkeyclass); + wkeyclass.name = ""; + + status = dcerpc_winreg_CreateKey(h, + mem_ctx, + hive_hnd, + wkey, + wkeyclass, + 0, + access_mask, + NULL, + &key_hnd, + &action, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, win_errstr(result))); + goto done; + } + + /* These values are hardcoded in all QueryServiceConfig() replies. + I'm just storing them here for cosmetic purposes */ + status = dcerpc_winreg_set_dword(mem_ctx, + h, + &key_hnd, + "Start", + SVCCTL_AUTO_START, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_dword(mem_ctx, + h, + &key_hnd, + "Type", + SERVICE_TYPE_WIN32_OWN_PROCESS, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_dword(mem_ctx, + h, + &key_hnd, + "ErrorControl", + SVCCTL_SVC_ERROR_NORMAL, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_sz(mem_ctx, + h, + &key_hnd, + "ObjectName", + "LocalSystem", + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + /* + * Special considerations for internal services and the DisplayName + * value. + */ + for (i = 0; builtin_svcs[i].servicename; i++) { + if (strequal(name, builtin_svcs[i].servicename)) { + ipath = talloc_asprintf(mem_ctx, + "%s/%s/%s", + get_dyn_MODULESDIR(), + SVCCTL_SCRIPT_DIR, + builtin_svcs[i].daemon); + description = talloc_strdup(mem_ctx, builtin_svcs[i].description); + dname = talloc_strdup(mem_ctx, builtin_svcs[i].dispname); + break; + } + } + + /* Default to an external service if we haven't found a match */ + if (builtin_svcs[i].servicename == NULL) { + struct rcinit_file_information *init_info = NULL; + char *dispname = NULL; + + ipath = talloc_asprintf(mem_ctx, + "%s/%s/%s", + get_dyn_MODULESDIR(), + SVCCTL_SCRIPT_DIR, + name); + + /* lookup common unix display names */ + dispname = svcctl_get_common_service_dispname(mem_ctx, name); + dname = talloc_strdup(mem_ctx, dispname ? dispname : ""); + + /* get info from init file itself */ + if (read_init_file(mem_ctx, name, &init_info)) { + description = talloc_strdup(mem_ctx, + init_info->description); + } else { + description = talloc_strdup(mem_ctx, + "External Unix Service"); + } + } + + if (ipath == NULL || dname == NULL || description == NULL) { + goto done; + } + + status = dcerpc_winreg_set_sz(mem_ctx, + h, + &key_hnd, + "DisplayName", + dname, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_sz(mem_ctx, + h, + &key_hnd, + "ImagePath", + ipath, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_sz(mem_ctx, + h, + &key_hnd, + "Description", + description, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + sd = svcctl_gen_service_sd(mem_ctx); + if (sd == NULL) { + DEBUG(0, ("add_new_svc_name: Failed to create default " + "sec_desc!\n")); + goto done; + } + + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &result); + } + ZERO_STRUCT(key_hnd); + + ZERO_STRUCT(wkey); + wkey.name = talloc_asprintf(mem_ctx, "%s\\%s\\Security", key, name); + if (wkey.name == NULL) { + result = WERR_NOT_ENOUGH_MEMORY; + goto done; + } + + ZERO_STRUCT(wkeyclass); + wkeyclass.name = ""; + + status = dcerpc_winreg_CreateKey(h, + mem_ctx, + hive_hnd, + wkey, + wkeyclass, + 0, + access_mask, + NULL, + &key_hnd, + &action, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n", + wkey.name, win_errstr(result))); + goto done; + } + + status = dcerpc_winreg_set_sd(mem_ctx, + h, + &key_hnd, + "Security", + sd, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n", + win_errstr(result))); + goto done; + } + + ok = true; +done: + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &result); + } + + return ok; +} + +bool svcctl_init_winreg(struct messaging_context *msg_ctx) +{ + struct dcerpc_binding_handle *h = NULL; + uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + struct policy_handle hive_hnd, key_hnd; + const char **service_list = lp_svcctl_list(); + const char **subkeys = NULL; + uint32_t num_subkeys = 0; + char *key = NULL; + uint32_t i; + NTSTATUS status; + WERROR result = WERR_OK; + bool ok = false; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return false; + } + + DEBUG(3, ("Initialise the svcctl registry keys if needed.\n")); + + ZERO_STRUCT(hive_hnd); + ZERO_STRUCT(key_hnd); + + key = talloc_strdup(tmp_ctx, TOP_LEVEL_SERVICES_KEY); + if (key == NULL) { + goto done; + } + + status = dcerpc_winreg_int_hklm_openkey(tmp_ctx, + get_session_info_system(), + msg_ctx, + &h, + key, + false, + access_mask, + &hive_hnd, + &key_hnd, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg: Could not open %s - %s\n", + key, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg: Could not open %s - %s\n", + key, win_errstr(result))); + goto done; + } + + /* get all subkeys */ + status = dcerpc_winreg_enum_keys(tmp_ctx, + h, + &key_hnd, + &num_subkeys, + &subkeys, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("svcctl_init_winreg: Could not enum keys at %s - %s\n", + key, nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("svcctl_init_winreg: Could not enum keys at %s - %s\n", + key, win_errstr(result))); + goto done; + } + + for (i = 0; builtin_svcs[i].servicename != NULL; i++) { + uint32_t j; + bool skip = false; + + for (j = 0; j < num_subkeys; j++) { + if (strequal(subkeys[i], builtin_svcs[i].servicename)) { + skip = true; + } + } + + if (skip) { + continue; + } + + ok = svcctl_add_service(tmp_ctx, + h, + &hive_hnd, + key, + access_mask, + builtin_svcs[i].servicename); + if (!ok) { + goto done; + } + } + + for (i = 0; service_list && service_list[i]; i++) { + uint32_t j; + bool skip = false; + + for (j = 0; j < num_subkeys; j++) { + if (strequal(subkeys[i], service_list[i])) { + skip = true; + } + } + + if (skip) { + continue; + } + + ok = svcctl_add_service(tmp_ctx, + h, + &hive_hnd, + key, + access_mask, + service_list[i]); + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result); + } + ZERO_STRUCT(key_hnd); + + if (!ok) { + goto done; + } + } + +done: + if (is_valid_policy_hnd(&key_hnd)) { + dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result); + } + + talloc_free(tmp_ctx); + return ok; +} + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/svcctl/srv_svcctl_reg.h b/source3/rpc_server/svcctl/srv_svcctl_reg.h new file mode 100644 index 0000000..ab12a03 --- /dev/null +++ b/source3/rpc_server/svcctl/srv_svcctl_reg.h @@ -0,0 +1,29 @@ +/* + * Unix SMB/CIFS implementation. + * + * SVCCTL RPC server keys initialization + * + * 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/>. + */ + +#ifndef SRV_SERVICES_REG_H +#define SRV_SERVICES_REG_H + +bool svcctl_init_winreg(struct messaging_context *msg_ctx); + +#endif /* SRV_SERVICES_REG_H */ + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source3/rpc_server/winreg/srv_winreg_nt.c b/source3/rpc_server/winreg/srv_winreg_nt.c new file mode 100644 index 0000000..132213a --- /dev/null +++ b/source3/rpc_server/winreg/srv_winreg_nt.c @@ -0,0 +1,1126 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * + * Copyright (C) Gerald Carter 2002-2006. + * + * 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/>. + */ + +/* Implementation of registry functions. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_winreg.h" +#include "librpc/gen_ndr/ndr_winreg_scompat.h" +#include "registry.h" +#include "registry/reg_api.h" +#include "registry/reg_perfcount.h" +#include "rpc_misc.h" +#include "auth.h" +#include "lib/privileges.h" +#include "libcli/security/secdesc.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +enum handle_types { HTYPE_REGVAL, HTYPE_REGKEY }; + +/****************************************************************** + Find a registry key handle and return a struct registry_key * + *****************************************************************/ + +static struct registry_key *find_regkey_by_hnd(struct pipes_struct *p, + struct policy_handle *hnd, + enum handle_types type) +{ + struct registry_key *regkey = NULL; + NTSTATUS status; + + regkey = find_policy_by_hnd(p, + hnd, + type, + struct registry_key, + &status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2,("find_regkey_index_by_hnd: Registry Key not found: %s\n", + nt_errstr(status))); + return NULL; + } + + return regkey; +} + +/******************************************************************* + Function for open a new registry handle and creating a handle + Note that P should be valid & hnd should already have space + + When we open a key, we store the full path to the key as + HK[LM|U]\<key>\<key>\... + *******************************************************************/ + +static WERROR open_registry_key(struct pipes_struct *p, + struct policy_handle *hnd, + struct registry_key *parent, + const char *subkeyname, + uint32_t access_desired) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR result = WERR_OK; + struct registry_key *key; + + if (parent == NULL) { + result = reg_openhive(p->mem_ctx, subkeyname, access_desired, + session_info->security_token, &key); + } + else { + result = reg_openkey(p->mem_ctx, parent, subkeyname, + access_desired, &key); + } + + if ( !W_ERROR_IS_OK(result) ) { + return result; + } + + if ( !create_policy_hnd( p, hnd, HTYPE_REGKEY, key ) ) { + return WERR_FILE_NOT_FOUND; + } + + return WERR_OK; +} + +/******************************************************************* + Function for open a new registry handle and creating a handle + Note that P should be valid & hnd should already have space + *******************************************************************/ + +static bool close_registry_key(struct pipes_struct *p, + struct policy_handle *hnd, + enum handle_types type) +{ + struct registry_key *regkey = find_regkey_by_hnd(p, hnd, type); + + if ( !regkey ) { + DEBUG(2,("close_registry_key: Invalid handle (%s:%u:%u)\n", + OUR_HANDLE(hnd))); + return False; + } + + close_policy_hnd(p, hnd); + + return True; +} + +/******************************************************************** + _winreg_CloseKey + ********************************************************************/ + +WERROR _winreg_CloseKey(struct pipes_struct *p, + struct winreg_CloseKey *r) +{ + bool ok; + + /* close the policy handle */ + + ok = close_registry_key(p, r->in.handle, HTYPE_REGKEY); + if (!ok) { + return WERR_INVALID_HANDLE; + } + + ZERO_STRUCTP(r->out.handle); + + return WERR_OK; +} + +/******************************************************************* + _winreg_OpenHKLM + ********************************************************************/ + +WERROR _winreg_OpenHKLM(struct pipes_struct *p, + struct winreg_OpenHKLM *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKLM, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKPD + ********************************************************************/ + +WERROR _winreg_OpenHKPD(struct pipes_struct *p, + struct winreg_OpenHKPD *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKPD, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKPT + ********************************************************************/ + +WERROR _winreg_OpenHKPT(struct pipes_struct *p, + struct winreg_OpenHKPT *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKPT, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKCR + ********************************************************************/ + +WERROR _winreg_OpenHKCR(struct pipes_struct *p, + struct winreg_OpenHKCR *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKCR, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKU + ********************************************************************/ + +WERROR _winreg_OpenHKU(struct pipes_struct *p, + struct winreg_OpenHKU *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKU, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKCU + ********************************************************************/ + +WERROR _winreg_OpenHKCU(struct pipes_struct *p, + struct winreg_OpenHKCU *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKCU, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKCC + ********************************************************************/ + +WERROR _winreg_OpenHKCC(struct pipes_struct *p, + struct winreg_OpenHKCC *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKCC, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKDD + ********************************************************************/ + +WERROR _winreg_OpenHKDD(struct pipes_struct *p, + struct winreg_OpenHKDD *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKDD, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenHKPN + ********************************************************************/ + +WERROR _winreg_OpenHKPN(struct pipes_struct *p, + struct winreg_OpenHKPN *r) +{ + return open_registry_key(p, r->out.handle, NULL, KEY_HKPN, r->in.access_mask); +} + +/******************************************************************* + _winreg_OpenKey + ********************************************************************/ + +WERROR _winreg_OpenKey(struct pipes_struct *p, + struct winreg_OpenKey *r) +{ + struct registry_key *parent = find_regkey_by_hnd(p, + r->in.parent_handle, + HTYPE_REGKEY); + + if ( !parent ) + return WERR_INVALID_HANDLE; + + return open_registry_key(p, r->out.handle, parent, r->in.keyname.name, r->in.access_mask); +} + +/******************************************************************* + _winreg_QueryValue + ********************************************************************/ + +WERROR _winreg_QueryValue(struct pipes_struct *p, + struct winreg_QueryValue *r) +{ + WERROR status = WERR_FILE_NOT_FOUND; + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + prs_struct prs_hkpd; + + uint8_t *outbuf = NULL; + uint32_t outbuf_size = 0; + + bool free_buf = False; + bool free_prs = False; + + if ( !regkey ) + return WERR_INVALID_HANDLE; + + if (r->in.value_name->name == NULL) { + return WERR_INVALID_PARAMETER; + } + + if ((r->out.data_length == NULL) || (r->out.type == NULL) || (r->out.data_size == NULL)) { + return WERR_INVALID_PARAMETER; + } + + DEBUG(7,("_winreg_QueryValue: policy key name = [%s]\n", regkey->key->name)); + DEBUG(7,("_winreg_QueryValue: policy key type = [%08x]\n", regkey->key->type)); + + /* Handle QueryValue calls on HKEY_PERFORMANCE_DATA */ + if(regkey->key->type == REG_KEY_HKPD) + { + if (strequal(r->in.value_name->name, "Global")) { + if (!prs_init(&prs_hkpd, *r->in.data_size, p->mem_ctx, MARSHALL)) + return WERR_NOT_ENOUGH_MEMORY; + status = reg_perfcount_get_hkpd( + &prs_hkpd, *r->in.data_size, &outbuf_size, NULL); + outbuf = (uint8_t *)prs_hkpd.data_p; + free_prs = True; + } + else if (strequal(r->in.value_name->name, "Counter 009")) { + outbuf_size = reg_perfcount_get_counter_names( + reg_perfcount_get_base_index(), + (char **)(void *)&outbuf); + free_buf = True; + } + else if (strequal(r->in.value_name->name, "Explain 009")) { + outbuf_size = reg_perfcount_get_counter_help( + reg_perfcount_get_base_index(), + (char **)(void *)&outbuf); + free_buf = True; + } + else if (isdigit(r->in.value_name->name[0])) { + /* we probably have a request for a specific object + * here */ + if (!prs_init(&prs_hkpd, *r->in.data_size, p->mem_ctx, MARSHALL)) + return WERR_NOT_ENOUGH_MEMORY; + status = reg_perfcount_get_hkpd( + &prs_hkpd, *r->in.data_size, &outbuf_size, + r->in.value_name->name); + outbuf = (uint8_t *)prs_hkpd.data_p; + free_prs = True; + } + else { + DEBUG(3,("Unsupported key name [%s] for HKPD.\n", + r->in.value_name->name)); + return WERR_FILE_NOT_FOUND; + } + + *r->out.type = REG_BINARY; + } + else { + struct registry_value *val; + + status = reg_queryvalue(p->mem_ctx, regkey, r->in.value_name->name, + &val); + if (!W_ERROR_IS_OK(status)) { + + DEBUG(10,("_winreg_QueryValue: reg_queryvalue failed with: %s\n", + win_errstr(status))); + + if (r->out.data_size) { + *r->out.data_size = 0; + } + if (r->out.data_length) { + *r->out.data_length = 0; + } + return status; + } + + outbuf = val->data.data; + outbuf_size = val->data.length; + *r->out.type = val->type; + } + + status = WERR_FILE_NOT_FOUND; + + if (*r->in.data_size < outbuf_size) { + *r->out.data_size = outbuf_size; + status = r->in.data ? WERR_MORE_DATA : WERR_OK; + } else { + *r->out.data_length = outbuf_size; + *r->out.data_size = outbuf_size; + if (r->out.data) { + memcpy(r->out.data, outbuf, outbuf_size); + } + status = WERR_OK; + } + + if (free_prs) prs_mem_free(&prs_hkpd); + if (free_buf) SAFE_FREE(outbuf); + + return status; +} + +/***************************************************************************** + _winreg_QueryInfoKey + ****************************************************************************/ + +WERROR _winreg_QueryInfoKey(struct pipes_struct *p, + struct winreg_QueryInfoKey *r) +{ + WERROR status = WERR_OK; + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !regkey ) + return WERR_INVALID_HANDLE; + + r->out.classname->name = NULL; + + status = reg_queryinfokey(regkey, r->out.num_subkeys, r->out.max_subkeylen, + r->out.max_classlen, r->out.num_values, r->out.max_valnamelen, + r->out.max_valbufsize, r->out.secdescsize, + r->out.last_changed_time); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + /* + * These calculations account for the registry buffers being + * UTF-16. They are inexact at best, but so far they worked. + */ + + *r->out.max_subkeylen *= 2; + + *r->out.max_valnamelen += 1; + *r->out.max_valnamelen *= 2; + + return WERR_OK; +} + + +/***************************************************************************** + _winreg_GetVersion + ****************************************************************************/ + +WERROR _winreg_GetVersion(struct pipes_struct *p, + struct winreg_GetVersion *r) +{ + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !regkey ) + return WERR_INVALID_HANDLE; + + return reg_getversion(r->out.version); +} + + +/***************************************************************************** + _winreg_EnumKey + ****************************************************************************/ + +WERROR _winreg_EnumKey(struct pipes_struct *p, + struct winreg_EnumKey *r) +{ + WERROR err = WERR_OK; + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + char *name; + + if ( !key ) + return WERR_INVALID_HANDLE; + + if ( !r->in.name || !r->in.keyclass ) + return WERR_INVALID_PARAMETER; + + DEBUG(8,("_winreg_EnumKey: enumerating key [%s]\n", key->key->name)); + + err = reg_enumkey(p->mem_ctx, key, r->in.enum_index, &name, + r->out.last_changed_time); + if (!W_ERROR_IS_OK(err)) { + return err; + } + r->out.name->name = name; + r->out.keyclass->name = ""; + return WERR_OK; +} + +/***************************************************************************** + _winreg_EnumValue + ****************************************************************************/ + +WERROR _winreg_EnumValue(struct pipes_struct *p, + struct winreg_EnumValue *r) +{ + WERROR err = WERR_OK; + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + char *valname = NULL; + struct registry_value *val = NULL; + + if ( !key ) + return WERR_INVALID_HANDLE; + + if ( !r->in.name ) + return WERR_INVALID_PARAMETER; + + DEBUG(8,("_winreg_EnumValue: enumerating values for key [%s]\n", + key->key->name)); + + err = reg_enumvalue(p->mem_ctx, key, r->in.enum_index, &valname, &val); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + if (r->out.name != NULL) { + r->out.name->name = valname; + } + + if (r->out.type != NULL) { + *r->out.type = val->type; + } + + if (r->out.value != NULL) { + if ((r->out.size == NULL) || (r->out.length == NULL)) { + return WERR_INVALID_PARAMETER; + } + + if (val->data.length > *r->out.size) { + *r->out.size = val->data.length; + return WERR_MORE_DATA; + } + + memcpy( r->out.value, val->data.data, val->data.length ); + } + + if (r->out.length != NULL) { + *r->out.length = val->data.length; + } + if (r->out.size != NULL) { + *r->out.size = val->data.length; + } + + return WERR_OK; +} + +/******************************************************************* + _winreg_InitiateSystemShutdown + ********************************************************************/ + +WERROR _winreg_InitiateSystemShutdown(struct pipes_struct *p, + struct winreg_InitiateSystemShutdown *r) +{ + struct winreg_InitiateSystemShutdownEx s; + + s.in.hostname = r->in.hostname; + s.in.message = r->in.message; + s.in.timeout = r->in.timeout; + s.in.force_apps = r->in.force_apps; + s.in.do_reboot = r->in.do_reboot; + s.in.reason = 0; + + /* thunk down to _winreg_InitiateSystemShutdownEx() + (just returns a status) */ + + return _winreg_InitiateSystemShutdownEx( p, &s ); +} + +/******************************************************************* + _winreg_InitiateSystemShutdownEx + ********************************************************************/ + +#define SHUTDOWN_R_STRING "-r" +#define SHUTDOWN_F_STRING "-f" + + +WERROR _winreg_InitiateSystemShutdownEx(struct pipes_struct *p, + struct winreg_InitiateSystemShutdownEx *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *shutdown_script = NULL; + char *chkmsg = NULL; + fstring str_timeout; + fstring str_reason; + fstring do_reboot; + fstring f; + int ret = -1; + bool can_shutdown = false; + + shutdown_script = lp_shutdown_script(p->mem_ctx, lp_sub); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + if (!*shutdown_script) { + return WERR_ACCESS_DENIED; + } + + /* pull the message string and perform necessary sanity checks on it */ + + if ( r->in.message && r->in.message->string ) { + chkmsg = talloc_alpha_strcpy(p->mem_ctx, + r->in.message->string, + NULL); + if (chkmsg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + fstr_sprintf(str_timeout, "%d", r->in.timeout); + fstr_sprintf(do_reboot, r->in.do_reboot ? SHUTDOWN_R_STRING : ""); + fstr_sprintf(f, r->in.force_apps ? SHUTDOWN_F_STRING : ""); + fstr_sprintf(str_reason, "%d", r->in.reason ); + + shutdown_script = talloc_all_string_sub(p->mem_ctx, + shutdown_script, "%z", chkmsg ? chkmsg : ""); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + shutdown_script = talloc_all_string_sub(p->mem_ctx, + shutdown_script, "%t", str_timeout); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + shutdown_script = talloc_all_string_sub(p->mem_ctx, + shutdown_script, "%r", do_reboot); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + shutdown_script = talloc_all_string_sub(p->mem_ctx, + shutdown_script, "%f", f); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + shutdown_script = talloc_all_string_sub(p->mem_ctx, + shutdown_script, "%x", str_reason); + if (!shutdown_script) { + return WERR_NOT_ENOUGH_MEMORY; + } + + can_shutdown = security_token_has_privilege( + session_info->security_token, SEC_PRIV_REMOTE_SHUTDOWN); + + /* IF someone has privs, run the shutdown script as root. OTHERWISE run it as not root + Take the error return from the script and provide it as the Windows return code. */ + + /********** BEGIN SeRemoteShutdownPrivilege BLOCK **********/ + + if ( can_shutdown ) + become_root(); + + ret = smbrun(shutdown_script, NULL, NULL); + + if ( can_shutdown ) + unbecome_root(); + + /********** END SeRemoteShutdownPrivilege BLOCK **********/ + + DEBUG(3,("_reg_shutdown_ex: Running the command `%s' gave %d\n", + shutdown_script, ret)); + + return (ret == 0) ? WERR_OK : WERR_ACCESS_DENIED; +} + +/******************************************************************* + _winreg_AbortSystemShutdown + ********************************************************************/ + +WERROR _winreg_AbortSystemShutdown(struct pipes_struct *p, + struct winreg_AbortSystemShutdown *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *abort_shutdown_script = NULL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + int ret = -1; + bool can_shutdown = false; + + abort_shutdown_script = lp_abort_shutdown_script(talloc_tos(), lp_sub); + if (!*abort_shutdown_script) + return WERR_ACCESS_DENIED; + + can_shutdown = security_token_has_privilege( + session_info->security_token, SEC_PRIV_REMOTE_SHUTDOWN); + + /********** BEGIN SeRemoteShutdownPrivilege BLOCK **********/ + + if ( can_shutdown ) + become_root(); + + ret = smbrun(abort_shutdown_script, NULL, NULL); + + if ( can_shutdown ) + unbecome_root(); + + /********** END SeRemoteShutdownPrivilege BLOCK **********/ + + DEBUG(3,("_winreg_AbortSystemShutdown: Running the command `%s' gave %d\n", + abort_shutdown_script, ret)); + + return (ret == 0) ? WERR_OK : WERR_ACCESS_DENIED; +} + +/******************************************************************* + _winreg_RestoreKey + ********************************************************************/ + +WERROR _winreg_RestoreKey(struct pipes_struct *p, + struct winreg_RestoreKey *r) +{ + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !regkey ) { + return WERR_INVALID_HANDLE; + } + return WERR_BAD_PATHNAME; +} + +/******************************************************************* + _winreg_SaveKey + ********************************************************************/ + +WERROR _winreg_SaveKey(struct pipes_struct *p, + struct winreg_SaveKey *r) +{ + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !regkey ) { + return WERR_INVALID_HANDLE; + } + return WERR_BAD_PATHNAME; +} + +/******************************************************************* + _winreg_SaveKeyEx + ********************************************************************/ + +WERROR _winreg_SaveKeyEx(struct pipes_struct *p, + struct winreg_SaveKeyEx *r) +{ + /* fill in your code here if you think this call should + do anything */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************* + _winreg_CreateKey + ********************************************************************/ + +WERROR _winreg_CreateKey(struct pipes_struct *p, + struct winreg_CreateKey *r) +{ + struct registry_key *parent = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + struct registry_key *new_key = NULL; + WERROR result = WERR_OK; + + if ( !parent ) + return WERR_INVALID_HANDLE; + + DEBUG(10, ("_winreg_CreateKey called with parent key '%s' and " + "subkey name '%s'\n", parent->key->name, r->in.name.name)); + + result = reg_createkey(NULL, parent, r->in.name.name, r->in.access_mask, + &new_key, r->out.action_taken); + if (!W_ERROR_IS_OK(result)) { + return result; + } + + if (!create_policy_hnd(p, r->out.new_handle, HTYPE_REGKEY, new_key)) { + TALLOC_FREE(new_key); + return WERR_FILE_NOT_FOUND; + } + + return WERR_OK; +} + +/******************************************************************* + _winreg_SetValue + ********************************************************************/ + +WERROR _winreg_SetValue(struct pipes_struct *p, + struct winreg_SetValue *r) +{ + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + struct registry_value *val = NULL; + + if ( !key ) + return WERR_INVALID_HANDLE; + + DEBUG(8,("_winreg_SetValue: Setting value for [%s:%s]\n", + key->key->name, r->in.name.name)); + + val = talloc_zero(p->mem_ctx, struct registry_value); + if (val == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + val->type = r->in.type; + val->data = data_blob_talloc(p->mem_ctx, r->in.data, r->in.size); + + return reg_setvalue(key, r->in.name.name, val); +} + +/******************************************************************* + _winreg_DeleteKey + ********************************************************************/ + +WERROR _winreg_DeleteKey(struct pipes_struct *p, + struct winreg_DeleteKey *r) +{ + struct registry_key *parent = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !parent ) + return WERR_INVALID_HANDLE; + + return reg_deletekey(parent, r->in.key.name); +} + + +/******************************************************************* + _winreg_DeleteValue + ********************************************************************/ + +WERROR _winreg_DeleteValue(struct pipes_struct *p, + struct winreg_DeleteValue *r) +{ + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + + if ( !key ) + return WERR_INVALID_HANDLE; + + return reg_deletevalue(key, r->in.value.name); +} + +/******************************************************************* + _winreg_GetKeySecurity + ********************************************************************/ + +WERROR _winreg_GetKeySecurity(struct pipes_struct *p, + struct winreg_GetKeySecurity *r) +{ + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + WERROR err = WERR_OK; + struct security_descriptor *secdesc = NULL; + uint8_t *data = NULL; + size_t len = 0; + + if ( !key ) + return WERR_INVALID_HANDLE; + + /* access checks first */ + + if ( !(key->key->access_granted & SEC_STD_READ_CONTROL) ) + return WERR_ACCESS_DENIED; + + err = reg_getkeysecurity(p->mem_ctx, key, &secdesc); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + err = ntstatus_to_werror(marshall_sec_desc(p->mem_ctx, secdesc, + &data, &len)); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + if (len > r->out.sd->size) { + r->out.sd->size = len; + return WERR_INSUFFICIENT_BUFFER; + } + + r->out.sd->size = len; + r->out.sd->len = len; + r->out.sd->data = data; + + return WERR_OK; +} + +/******************************************************************* + _winreg_SetKeySecurity + ********************************************************************/ + +WERROR _winreg_SetKeySecurity(struct pipes_struct *p, + struct winreg_SetKeySecurity *r) +{ + struct registry_key *key = find_regkey_by_hnd(p, + r->in.handle, + HTYPE_REGKEY); + struct security_descriptor *secdesc = NULL; + WERROR err = WERR_OK; + + if ( !key ) + return WERR_INVALID_HANDLE; + + /* access checks first */ + + if ( !(key->key->access_granted & SEC_STD_WRITE_DAC) ) + return WERR_ACCESS_DENIED; + + err = ntstatus_to_werror(unmarshall_sec_desc(p->mem_ctx, r->in.sd->data, + r->in.sd->len, &secdesc)); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + return reg_setkeysecurity(key, secdesc); +} + +/******************************************************************* + _winreg_FlushKey + ********************************************************************/ + +WERROR _winreg_FlushKey(struct pipes_struct *p, + struct winreg_FlushKey *r) +{ + /* I'm just replying OK because there's not a lot + here I see to do i --jerry */ + + return WERR_OK; +} + +/******************************************************************* + _winreg_UnLoadKey + ********************************************************************/ + +WERROR _winreg_UnLoadKey(struct pipes_struct *p, + struct winreg_UnLoadKey *r) +{ + /* fill in your code here if you think this call should + do anything */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************* + _winreg_ReplaceKey + ********************************************************************/ + +WERROR _winreg_ReplaceKey(struct pipes_struct *p, + struct winreg_ReplaceKey *r) +{ + /* fill in your code here if you think this call should + do anything */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************* + _winreg_LoadKey + ********************************************************************/ + +WERROR _winreg_LoadKey(struct pipes_struct *p, + struct winreg_LoadKey *r) +{ + /* fill in your code here if you think this call should + do anything */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************* + _winreg_NotifyChangeKeyValue + ********************************************************************/ + +WERROR _winreg_NotifyChangeKeyValue(struct pipes_struct *p, + struct winreg_NotifyChangeKeyValue *r) +{ + return WERR_NOT_SUPPORTED; +} + +/******************************************************************* + _winreg_QueryMultipleValues + ********************************************************************/ + +WERROR _winreg_QueryMultipleValues(struct pipes_struct *p, + struct winreg_QueryMultipleValues *r) +{ + struct winreg_QueryMultipleValues2 r2; + uint32_t needed = 0; + + r2.in.key_handle = r->in.key_handle; + r2.in.values_in = r->in.values_in; + r2.in.num_values = r->in.num_values; + r2.in.offered = r->in.buffer_size; + r2.in.buffer = r->in.buffer; + r2.out.values_out = r->out.values_out; + r2.out.needed = &needed; + r2.out.buffer = r->out.buffer; + + return _winreg_QueryMultipleValues2(p, &r2); +} + +/******************************************************************* + ********************************************************************/ + +static WERROR construct_multiple_entry(TALLOC_CTX *mem_ctx, + const char *valuename, + uint32_t value_length, + uint32_t offset, + enum winreg_Type type, + struct QueryMultipleValue *r) +{ + r->ve_valuename = talloc_zero(mem_ctx, struct winreg_ValNameBuf); + if (r->ve_valuename == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + r->ve_valuename->name = talloc_strdup(r->ve_valuename, valuename ? valuename : ""); + if (r->ve_valuename->name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + r->ve_valuename->size = strlen_m_term(r->ve_valuename->name)*2; + r->ve_valuelen = value_length; + r->ve_valueptr = offset; + r->ve_type = type; + + return WERR_OK; +} + +/******************************************************************* + _winreg_QueryMultipleValues2 + ********************************************************************/ + +WERROR _winreg_QueryMultipleValues2(struct pipes_struct *p, + struct winreg_QueryMultipleValues2 *r) +{ + struct registry_key *regkey = find_regkey_by_hnd(p, + r->in.key_handle, + HTYPE_REGKEY); + struct registry_value *vals = NULL; + const char **names = NULL; + uint32_t offset = 0, num_vals = 0; + DATA_BLOB result = data_blob_null; + uint32_t i = 0; + WERROR err = WERR_OK; + + if (!regkey) { + return WERR_INVALID_HANDLE; + } + + names = talloc_zero_array(p->mem_ctx, const char *, r->in.num_values); + if (names == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + for (i=0; i < r->in.num_values; i++) { + if (r->in.values_in[i].ve_valuename && + r->in.values_in[i].ve_valuename->name) { + names[i] = talloc_strdup(names, + r->in.values_in[i].ve_valuename->name); + if (names[i] == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + } + + err = reg_querymultiplevalues(p->mem_ctx, regkey, + r->in.num_values, names, + &num_vals, &vals); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + result = data_blob_talloc(p->mem_ctx, NULL, 0); + + for (i=0; i < r->in.num_values; i++) { + const char *valuename = NULL; + + if (vals[i].data.length > 0) { + if (!data_blob_append(p->mem_ctx, &result, + vals[i].data.data, + vals[i].data.length)) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + if (r->in.values_in[i].ve_valuename && + r->in.values_in[i].ve_valuename->name) { + valuename = r->in.values_in[i].ve_valuename->name; + } + + err = construct_multiple_entry(r->out.values_out, + valuename, + vals[i].data.length, + offset, + vals[i].type, + &r->out.values_out[i]); + if (!W_ERROR_IS_OK(err)) { + return err; + } + + offset += vals[i].data.length; + } + + *r->out.needed = result.length; + + if (r->in.num_values != num_vals) { + return WERR_FILE_NOT_FOUND; + } + + if (*r->in.offered >= *r->out.needed) { + if (r->out.buffer) { + memcpy(r->out.buffer, result.data, MIN(result.length, *r->in.offered)); + } + return WERR_OK; + } else { + return WERR_MORE_DATA; + } +} + +/******************************************************************* + _winreg_DeleteKeyEx + ********************************************************************/ + +WERROR _winreg_DeleteKeyEx(struct pipes_struct *p, + struct winreg_DeleteKeyEx *r) +{ + /* fill in your code here if you think this call should + do anything */ + + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_winreg_scompat.c" diff --git a/source3/rpc_server/witness/srv_witness_nt.c b/source3/rpc_server/witness/srv_witness_nt.c new file mode 100644 index 0000000..1d5f720 --- /dev/null +++ b/source3/rpc_server/witness/srv_witness_nt.c @@ -0,0 +1,2465 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2012,2023 Stefan Metzmacher + * Copyright (C) 2015 Guenther Deschner + * Copyright (C) 2018 Samuel Cabrero + * + * 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 "ctdbd_conn.h" +#include "ctdb/protocol/protocol.h" +#include "ctdb_srvids.h" +#include "messages.h" +#include "lib/messages_ctdb.h" +#include "lib/global_contexts.h" +#include "lib/util/util_tdb.h" +#include "lib/util/util_str_escape.h" +#include "source3/include/util_tdb.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "lib/param/param.h" +#include "lib/util/tevent_werror.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/rpc/dcesrv_core.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/auth.h" +#include "librpc/gen_ndr/ndr_witness.h" +#include "librpc/gen_ndr/ndr_witness_scompat.h" +#include "librpc/gen_ndr/ndr_rpcd_witness.h" +#include "rpc_server/rpc_server.h" + +#define SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION 0x01 + +struct swn_service_interface; +struct swn_service_registration; +struct swn_service_async_notify_state; + +struct swn_service_globals { + struct dcesrv_context *dce_ctx; + struct db_context *dce_conn_register; + + const char *server_global_name; + uint32_t local_vnn; + + struct { + bool valid; + uint64_t generation; + struct swn_service_interface *list; + } interfaces; + + struct { + uint32_t unused_timeout_secs; + struct swn_service_registration *list; + struct db_context *db; + } registrations; +}; + +struct swn_service_interface { + struct swn_service_interface *prev, *next; + + const char *group_name; + struct samba_sockaddr addr; + enum witness_interfaceInfo_state state; + bool local_iface; + uint32_t current_vnn; + uint64_t change_generation; + uint64_t check_generation; +}; + +struct swn_service_registration { + struct swn_service_registration *prev, *next; + + struct swn_service_globals *swn; + + struct { + struct tevent_context *ev_ctx; + struct messaging_context *msg_ctx; + struct tevent_req *subreq; + } msg; + + struct { + struct policy_handle handle; + void *ptr; + } key; + + struct { + enum witness_version version; + const char *computer_name; + } client; + + const char *net_name; + const char *share_name; + struct samba_sockaddr ip_address; + + struct { + bool triggered; + struct witness_notifyResponse *response; + WERROR result; + } forced_response; + + struct { + bool triggered; + /* + * We only do ip based RESOURCE_CHANGE notifications for now + * and it means we do just one notification at a time + * and don't need to queue pending notifications. + */ + enum witness_interfaceInfo_state last_ip_state; + } change_notification; + + struct { + bool triggered; + uint32_t new_node; + struct samba_sockaddr new_ip; + } move_notification; + + struct { + bool required; + bool triggered; + uint32_t new_node; + struct samba_sockaddr new_ip; + } share_notification; + + struct { + bool required; + bool triggered; + /* + * TODO: find how this works on windows and implement + * Windows Server 2022 as client doesn't use it... + */ + } ip_notification; + + struct { + struct timeval create_time; + struct timeval last_time; + uint32_t unused_timeout_secs; + struct timeval expire_time; + struct tevent_timer *timer; + } usage; + + struct { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + struct tevent_timer *timer; + } forced_unregister; + + struct { + uint32_t timeout_secs; + struct tevent_queue *queue; + struct swn_service_async_notify_state *list; + } async_notify; +}; + +static struct swn_service_globals *swn_globals = NULL; + +static int swn_service_globals_destructor(struct swn_service_globals *swn) +{ + SMB_ASSERT(swn == swn_globals); + swn_globals = NULL; + + while (swn->registrations.list != NULL) { + /* + * NO TALLOC_FREE() because of DLIST_REMOVE() + * in swn_service_registration_destructor() + */ + talloc_free(swn->registrations.list); + } + + return 0; +} + +static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state); + +static int swn_service_registration_destructor(struct swn_service_registration *reg) +{ + struct swn_service_globals *swn = reg->swn; + struct GUID_txt_buf key_buf; + const char *key_str = GUID_buf_string(®->key.handle.uuid, &key_buf); + DATA_BLOB key_blob = data_blob_string_const(key_str); + TDB_DATA key = make_tdb_data(key_blob.data, key_blob.length); + NTSTATUS status; + + tevent_queue_stop(reg->async_notify.queue); + while (reg->async_notify.list != NULL) { + swn_service_async_notify_reg_destroyed(reg->async_notify.list); + } + + status = dbwrap_delete(reg->swn->registrations.db, key); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("rpcd_witness_registration: key '%s' delete - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + } else if (DEBUGLVL(DBGLVL_DEBUG)) { + DBG_DEBUG("rpcd_witness_registration: key '%s' deleted\n", + tdb_data_dbg(key)); + } + + DLIST_REMOVE(swn->registrations.list, reg); + reg->swn = NULL; + + /* + * make sure to drop the policy/context handle from + * the assoc_group + */ + TALLOC_FREE(reg->key.ptr); + + return 0; +} + +static void swn_service_registration_update_usage(struct swn_service_registration *reg, + struct timeval now) +{ + uint64_t expire_timeout_secs = 0; + uint64_t max_expire_timeout_secs = TIME_T_MAX; + + reg->usage.last_time = now; + + if (max_expire_timeout_secs > reg->usage.last_time.tv_sec) { + max_expire_timeout_secs -= reg->usage.last_time.tv_sec; + } else { + /* + * This should never happen unless + * a 32 bit system hits its limit + */ + max_expire_timeout_secs = 0; + } + + if (tevent_queue_length(reg->async_notify.queue) != 0) { + expire_timeout_secs += reg->async_notify.timeout_secs; + } + + expire_timeout_secs += reg->usage.unused_timeout_secs; + expire_timeout_secs = MIN(expire_timeout_secs, max_expire_timeout_secs); + + reg->usage.expire_time = timeval_add(®->usage.last_time, + expire_timeout_secs, 0); + + if (expire_timeout_secs == 0) { + /* + * No timer needed, witness v1 + * or max_expire_timeout_secs = 0 + */ + TALLOC_FREE(reg->usage.timer); + } + + if (reg->usage.timer == NULL) { + /* no timer to update */ + reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, }; + return; + } + + tevent_update_timer(reg->usage.timer, reg->usage.expire_time); +} + +static void swn_service_registration_unused(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct swn_service_registration *reg = + talloc_get_type_abort(private_data, + struct swn_service_registration); + + reg->usage.timer = NULL; + + TALLOC_FREE(reg); +} + +static void swn_service_registration_force_unregister(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct swn_service_registration *reg = + talloc_get_type_abort(private_data, + struct swn_service_registration); + + reg->forced_unregister.timer = NULL; + + TALLOC_FREE(reg); +} + +static int swn_service_ctdb_ipreallocated(struct tevent_context *ev, + uint32_t src_vnn, uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, size_t msglen, + void *private_data); + +static NTSTATUS swn_service_init_globals(struct dcesrv_context *dce_ctx) +{ + struct swn_service_globals *swn = NULL; + char *global_path = NULL; + const char *realm = NULL; + const char *nbname = NULL; + int ret; + bool ok; + + if (swn_globals != NULL) { + SMB_ASSERT(swn_globals->dce_ctx == dce_ctx); + return NT_STATUS_OK; + } + + swn = talloc_zero(dce_ctx, struct swn_service_globals); + if (swn == NULL) { + return NT_STATUS_NO_MEMORY; + } + swn->dce_ctx = dce_ctx; + + swn->dce_conn_register = db_open_rbt(swn); + if (swn->dce_conn_register == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + + /* + * This contains secret information like client keys! + */ + global_path = lock_path(swn, "rpcd_witness_registration.tdb"); + if (global_path == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + + swn->registrations.db = db_open(swn, global_path, + 0, /* hash_size */ + TDB_DEFAULT | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH, + O_RDWR | O_CREAT, 0600, + DBWRAP_LOCK_ORDER_1, + DBWRAP_FLAG_NONE); + if (swn->registrations.db == NULL) { + NTSTATUS status; + + status = map_nt_error_from_unix_common(errno); + TALLOC_FREE(swn); + + return status; + } + TALLOC_FREE(global_path); + + nbname = lpcfg_netbios_name(dce_ctx->lp_ctx); + realm = lpcfg_realm(dce_ctx->lp_ctx); + if (realm != NULL && realm[0] != '\0') { + char *name = NULL; + + name = talloc_asprintf(swn, "%s.%s", nbname, realm); + if (name == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + ok = strlower_m(name); + if (!ok) { + TALLOC_FREE(swn); + return NT_STATUS_INTERNAL_ERROR; + } + swn->server_global_name = name; + } else { + swn->server_global_name = talloc_strdup(swn, nbname); + if (swn->server_global_name == NULL) { + TALLOC_FREE(swn); + return NT_STATUS_NO_MEMORY; + } + } + + swn->local_vnn = get_my_vnn(); + + ret = register_with_ctdbd(messaging_ctdb_connection(), + CTDB_SRVID_IPREALLOCATED, + swn_service_ctdb_ipreallocated, + swn); + if (ret != 0) { + TALLOC_FREE(swn); + return NT_STATUS_INTERNAL_ERROR; + } + + swn->registrations.unused_timeout_secs = 30; + + talloc_set_destructor(swn, swn_service_globals_destructor); + swn_globals = swn; + return NT_STATUS_OK; +} + +static struct swn_service_interface *swn_service_interface_by_addr( + struct swn_service_globals *swn, + const struct samba_sockaddr *addr) +{ + struct swn_service_interface *iface = NULL; + + for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) { + bool ok; + + ok = sockaddr_equal(&iface->addr.u.sa, &addr->u.sa); + if (ok) { + return iface; + } + } + + return NULL; +} + +static void swn_service_interface_changed(struct swn_service_globals *swn, + struct swn_service_interface *iface) +{ + struct swn_service_registration *reg = NULL; + char addr[INET6_ADDRSTRLEN] = { 0, }; + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + DBG_NOTICE("addr[%s] state[%u] local_iface[%u] " + "current_vnn[%"PRIu32"] generation[%"PRIu64"][%"PRIu64"]\n", + addr, + iface->state, + iface->local_iface, + iface->current_vnn, + iface->change_generation, + iface->check_generation); + + for (reg = swn->registrations.list; reg != NULL; reg = reg->next) { + bool match; + + /* + * We only check the ip address, + * we do not make real use of the group name. + */ + + match = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!match) { + continue; + } + + if (reg->change_notification.last_ip_state + != WITNESS_STATE_UNAVAILABLE) + { + /* + * Remember the current state unless we already + * hit WITNESS_STATE_UNAVAILAVLE before we notified + * the client + */ + reg->change_notification.last_ip_state = iface->state; + } + + reg->change_notification.triggered = true; + + tevent_queue_start(reg->async_notify.queue); + } + + return; +} + +static NTSTATUS swn_service_add_or_update_interface(struct swn_service_globals *swn, + const char *group_name, + const struct samba_sockaddr *addr, + enum witness_interfaceInfo_state state, + bool local_iface, + uint32_t current_vnn) +{ + struct swn_service_interface *iface = NULL; + bool changed = false; + bool force_unavailable = false; + bool filter; + + if (addr->u.sa.sa_family != AF_INET && + addr->u.sa.sa_family != AF_INET6) + { + /* + * We only support ipv4 and ipv6 + */ + return NT_STATUS_OK; + } + + filter = is_loopback_addr(&addr->u.sa); + if (filter) { + return NT_STATUS_OK; + } + filter = is_linklocal_addr(&addr->u.ss); + if (filter) { + return NT_STATUS_OK; + } + + for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) { + bool match; + + match = strequal(group_name, iface->group_name); + if (!match) { + continue; + } + + match = sockaddr_equal(&addr->u.sa, &iface->addr.u.sa); + if (!match) { + continue; + } + + break; + } + + if (iface == NULL) { + iface = talloc_zero(swn, struct swn_service_interface); + if (iface == NULL) { + return NT_STATUS_NO_MEMORY; + } + + iface->group_name = talloc_strdup(iface, group_name); + if (iface->group_name == NULL) { + TALLOC_FREE(iface); + return NT_STATUS_NO_MEMORY; + } + + iface->addr = *addr; + + iface->state = WITNESS_STATE_UNKNOWN; + iface->current_vnn = NONCLUSTER_VNN; + DLIST_ADD_END(swn->interfaces.list, iface); + + iface->change_generation = swn->interfaces.generation; + } + + if (iface->state != state) { + changed = true; + iface->state = state; + } + + if (iface->current_vnn != current_vnn) { + changed = true; + if (iface->current_vnn != NONCLUSTER_VNN) { + force_unavailable = true; + } + iface->current_vnn = current_vnn; + } + + if (iface->local_iface != local_iface) { + changed = true; + force_unavailable = true; + iface->local_iface = local_iface; + } + + iface->check_generation = swn->interfaces.generation; + + if (!changed) { + return NT_STATUS_OK; + } + + iface->change_generation = swn->interfaces.generation; + + if (force_unavailable) { + iface->state = WITNESS_STATE_UNAVAILABLE; + } + + swn_service_interface_changed(swn, iface); + + if (force_unavailable) { + iface->state = state; + } + + return NT_STATUS_OK; +}; + +static int swn_service_ctdb_all_ip_cb(uint32_t total_ip_count, + const struct sockaddr_storage *ip, + uint32_t pinned_vnn, + uint32_t current_vnn, + void *private_data) +{ + struct swn_service_globals *swn = + talloc_get_type_abort(private_data, + struct swn_service_globals); + enum witness_interfaceInfo_state state = WITNESS_STATE_UNKNOWN; + struct samba_sockaddr addr = { + .u = { + .ss = *ip, + }, + }; + NTSTATUS status; + bool local_iface = false; + + SMB_ASSERT(swn->local_vnn != NONCLUSTER_VNN); + + if (current_vnn == NONCLUSTER_VNN) { + /* + * No node hosts this address + */ + state = WITNESS_STATE_UNAVAILABLE; + } else { + state = WITNESS_STATE_AVAILABLE; + } + + if (current_vnn == swn->local_vnn || pinned_vnn == swn->local_vnn) { + local_iface = true; + } + + status = swn_service_add_or_update_interface(swn, + swn->server_global_name, + &addr, + state, + local_iface, + current_vnn); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("swn_service_add_or_update_interface() failed: %s\n", + nt_errstr(status)); + return map_errno_from_nt_status(status); + } + + return 0; +} + +static NTSTATUS swn_service_reload_interfaces(struct dcesrv_context *dce_ctx) +{ + struct swn_service_interface *iface = NULL; + struct swn_service_interface *next = NULL; + bool include_node_ips = false; + bool include_public_ips = true; + int ret; + NTSTATUS status; + + status = swn_service_init_globals(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (swn_globals->interfaces.valid) { + return NT_STATUS_OK; + } + + swn_globals->interfaces.generation += 1; + + include_node_ips = lpcfg_parm_bool(dce_ctx->lp_ctx, + NULL, + "rpcd witness", + "include node ips", + include_node_ips); + include_public_ips = lpcfg_parm_bool(dce_ctx->lp_ctx, + NULL, + "rpcd witness", + "include public ips", + include_public_ips); + + ret = ctdbd_all_ip_foreach(messaging_ctdb_connection(), + include_node_ips, + include_public_ips, + swn_service_ctdb_all_ip_cb, + swn_globals); + if (ret != 0) { + return NT_STATUS_INTERNAL_ERROR; + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = next) { + next = iface->next; + + if (iface->check_generation == swn_globals->interfaces.generation) { + continue; + } + + status = swn_service_add_or_update_interface(swn_globals, + iface->group_name, + &iface->addr, + WITNESS_STATE_UNAVAILABLE, + iface->local_iface, + iface->current_vnn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DLIST_REMOVE(swn_globals->interfaces.list, iface); + TALLOC_FREE(iface); + } + + /* CTDB_SRVID_IPREALLOCATED is still registered */ + + swn_globals->interfaces.valid = true; + return NT_STATUS_OK; +} + +static int swn_service_ctdb_ipreallocated(struct tevent_context *ev, + uint32_t src_vnn, uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, size_t msglen, + void *private_data) +{ + struct swn_service_globals *swn = + talloc_get_type_abort(private_data, + struct swn_service_globals); + NTSTATUS status; + + DBG_DEBUG("PID[%d] swn[%p] IPREALLOCATED\n", getpid(), swn); + + swn->interfaces.valid = false; + status = swn_service_reload_interfaces(swn->dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + swn->interfaces.valid = false; + return 0; + } + + return 0; +} + +struct swn_dcesrv_connection { + struct db_context *rbt; + struct dcesrv_connection *conn; + struct samba_sockaddr cli_addr; + struct samba_sockaddr srv_addr; + char addr[INET6_ADDRSTRLEN]; +}; + +static int swn_dcesrv_connection_release_ip(struct tevent_context *ev, + uint32_t src_vnn, + uint32_t dst_vnn, + uint64_t dst_srvid, + const uint8_t *msg, + size_t msglen, + void *private_data) +{ + struct swn_dcesrv_connection *sc = + talloc_get_type_abort(private_data, + struct swn_dcesrv_connection); + struct dcesrv_connection *conn = sc->conn; + const char *ip = NULL; + const char *addr = sc->addr; + const char *p = addr; + + if (conn->terminate != NULL) { + /* avoid recursion */ + return 0; + } + + if (msglen == 0) { + return 0; + } + if (msg[msglen-1] != '\0') { + return 0; + } + + ip = (const char *)msg; + + if (strncmp("::ffff:", addr, 7) == 0) { + p = addr + 7; + } + + DBG_DEBUG("Got release IP message for %s, our address is %s\n", ip, p); + + if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) { + DBG_NOTICE("Got release IP message for our IP %s - exiting immediately\n", + ip); + talloc_free(sc); + dcesrv_terminate_connection(conn, "CTDB_SRVID_RELEASE_IP"); + return EADDRNOTAVAIL; + } + + return 0; + +} + +static int swn_dcesrv_connection_destructor(struct swn_dcesrv_connection *sc) +{ + struct ctdbd_connection *cconn = messaging_ctdb_connection(); + struct dcesrv_connection *conn = sc->conn; + uintptr_t conn_ptr = (uintptr_t)conn; + NTSTATUS status; + TDB_DATA key; + + key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr)); + + status = dbwrap_delete(sc->rbt, key); + SMB_ASSERT(NT_STATUS_IS_OK(status)); + + if (cconn == NULL) { + return 0; + } + + ctdbd_unregister_ips(cconn, + &sc->srv_addr.u.ss, + &sc->cli_addr.u.ss, + swn_dcesrv_connection_release_ip, + sc); + + return 0; +} + +static NTSTATUS dcesrv_interface_witness_register_ips(struct dcesrv_connection *conn) +{ + struct ctdbd_connection *cconn = messaging_ctdb_connection(); + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct tsocket_address *client_address = + dcesrv_connection_get_remote_address(conn); + const struct tsocket_address *server_address = + dcesrv_connection_get_local_address(conn); + NTSTATUS status; + uintptr_t conn_ptr = (uintptr_t)conn; + struct swn_dcesrv_connection *sc = NULL; + uintptr_t sc_ptr; + const char *addr = NULL; + TDB_DATA key; + TDB_DATA val; + bool exists; + int ret; + + status = swn_service_init_globals(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("swn_service_init_globals() failed: %s\n", + nt_errstr(status)); + return status; + } + + key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr)); + + exists = dbwrap_exists(swn_globals->dce_conn_register, key); + if (exists) { + /* Already registered */ + return NT_STATUS_OK; + } + + sc = talloc_zero(conn, struct swn_dcesrv_connection); + if (sc == NULL) { + return NT_STATUS_NO_MEMORY; + } + sc->rbt = swn_globals->dce_conn_register; + sc->conn = conn; + + if (tsocket_address_is_inet(client_address, "ip")) { + ssize_t sret; + + sret = tsocket_address_bsd_sockaddr(client_address, + &sc->cli_addr.u.sa, + sizeof(sc->cli_addr.u.ss)); + if (sret == -1) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + sc->cli_addr.sa_socklen = sret; + } + + if (tsocket_address_is_inet(server_address, "ip")) { + ssize_t sret; + + sret = tsocket_address_bsd_sockaddr(server_address, + &sc->srv_addr.u.sa, + sizeof(sc->srv_addr.u.ss)); + if (sret == -1) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + sc->srv_addr.sa_socklen = sret; + } + + addr = print_sockaddr(sc->addr, sizeof(sc->addr), &sc->srv_addr.u.ss); + if (addr == NULL) { + TALLOC_FREE(sc); + return NT_STATUS_NO_MEMORY; + } + + sc_ptr = (uintptr_t)sc; + val = make_tdb_data((uint8_t *)&sc_ptr, sizeof(sc_ptr)); + + status = dbwrap_store(sc->rbt, key, val, TDB_INSERT); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sc); + return status; + } + + talloc_set_destructor(sc, swn_dcesrv_connection_destructor); + + + ret = ctdbd_register_ips(cconn, + &sc->srv_addr.u.ss, + &sc->cli_addr.u.ss, + swn_dcesrv_connection_release_ip, + sc); + if (ret != 0) { + TALLOC_FREE(sc); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +#define DCESRV_INTERFACE_WITNESS_BIND(context, iface) \ + dcesrv_interface_witness_bind(context, iface) +static NTSTATUS dcesrv_interface_witness_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + NTSTATUS status; + + status = dcesrv_interface_witness_register_ips(context->conn); + if (!NT_STATUS_IS_OK(status)) { + /* + * This is not really critical, so we just print + * as warning... + */ + DBG_WARNING("dcesrv_interface_witness_register_ips() failed: %s\n", + nt_errstr(status)); + } + + /* + * [MS-SWN] Section 7. If the authentication level is not + * integrity or privacy level, Windows servers will fail the call + * with access denied + */ + return dcesrv_interface_bind_require_integrity(context, iface); +} + +/**************************************************************** + _witness_GetInterfaceList +****************************************************************/ + +WERROR _witness_GetInterfaceList(struct pipes_struct *p, + struct witness_GetInterfaceList *r) +{ + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_interface *iface = NULL; + struct witness_interfaceList *list = NULL; + size_t num_interfaces = 0; + NTSTATUS status; + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) { + num_interfaces += 1; + } + + list = talloc_zero(p->mem_ctx, struct witness_interfaceList); + if (list == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + list->interfaces = talloc_zero_array(list, + struct witness_interfaceInfo, + num_interfaces); + if (list->interfaces == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) { + struct witness_interfaceInfo *info = + &list->interfaces[list->num_interfaces++]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_INFO_IPv4_VALID; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_INFO_IPv6_VALID; + ipv6 = addr; + } + + if (!iface->local_iface) { + /* + * If it's not a local interface + * it is able to serve as + * witness server + */ + flags |= WITNESS_INFO_WITNESS_IF; + } + + info->group_name = talloc_strdup(list, iface->group_name); + if (info->group_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->version = WITNESS_V2; /* WitnessServiceVersion; */ + info->state = iface->state; + info->ipv4 = talloc_strdup(list, ipv4); + if (info->ipv4 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (info->ipv6 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + info->flags = flags; + } + + *r->out.interface_list = list; + return WERR_OK; +} + +static bool swn_server_registration_message_filter(struct messaging_rec *rec, void *private_data) +{ + struct swn_service_registration *reg = NULL; + struct policy_handle context_handle; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + bool match; + + if (rec->msg_type != MSG_RPCD_WITNESS_REGISTRATION_UPDATE) { + return false; + } + + if (rec->num_fds != 0) { + return false; + } + + if (rec->buf.length < 20) { + return false; + } + + reg = talloc_get_type_abort(private_data, struct swn_service_registration); + + blob = data_blob_const(rec->buf.data, 20); + ndr_err = ndr_pull_struct_blob_all_noalloc(&blob, &context_handle, + (ndr_pull_flags_fn_t)ndr_pull_policy_handle); + SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + + match = ndr_policy_handle_equal(&context_handle, ®->key.handle); + if (!match) { + return false; + } + + return true; +} + +static void swn_server_registration_client_move_to_node( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_node *move) +{ + reg->move_notification.triggered = true; + reg->move_notification.new_node = move->new_node; + reg->move_notification.new_ip = (struct samba_sockaddr) { + .sa_socklen = 0, + }; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_client_move_to_ip( + struct swn_service_registration *reg, + const char *new_ip_str) +{ + struct samba_sockaddr new_ip = { + .sa_socklen = 0, + }; + bool ok; + + ok = is_ipaddress(new_ip_str); + if (!ok) { + return; + } + ok = interpret_string_addr(&new_ip.u.ss, + new_ip_str, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + return; + } + + reg->move_notification.triggered = true; + reg->move_notification.new_node = NONCLUSTER_VNN; + reg->move_notification.new_ip = new_ip; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_client_move_to_ipv4( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv4 *move) +{ + swn_server_registration_client_move_to_ip(reg, move->new_ipv4); +} + +static void swn_server_registration_client_move_to_ipv6( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv6 *move) +{ + swn_server_registration_client_move_to_ip(reg, move->new_ipv6); +} + +static void swn_server_registration_share_move_to_node( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_node *move) +{ + if (!reg->share_notification.required) { + return; + } + + reg->share_notification.triggered = true; + reg->share_notification.new_node = move->new_node; + reg->share_notification.new_ip = (struct samba_sockaddr) { + .sa_socklen = 0, + }; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_share_move_to_ip( + struct swn_service_registration *reg, + const char *new_ip_str) +{ + struct samba_sockaddr new_ip = { + .sa_socklen = 0, + }; + bool ok; + + ok = is_ipaddress(new_ip_str); + if (!ok) { + return; + } + ok = interpret_string_addr(&new_ip.u.ss, + new_ip_str, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + return; + } + + if (!reg->share_notification.required) { + return; + } + + reg->share_notification.triggered = true; + reg->share_notification.new_node = NONCLUSTER_VNN; + reg->share_notification.new_ip = new_ip; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_share_move_to_ipv4( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv4 *move) +{ + swn_server_registration_share_move_to_ip(reg, move->new_ipv4); +} + +static void swn_server_registration_share_move_to_ipv6( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_move_to_ipv6 *move) +{ + swn_server_registration_share_move_to_ip(reg, move->new_ipv6); +} + +static void swn_server_registration_force_response( + struct swn_service_registration *reg, + struct rpcd_witness_registration_update_force_response *response) +{ + reg->forced_response.triggered = true; + reg->forced_response.response = talloc_move(reg, &response->response); + reg->forced_response.result = response->result; + + tevent_queue_start(reg->async_notify.queue); +} + +static void swn_server_registration_message_done(struct tevent_req *subreq) +{ + struct swn_service_registration *reg = + tevent_req_callback_data(subreq, + struct swn_service_registration); + TALLOC_CTX *frame = talloc_stackframe(); + struct messaging_rec *rec = NULL; + struct rpcd_witness_registration_updateB update_blob; + enum ndr_err_code ndr_err; + NTSTATUS status; + int ret; + + SMB_ASSERT(reg->msg.subreq == subreq); + reg->msg.subreq = NULL; + + ret = messaging_filtered_read_recv(subreq, frame, &rec); + TALLOC_FREE(subreq); + if (ret != 0) { + status = map_nt_error_from_unix_common(ret); + DBG_ERR("messaging_filtered_read_recv() - %s\n", + nt_errstr(status)); + goto wait_for_next; + } + + DBG_DEBUG("MSG_RPCD_WITNESS_REGISTRATION_UPDATE: received...\n"); + + ndr_err = ndr_pull_struct_blob(&rec->buf, frame, &update_blob, + (ndr_pull_flags_fn_t)ndr_pull_rpcd_witness_registration_updateB); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_ERR("ndr_pull_struct_blob - %s\n", nt_errstr(status)); + goto wait_for_next; + } + + if (DEBUGLVL(DBGLVL_DEBUG)) { + NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update_blob); + } + + switch (update_blob.type) { + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE: + swn_server_registration_client_move_to_node(reg, + &update_blob.update.client_move_to_node); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4: + swn_server_registration_client_move_to_ipv4(reg, + &update_blob.update.client_move_to_ipv4); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6: + swn_server_registration_client_move_to_ipv6(reg, + &update_blob.update.client_move_to_ipv6); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE: + swn_server_registration_share_move_to_node(reg, + &update_blob.update.share_move_to_node); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4: + swn_server_registration_share_move_to_ipv4(reg, + &update_blob.update.share_move_to_ipv4); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6: + swn_server_registration_share_move_to_ipv6(reg, + &update_blob.update.share_move_to_ipv6); + break; + case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER: + TALLOC_FREE(reg); + TALLOC_FREE(frame); + return; + case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE: + swn_server_registration_force_response(reg, + &update_blob.update.force_response); + break; + } + +wait_for_next: + TALLOC_FREE(frame); + reg->msg.subreq = messaging_filtered_read_send(reg, + reg->msg.ev_ctx, + reg->msg.msg_ctx, + swn_server_registration_message_filter, + reg); + if (reg->msg.subreq == NULL) { + DBG_ERR("messaging_filtered_read_send() failed\n"); + return; + } + tevent_req_set_callback(reg->msg.subreq, + swn_server_registration_message_done, + reg); +} + +static WERROR swn_server_registration_create(struct swn_service_globals *swn, + struct pipes_struct *p, + const struct witness_RegisterEx *r, + const struct swn_service_interface *iface, + struct swn_service_registration **preg) +{ + struct swn_service_registration *reg = NULL; + const struct tsocket_address *client_address = + dcesrv_connection_get_remote_address(p->dce_call->conn); + const struct tsocket_address *server_address = + dcesrv_connection_get_local_address(p->dce_call->conn); + struct auth_session_info *session_info = + dcesrv_call_session_info(p->dce_call); + struct rpcd_witness_registration rg = { .version = 0, }; + enum ndr_err_code ndr_err; + NTSTATUS status; + struct GUID_txt_buf key_buf = {}; + const char *key_str = NULL; + DATA_BLOB key_blob = { .length = 0, }; + TDB_DATA key = { .dsize = 0, }; + DATA_BLOB val_blob = { .length = 0, }; + TDB_DATA val = { .dsize = 0, }; + + /* + * [MS-SWN] 3.1.4.5 + * The server MUST create a WitnessRegistration entry as follows and + * insert it into the WitnessRegistrationList. + */ + reg = talloc_zero(p->mem_ctx, struct swn_service_registration); + if (reg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + reg->swn = swn; + + reg->client.version = r->in.version; + + /* + * all other string values are checked against + * well known expected values. + * + * So we better escape the client_computer_name + * if it contains strange things... + */ + reg->client.computer_name = log_escape(reg, r->in.client_computer_name); + if (reg->client.computer_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + + reg->net_name = talloc_strdup(reg, r->in.net_name); + if (reg->net_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + + reg->ip_address = iface->addr; + + if (r->in.share_name != NULL) { + reg->share_name = talloc_strdup(reg, r->in.share_name); + if (reg->share_name == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + reg->share_notification.required = true; + } + + reg->async_notify.timeout_secs = r->in.timeout; + reg->async_notify.queue = tevent_queue_create(reg, "async_notify"); + if (reg->async_notify.queue == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_queue_stop(reg->async_notify.queue); + + reg->ip_notification.required = (r->in.flags & + WITNESS_REGISTER_IP_NOTIFICATION); + + reg->usage.create_time = p->dce_call->time; + reg->usage.unused_timeout_secs = + swn_globals->registrations.unused_timeout_secs; + /* + * swn_service_registration_update_usage() below + * will update the timer to its real expire time! + */ + reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, }; + reg->usage.timer = tevent_add_timer(p->dce_call->event_ctx, + reg, + reg->usage.expire_time, + swn_service_registration_unused, + reg); + if (reg->usage.timer == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + swn_service_registration_update_usage(reg, reg->usage.create_time); + + reg->msg.ev_ctx = p->dce_call->event_ctx; + reg->msg.msg_ctx = p->msg_ctx; + reg->msg.subreq = messaging_filtered_read_send(reg, + reg->msg.ev_ctx, + reg->msg.msg_ctx, + swn_server_registration_message_filter, + reg); + if (reg->msg.subreq == NULL) { + TALLOC_FREE(reg); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_req_set_callback(reg->msg.subreq, + swn_server_registration_message_done, + reg); + + reg->key.ptr = create_policy_hnd(p, ®->key.handle, + SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION, + reg); + if (reg->key.ptr == NULL) { + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + + DLIST_ADD_END(swn_globals->registrations.list, reg); + talloc_set_destructor(reg, swn_service_registration_destructor); + + key_str = GUID_buf_string(®->key.handle.uuid, &key_buf); + key_blob = data_blob_string_const(key_str); + key = make_tdb_data(key_blob.data, key_blob.length); + + rg = (struct rpcd_witness_registration) { + .version = r->in.version, + .net_name = r->in.net_name, + .share_name = r->in.share_name, + .ip_address = r->in.ip_address, + .client_computer_name = reg->client.computer_name, + .flags = r->in.flags, + .timeout = r->in.timeout, + .context_handle = reg->key.handle, + .server_id = messaging_server_id(p->msg_ctx), + .account_name = session_info->info->account_name, + .domain_name = session_info->info->domain_name, + .account_sid = session_info->security_token->sids[PRIMARY_USER_SID_INDEX], + .local_address = tsocket_address_string(server_address, p->mem_ctx), + .remote_address = tsocket_address_string(client_address, p->mem_ctx), + .registration_time = timeval_to_nttime(&p->dce_call->time), + }; + + ndr_err = ndr_push_struct_blob(&val_blob, p->mem_ctx, &rg, + (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("rpcd_witness_registration: key '%s' ndr_push - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + val = make_tdb_data(val_blob.data, val_blob.length); + + status = dbwrap_store(reg->swn->registrations.db, key, val, TDB_INSERT); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("rpcd_witness_registration: key '%s' store - %s\n", + tdb_data_dbg(key), + nt_errstr(status)); + TALLOC_FREE(reg); + return WERR_NO_SYSTEM_RESOURCES; + } + + if (DEBUGLVL(DBGLVL_DEBUG)) { + DBG_DEBUG("rpcd_witness_registration: key '%s' stored\n", + tdb_data_dbg(key)); + NDR_PRINT_DEBUG(rpcd_witness_registration, &rg); + } + + *preg = reg; + return WERR_OK; +} + +static WERROR swn_server_check_net_name(struct swn_service_globals *swn, + const char *net_name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *stripped_net_name = NULL; + char *p = NULL; + bool ok; + + ok = strequal(swn->server_global_name, net_name); + if (ok) { + TALLOC_FREE(frame); + return WERR_OK; + } + + stripped_net_name = talloc_strdup(frame, net_name); + if (stripped_net_name == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + + p = strchr(stripped_net_name, '.'); + if (p != NULL) { + *p = '\0'; + } + + ok = is_myname(stripped_net_name); + if (ok) { + TALLOC_FREE(frame); + return WERR_OK; + } + + TALLOC_FREE(frame); + return WERR_INVALID_PARAMETER; +} + +/**************************************************************** + _witness_Register +****************************************************************/ + +WERROR _witness_Register(struct pipes_struct *p, + struct witness_Register *r) +{ + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_registration *reg = NULL; + struct samba_sockaddr addr = { .sa_socklen = 0, }; + struct swn_service_interface *iface = NULL; + const struct witness_RegisterEx rex = { + .in = { + .version = r->in.version, + .net_name = r->in.net_name, + .ip_address = r->in.ip_address, + .client_computer_name = r->in.client_computer_name, + }, + }; + NTSTATUS status; + WERROR werr; + bool ok; + + /* + * [MS-SWN] 3.1.4.2 + * If the Version field of the request is not 0x00010001, the server + * MUST stop processing the request and return the error code + * ERROR_REVISION_MISMATCH + */ + if (r->in.version != WITNESS_V1) { + return WERR_REVISION_MISMATCH; + } + + /* + * [MS-SWN] 3.1.4.2 + * If NetName, IpAddress or ClientComputerName is NULL, the server + * MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + if (r->in.net_name == NULL || + r->in.ip_address == NULL || + r->in.client_computer_name == NULL) + { + return WERR_INVALID_PARAMETER; + } + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * [MS-SWN] 3.1.4.2 + * If the NetName parameter is not equal to ServerGlobalName, the + * server MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + werr = swn_server_check_net_name(swn_globals, r->in.net_name); + if (!W_ERROR_IS_OK(werr)) { + DBG_INFO("Invalid net_name[%s], " + "server_global_name[%s]: %s\n", + log_escape(p->mem_ctx, r->in.net_name), + swn_globals->server_global_name, + win_errstr(werr)); + return werr; + } + + /* + * [MS-SWN] 3.1.4.2 + * The server MUST enumerate the shares by calling NetrShareEnum as + * specified in [MS-SRVS] section 3.1.4.8. In the enumerated list, + * if any of the shares has shi*_type set to STYPE_CLUSTER_SOFS, as + * specified in [MS-SRVS] section 2.2.2.4, the server MUST search for + * an Interface in InterfaceList, where Interface.IPv4Address or + * Interface.IPv6Address matches the IpAddress parameter based on its + * format. If no matching entry is found, the server MUST fail the + * request and return the error code ERROR_INVALID_STATE. + * + * After clarifying this point with dochelp: + * A server only sets the CLUSTER_SOFS, CLUSTER_FS, or CLUSTER_DFS bit + * flags in NetrShareEnum when the call is local and never will be set + * by remote calls. This point only serves the purpose of identifying + * the SOFS shares. + * The server returns the error code ERROR_INVALID_STATE if the share + * enumeration of SMB share resources fails with any error other than + * STATUS_SUCCESS. It’s not the absence of SOFS shares, or just the + * call to ShareEnum. When the server enumerates the shares by calling + * NetrShareEnum locally, it tries to filter out only shares with + * STYPE_CLUSTER_SOFS. The scope of 'If no matching entry is found' + * is broader. Even if shares have STYPE_CLUSTER_SOFS, but no match + * could be found with the IpAddress, ERROR_INVALID_STATE will be + * returned too. + * + * In a CTDB cluster, all shares in the clustered filesystem are + * scale-out. We can skip this check and proceed to find the matching + * IP address. + */ + ok = is_ipaddress(r->in.ip_address); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + ok = interpret_string_addr(&addr.u.ss, + r->in.ip_address, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + iface = swn_service_interface_by_addr(swn_globals, &addr); + if (iface == NULL) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + + werr = swn_server_registration_create(swn_globals, p, &rex, iface, ®); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + *r->out.context_handle = reg->key.handle; + return WERR_OK; +} + + +/**************************************************************** + _witness_UnRegister +****************************************************************/ + +WERROR _witness_UnRegister(struct pipes_struct *p, + struct witness_UnRegister *r) +{ + bool ok; + + /* + * [MS-SWN] 3.1.4.3 + * The server MUST search for the WitnessRegistration in + * WitnessRegistrationList, where WitnessRegistration.RegistrationKey + * matches the pContext parameter. If no matching entry is found, + * the server SHOULD<4> stop processing the request and return the + * error code ERROR_NOT_FOUND. + */ + ok = close_policy_hnd(p, &r->in.context_handle); + if (!ok) { + if (p->fault_state != 0) { + p->fault_state = 0; + } + return WERR_NOT_FOUND; + } + + return WERR_OK; +} + +/**************************************************************** + _witness_AsyncNotify +****************************************************************/ + +struct swn_service_async_notify_state { + struct swn_service_async_notify_state *prev, *next; + struct tevent_context *ev; + struct tevent_req *req; + TALLOC_CTX *r_mem_ctx; + struct witness_AsyncNotify *r; + struct swn_service_registration *reg; + struct tevent_queue_entry *qe; +}; + +static void swn_service_async_notify_trigger(struct tevent_req *req, + void *private_data); + +static void swn_service_async_notify_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct swn_service_async_notify_state *state = + tevent_req_data(req, + struct swn_service_async_notify_state); + + TALLOC_FREE(state->qe); + + if (state->reg != NULL) { + DLIST_REMOVE(state->reg->async_notify.list, state); + state->reg = NULL; + } +} + +static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state) +{ + swn_service_async_notify_cleanup(state->req, TEVENT_REQ_USER_ERROR); + swn_service_async_notify_trigger(state->req, NULL); +} + +static bool swn_service_async_notify_cancel(struct tevent_req *req) +{ + return false; +} + +static struct tevent_req *swn_service_async_notify_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + TALLOC_CTX *r_mem_ctx, + struct witness_AsyncNotify *r, + struct swn_service_registration *reg) +{ + struct tevent_req *req = NULL; + struct swn_service_async_notify_state *state = NULL; + struct timeval now = timeval_current(); + + req = tevent_req_create(mem_ctx, &state, + struct swn_service_async_notify_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->req = req; + state->r_mem_ctx = r_mem_ctx; + state->r = r; + state->reg = reg; + + /* + * triggered changes likely wakeup + * more than one waiter, so we better + * let all individual waiters go through + * a tevent_immediate round. + */ + tevent_req_defer_callback(req, ev); + + tevent_req_set_cleanup_fn(req, swn_service_async_notify_cleanup); + tevent_req_set_cancel_fn(req, swn_service_async_notify_cancel); + + if (!reg->forced_response.triggered && + !reg->change_notification.triggered && + !reg->move_notification.triggered && + !reg->share_notification.triggered && + !reg->ip_notification.triggered) + { + tevent_queue_stop(reg->async_notify.queue); + } + + DLIST_ADD_END(reg->async_notify.list, state); + + state->qe = tevent_queue_add_entry(reg->async_notify.queue, ev, req, + swn_service_async_notify_trigger, + NULL) + if (tevent_req_nomem(state->qe, req)) { + return tevent_req_post(req, ev); + } + + if (reg->async_notify.timeout_secs != 0) { + struct timeval endtime; + bool ok; + + endtime = timeval_add(&now, reg->async_notify.timeout_secs, 0); + ok = tevent_req_set_endtime(req, ev, endtime); + if (!ok) { + tevent_req_oom(req); + return tevent_req_post(req, ev); + } + } + + /* + * Once we added the queue entry + * swn_service_registration_update_usage() + * will adjust the registration expire time... + */ + swn_service_registration_update_usage(state->reg, now); + + /* + * Wait for trigger or timeout... + */ + return req; +} + +static void swn_service_async_notify_trigger(struct tevent_req *req, + void *private_data) +{ + struct swn_service_async_notify_state *state = + tevent_req_data(req, + struct swn_service_async_notify_state); + struct swn_service_registration *reg = state->reg; + struct witness_notifyResponse *resp = NULL; + WERROR forced_result = WERR_OK; + bool defer_forced_unregister = false; + + if (reg == NULL) { + tevent_req_werror(req, WERR_NOT_FOUND); + return; + } + + if (reg->forced_response.triggered) { + resp = talloc_move(state, ®->forced_response.response); + forced_result = reg->forced_response.result; + + reg->forced_response.triggered = false; + reg->forced_response.result = WERR_OK; + goto finished; + } + + if (reg->change_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + const struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + char reg_ip[INET6_ADDRSTRLEN] = { 0, }; + struct witness_ResourceChange *rc = NULL; + enum witness_interfaceInfo_state cur_state; + + print_sockaddr(reg_ip, sizeof(reg_ip), ®->ip_address.u.ss); + + iface = swn_service_interface_by_addr(swn, ®->ip_address); + if (iface != NULL) { + cur_state = iface->state; + } else { + /* + * If the interface is no longer in our list + * it must be unavailable + */ + cur_state = WITNESS_STATE_UNAVAILABLE; + } + if (cur_state != WITNESS_STATE_AVAILABLE) { + reg->change_notification.last_ip_state = cur_state; + } + + resp = talloc_zero(state, struct witness_notifyResponse); + if (tevent_req_nomem(resp, req)) { + return; + } + + msgs = talloc_zero_array(resp, + union witness_notifyResponse_message, + 1); + if (tevent_req_nomem(msgs, req)) { + return; + } + + resp->type = WITNESS_NOTIFY_RESOURCE_CHANGE; + resp->num = 0; + resp->messages = msgs; + + rc = &msgs[resp->num].resource_change; + + switch (reg->change_notification.last_ip_state) { + case WITNESS_STATE_AVAILABLE: + rc->type = WITNESS_RESOURCE_STATE_AVAILABLE; + break; + case WITNESS_STATE_UNAVAILABLE: + rc->type = WITNESS_RESOURCE_STATE_UNAVAILABLE; + break; + case WITNESS_STATE_UNKNOWN: + rc->type = WITNESS_RESOURCE_STATE_UNKNOWN; + break; + } + + /* + * We use the ip address as resource name + */ + rc->name = talloc_strdup(msgs, reg_ip); + if (tevent_req_nomem(rc->name, req)) { + return; + } + + resp->num += 1; + + if (rc->type != WITNESS_RESOURCE_STATE_AVAILABLE) { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + defer_forced_unregister = true; + } + + if (reg->change_notification.last_ip_state != cur_state) { + /* + * This means the last_ip_state was *not* available, + * and the current_state *is* available. + * + * keep the queue running and return the available + * message in the next run + */ + reg->change_notification.last_ip_state = cur_state; + goto finished; + } + + reg->change_notification.triggered = false; + reg->change_notification.last_ip_state = WITNESS_STATE_UNKNOWN; + goto finished; + } + + if (reg->move_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + struct witness_IPaddrInfoList *list = NULL; + uint32_t num_ips = 0; + const uint32_t *new_node = NULL; + const struct samba_sockaddr *new_ip = NULL; + + if (reg->move_notification.new_node != NONCLUSTER_VNN) { + new_node = ®->move_notification.new_node; + } + if (!is_zero_addr(®->move_notification.new_ip.u.ss)) { + new_ip = ®->move_notification.new_ip; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + num_ips += 1; + } + + if (num_ips == 0) { + goto no_moves; + } + + resp = talloc_zero(state, struct witness_notifyResponse); + if (tevent_req_nomem(resp, req)) { + return; + } + + msgs = talloc_zero_array(resp, + union witness_notifyResponse_message, + 1); + if (tevent_req_nomem(msgs, req)) { + return; + } + + list = &msgs[0].client_move; + list->addr = talloc_zero_array(msgs, + struct witness_IPaddrInfo, + num_ips); + if (tevent_req_nomem(list->addr, req)) { + return; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + struct witness_IPaddrInfo *info = &list->addr[list->num]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + bool is_reg_ip = false; + + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + switch (iface->state) { + case WITNESS_STATE_AVAILABLE: + flags |= WITNESS_IPADDR_ONLINE; + break; + case WITNESS_STATE_UNAVAILABLE: + flags |= WITNESS_IPADDR_OFFLINE; + break; + case WITNESS_STATE_UNKNOWN: + /* We map unknown also to offline */ + flags |= WITNESS_IPADDR_OFFLINE; + break; + } + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_IPADDR_V4; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_IPADDR_V6; + ipv6 = addr; + } + + info->ipv4 = talloc_strdup(list, ipv4); + if (tevent_req_nomem(info->ipv4, req)) { + return; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (tevent_req_nomem(info->ipv6, req)) { + return; + } + info->flags = flags; + list->num += 1; + + is_reg_ip = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!is_reg_ip) { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND from + * a pending AsyncNotify() and calls + * Unregister() (which also gets + * WERR_NOT_FOUND). Then the client calls + * GetInterfaceList() and RegisterEx() again. + */ + defer_forced_unregister = true; + } + } + + resp->type = WITNESS_NOTIFY_CLIENT_MOVE; + resp->num = talloc_array_length(msgs); + resp->messages = msgs; + +no_moves: + reg->move_notification.triggered = false; + if (resp != NULL) { + goto finished; + } + } + + if (reg->share_notification.triggered) { + struct swn_service_globals *swn = reg->swn; + struct swn_service_interface *iface = NULL; + union witness_notifyResponse_message *msgs = NULL; + struct witness_IPaddrInfoList *list = NULL; + uint32_t num_ips = 0; + const uint32_t *new_node = NULL; + const struct samba_sockaddr *new_ip = NULL; + + if (reg->share_notification.new_node != NONCLUSTER_VNN) { + new_node = ®->share_notification.new_node; + } + if (!is_zero_addr(®->share_notification.new_ip.u.ss)) { + new_ip = ®->share_notification.new_ip; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + num_ips += 1; + } + + if (num_ips == 0) { + goto no_share_moves; + } + + resp = talloc_zero(state, struct witness_notifyResponse); + if (tevent_req_nomem(resp, req)) { + return; + } + + msgs = talloc_zero_array(resp, + union witness_notifyResponse_message, + 1); + if (tevent_req_nomem(msgs, req)) { + return; + } + + list = &msgs[0].client_move; + list->addr = talloc_zero_array(msgs, + struct witness_IPaddrInfo, + num_ips); + if (tevent_req_nomem(list->addr, req)) { + return; + } + + for (iface = swn->interfaces.list; + iface != NULL; + iface = iface->next) + { + struct witness_IPaddrInfo *info = &list->addr[list->num]; + char addr[INET6_ADDRSTRLEN] = { 0, }; + const char *ipv4 = "0.0.0.0"; + const char *ipv6 = "::"; + uint32_t flags = 0; + bool is_reg_ip = false; + + if (new_node != NULL && + iface->current_vnn != *new_node) + { + continue; + } + + if (new_ip != NULL && + !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa)) + { + continue; + } + + switch (iface->state) { + case WITNESS_STATE_AVAILABLE: + flags |= WITNESS_IPADDR_ONLINE; + break; + case WITNESS_STATE_UNAVAILABLE: + flags |= WITNESS_IPADDR_OFFLINE; + break; + case WITNESS_STATE_UNKNOWN: + /* We map unknown also to offline */ + flags |= WITNESS_IPADDR_OFFLINE; + break; + } + + print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss); + if (iface->addr.u.sa.sa_family == AF_INET) { + flags |= WITNESS_IPADDR_V4; + ipv4 = addr; + } else if (iface->addr.u.sa.sa_family == AF_INET6) { + flags |= WITNESS_IPADDR_V6; + ipv6 = addr; + } + + info->ipv4 = talloc_strdup(list, ipv4); + if (tevent_req_nomem(info->ipv4, req)) { + return; + } + info->ipv6 = talloc_strdup(list, ipv6); + if (tevent_req_nomem(info->ipv6, req)) { + return; + } + info->flags = flags; + list->num += 1; + + is_reg_ip = sockaddr_equal(®->ip_address.u.sa, + &iface->addr.u.sa); + if (!is_reg_ip) { + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND from + * a pending AsyncNotify() and calls + * Unregister() (which also gets + * WERR_NOT_FOUND). Then the client calls + * GetInterfaceList() and RegisterEx() again. + */ + defer_forced_unregister = true; + } + } + + resp->type = WITNESS_NOTIFY_SHARE_MOVE; + resp->num = talloc_array_length(msgs); + resp->messages = msgs; + +no_share_moves: + reg->share_notification.triggered = false; + if (resp != NULL) { + goto finished; + } + } + +finished: + if (!reg->forced_response.triggered && + !reg->change_notification.triggered && + !reg->move_notification.triggered && + !reg->share_notification.triggered && + !reg->ip_notification.triggered) + { + tevent_queue_stop(reg->async_notify.queue); + } + + if (defer_forced_unregister) { + struct tevent_timer *te = NULL; + + /* + * In order to let a Windows server 2022 + * correctly re-register after moving + * to a new connection, we force an + * unregistration after 5 seconds. + * + * It means the client gets WERR_NOT_FOUND + * from a pending AsyncNotify() and calls + * Unregister() (which also gets WERR_NOT_FOUND). + * Then the client calls GetInterfaceList() + * and RegisterEx() again. + */ + TALLOC_FREE(reg->forced_unregister.timer); + te = tevent_add_timer(state->ev, + reg, + timeval_current_ofs(5,0), + swn_service_registration_force_unregister, + reg); + if (tevent_req_nomem(te, req)) { + return; + } + reg->forced_unregister.timer = te; + } + + *state->r->out.response = talloc_move(state->r_mem_ctx, &resp); + state->r->out.result = forced_result; + if (!W_ERROR_IS_OK(forced_result)) { + tevent_req_werror(req, forced_result); + return; + } + tevent_req_done(req); +} + +static WERROR swn_service_async_notify_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_werror(req); +} + +struct _witness_AsyncNotify_state { + struct dcesrv_call_state *dce_call; + struct witness_AsyncNotify *r; + struct swn_service_registration *reg; + struct tevent_req *subreq; +}; + +static bool _witness_AsyncNotify_cancel(struct tevent_req *req); +static void _witness_AsyncNotify_done(struct tevent_req *subreq); + +WERROR _witness_AsyncNotify(struct pipes_struct *p, + struct witness_AsyncNotify *r) +{ + struct tevent_req *req = NULL; + struct _witness_AsyncNotify_state *state = NULL; + struct swn_service_registration *reg = NULL; + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + /* + * [MS-SWN] 3.1.4.4 + * The server MUST search for the WitnessRegistration in + * WitnessRegistrationList, where WitnessRegistration.RegistrationKey + * matches the pContext parameter. If no matching entry is found, the + * server MUST fail the request and return the error code + * ERROR_NOT_FOUND. + */ + reg = find_policy_by_hnd(p, &r->in.context_handle, + SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION, + struct swn_service_registration, + &status); + if (!NT_STATUS_IS_OK(status)) { + if (p->fault_state != 0) { + p->fault_state = 0; + } + return WERR_NOT_FOUND; + } + + swn_service_registration_update_usage(reg, p->dce_call->time); + + req = tevent_req_create(p->mem_ctx, &state, + struct _witness_AsyncNotify_state); + if (req == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call = p->dce_call; + state->r = r; + state->reg = reg; + + tevent_req_set_cancel_fn(req, _witness_AsyncNotify_cancel); + + state->subreq = swn_service_async_notify_send(state, + state->dce_call->event_ctx, + state->dce_call, + state->r, + state->reg); + if (state->subreq == NULL) { + TALLOC_FREE(state); + return WERR_NOT_ENOUGH_MEMORY; + } + tevent_req_set_callback(state->subreq, + _witness_AsyncNotify_done, + req); + + state->dce_call->subreq = req; + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + return WERR_EVENT_PENDING; /* hidden by DCESRV_CALL_STATE_FLAG_ASYNC */ +} + +static bool _witness_AsyncNotify_cancel(struct tevent_req *req) +{ + struct _witness_AsyncNotify_state *state = + tevent_req_data(req, + struct _witness_AsyncNotify_state); + struct dcesrv_call_state *dce_call = state->dce_call; + + SMB_ASSERT(dce_call->subreq == req); + dce_call->subreq = NULL; + + TALLOC_FREE(state->subreq); + + if (dce_call->got_orphaned) { + dce_call->fault_code = DCERPC_FAULT_SERVER_UNAVAILABLE; + } else { + dce_call->fault_code = DCERPC_NCA_S_FAULT_CANCEL; + } + state->r->out.result = WERR_RPC_S_CALL_CANCELLED; + + dcesrv_async_reply(dce_call); + return true; +} + +static void _witness_AsyncNotify_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct _witness_AsyncNotify_state *state = + tevent_req_data(req, + struct _witness_AsyncNotify_state); + struct dcesrv_call_state *dce_call = state->dce_call; + + SMB_ASSERT(dce_call->subreq == req); + dce_call->subreq = NULL; + + SMB_ASSERT(state->subreq == subreq); + state->subreq = NULL; + + state->r->out.result = swn_service_async_notify_recv(subreq); + TALLOC_FREE(subreq); + + if (W_ERROR_EQUAL(state->r->out.result, WERR_NOT_FOUND)) { + state->reg = NULL; + } + + if (state->reg != NULL && + tevent_queue_length(state->reg->async_notify.queue) == 0) + { + struct timeval now = timeval_current(); + swn_service_registration_update_usage(state->reg, now); + } + + dcesrv_async_reply(dce_call); +} + +/**************************************************************** + _witness_RegisterEx +****************************************************************/ + +WERROR _witness_RegisterEx(struct pipes_struct *p, + struct witness_RegisterEx *r) +{ + struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx; + struct swn_service_registration *reg = NULL; + struct samba_sockaddr addr = { .sa_socklen = 0, }; + struct swn_service_interface *iface = NULL; + NTSTATUS status; + WERROR werr; + bool ok; + + /* + * [MS-SWN] 3.1.4.5 + * If the Version field of the request is not 0x00020000, the server + * MUST stop processing the request and return the error code + * ERROR_REVISION_MISMATCH + */ + if (r->in.version != WITNESS_V2) { + return WERR_REVISION_MISMATCH; + } + + /* + * [MS-SWN] 3.1.4.5 + * If NetName, IpAddress or ClientComputerName is NULL, the server + * MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + if (r->in.net_name == NULL || + r->in.ip_address == NULL || + r->in.client_computer_name == NULL) + { + return WERR_INVALID_PARAMETER; + } + + status = swn_service_reload_interfaces(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * [MS-SWN] 3.1.4.5 + * If the NetName parameter is not equal to ServerGlobalName, the + * server MUST fail the request and return the error code + * ERROR_INVALID_PARAMETER + */ + werr = swn_server_check_net_name(swn_globals, r->in.net_name); + if (!W_ERROR_IS_OK(werr)) { + DBG_INFO("Invalid net_name[%s], " + "server_global_name[%s]: %s\n", + log_escape(p->mem_ctx, r->in.net_name), + swn_globals->server_global_name, + win_errstr(werr)); + return werr; + } + + /* + * [MS-SWN] 3.1.4.5 + * If ShareName is not NULL, the server MUST enumerate the shares by + * calling NetrShareEnum as specified in [MS-SRVS] section 3.1.4.8. + * If the enumeration fails or if no shares are returned, the server + * MUST return the error code ERROR_INVALID_STATE. + * + * If none of the shares in the list has shi*_type set to + * STYPE_CLUSTER_SOFS as specified in [MS-SRVS] section 3.1.4.8, + * the server MUST ignore ShareName. + * + * In a CTDB cluster, all shares in the clustered filesystem are + * scale-out. Check if the provided share name is in a clustered FS + */ + if (r->in.share_name != NULL) { + char *save_share = NULL; + int cmp; + + /* + * For now we allow all shares... + * + * The main reason is that windows + * clients typically connect as + * machine account, so things like %U + * wouldn't work anyway. + * + * And in the end it's just a string, + * so we just check it's sane. + */ + save_share = log_escape(p->mem_ctx, r->in.share_name); + if (save_share == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + cmp = strcmp(save_share, r->in.share_name); + if (cmp != 0) { + DBG_INFO("Invalid share_name[%s]\n", + save_share); + return WERR_INVALID_STATE; + } + TALLOC_FREE(save_share); + } + + /* + * [MS-SWN] 3.1.4.5 + * The server MUST search for an Interface in InterfaceList, where + * Interface.IPv4Address or Interface.IPv6Address matches the + * IpAddress parameter based on its format. If no matching entry is + * found, the server MUST fail the request and return the error code + * ERROR_INVALID_STATE. + */ + ok = is_ipaddress(r->in.ip_address); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + ok = interpret_string_addr(&addr.u.ss, + r->in.ip_address, + AI_PASSIVE|AI_NUMERICHOST); + if (!ok) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + iface = swn_service_interface_by_addr(swn_globals, &addr); + if (iface == NULL) { + DBG_INFO("Invalid ip_address[%s]\n", + log_escape(p->mem_ctx, r->in.ip_address)); + return WERR_INVALID_STATE; + } + + werr = swn_server_registration_create(swn_globals, p, r, iface, ®); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + *r->out.context_handle = reg->key.handle; + return WERR_OK; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_witness_scompat.c" diff --git a/source3/rpc_server/wkssvc/srv_wkssvc_nt.c b/source3/rpc_server/wkssvc/srv_wkssvc_nt.c new file mode 100644 index 0000000..0724dd0 --- /dev/null +++ b/source3/rpc_server/wkssvc/srv_wkssvc_nt.c @@ -0,0 +1,964 @@ +/* + * Unix SMB/CIFS implementation. + * RPC Pipe client / server routines + * + * Copyright (C) Andrew Tridgell 1992-1997, + * Copyright (C) Gerald (Jerry) Carter 2006. + * Copyright (C) Guenther Deschner 2007-2008. + * + * 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/>. + */ + +/* This is the implementation of the wks interface. */ + +#include "includes.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/libnet_join.h" +#include "libnet/libnet_join.h" +#include "../libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_wkssvc.h" +#include "librpc/gen_ndr/ndr_wkssvc_scompat.h" +#include "../libcli/security/security.h" +#include "session.h" +#include "smbd/smbd.h" +#include "auth.h" +#include "krb5_env.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +struct dom_usr { + char *name; + char *domain; + time_t login_time; +}; + +static int dom_user_cmp(const struct dom_usr *usr1, const struct dom_usr *usr2) +{ + /* Called from qsort to compare two domain users in a dom_usr_t array + * for sorting by login time. Return >0 if usr1 login time was later + * than usr2 login time, <0 if it was earlier */ + return (usr1->login_time - usr2->login_time); +} + +/******************************************************************* + Get a list of the names of all users of this machine who are + logged into the domain. + + This should return a list of the users on this machine who are + logged into the domain (i.e. have been authenticated by the domain's + password server) but that doesn't fit well with the normal Samba + scenario where accesses out to the domain are made through smbclient + with each such session individually authenticated. So about the best + we can do currently is to list sessions of local users connected to + this server, which means that to get themself included in the list a + local user must create a session to the local samba server by running: + smbclient \\\\localhost\\share + + FIXME: find a better way to get local users logged into the domain + in this list. + ********************************************************************/ + +static int get_domain_userlist(TALLOC_CTX *mem_ctx, struct dom_usr **pusers) +{ + struct sessionid *session_list = NULL; + char *machine_name, *p, *nm; + const char *sep; + struct dom_usr *users, *tmp; + int i, num_users, num_sessions; + + sep = lp_winbind_separator(); + if (!sep) { + sep = "\\"; + } + + num_sessions = list_sessions(mem_ctx, &session_list); + if (num_sessions == 0) { + *pusers = NULL; + return 0; + } + + users = talloc_array(mem_ctx, struct dom_usr, num_sessions); + if (users == NULL) { + TALLOC_FREE(session_list); + return ENOMEM; + } + + for (i=num_users=0; i<num_sessions; i++) { + if (session_list[i].username[0] == '\0' || + session_list[i].remote_machine[0] == '\0') { + continue; + } + p = strpbrk(session_list[i].remote_machine, "./"); + if (p) { + *p = '\0'; + } + machine_name = talloc_asprintf_strupper_m( + users, "%s", session_list[i].remote_machine); + if (machine_name == NULL) { + DEBUG(10, ("talloc_asprintf failed\n")); + continue; + } + if (strcmp(machine_name, lp_netbios_name()) == 0) { + p = session_list[i].username; + nm = strstr(p, sep); + if (nm) { + /* + * "domain+name" format so split domain and + * name components + */ + *nm = '\0'; + nm += strlen(sep); + users[num_users].domain = + talloc_asprintf_strupper_m(users, + "%s", p); + users[num_users].name = talloc_strdup(users, + nm); + } else { + /* + * Simple user name so get domain from smb.conf + */ + users[num_users].domain = + talloc_strdup(users, lp_workgroup()); + users[num_users].name = talloc_strdup(users, + p); + } + users[num_users].login_time = + session_list[i].connect_start; + num_users++; + } + TALLOC_FREE(machine_name); + } + TALLOC_FREE(session_list); + + if (num_users == 0) { + TALLOC_FREE(users); + *pusers = NULL; + return 0; + } + + tmp = talloc_realloc(mem_ctx, users, struct dom_usr, num_users); + if (tmp == NULL) { + TALLOC_FREE(users); + return ENOMEM; + } + users = tmp; + + /* Sort the user list by time, oldest first */ + TYPESAFE_QSORT(users, num_users, dom_user_cmp); + + *pusers = users; + return 0; +} + +/******************************************************************* + RPC Workstation Service request NetWkstaGetInfo with level 100. + Returns to the requester: + - The machine name. + - The smb version number + - The domain name. + Returns a filled in wkssvc_NetWkstaInfo100 struct. + ********************************************************************/ + +static struct wkssvc_NetWkstaInfo100 *create_wks_info_100(TALLOC_CTX *mem_ctx) +{ + struct wkssvc_NetWkstaInfo100 *info100; + + info100 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo100); + if (info100 == NULL) { + return NULL; + } + + info100->platform_id = PLATFORM_ID_NT; /* unknown */ + info100->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION; + info100->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION; + + info100->server_name = talloc_asprintf_strupper_m( + info100, "%s", lp_netbios_name()); + info100->domain_name = talloc_asprintf_strupper_m( + info100, "%s", lp_workgroup()); + + return info100; +} + +/******************************************************************* + RPC Workstation Service request NetWkstaGetInfo with level 101. + Returns to the requester: + - As per NetWkstaGetInfo with level 100, plus: + - The LANMAN directory path (not currently supported). + Returns a filled in wkssvc_NetWkstaInfo101 struct. + ********************************************************************/ + +static struct wkssvc_NetWkstaInfo101 *create_wks_info_101(TALLOC_CTX *mem_ctx) +{ + struct wkssvc_NetWkstaInfo101 *info101; + + info101 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo101); + if (info101 == NULL) { + return NULL; + } + + info101->platform_id = PLATFORM_ID_NT; /* unknown */ + info101->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION; + info101->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION; + + info101->server_name = talloc_asprintf_strupper_m( + info101, "%s", lp_netbios_name()); + info101->domain_name = talloc_asprintf_strupper_m( + info101, "%s", lp_workgroup()); + info101->lan_root = ""; + + return info101; +} + +/******************************************************************* + RPC Workstation Service request NetWkstaGetInfo with level 102. + Returns to the requester: + - As per NetWkstaGetInfo with level 101, plus: + - The number of logged in users. + Returns a filled in wkssvc_NetWkstaInfo102 struct. + ********************************************************************/ + +static struct wkssvc_NetWkstaInfo102 *create_wks_info_102(TALLOC_CTX *mem_ctx) +{ + struct wkssvc_NetWkstaInfo102 *info102; + + info102 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo102); + if (info102 == NULL) { + return NULL; + } + + info102->platform_id = PLATFORM_ID_NT; /* unknown */ + info102->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION; + info102->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION; + + info102->server_name = talloc_asprintf_strupper_m( + info102, "%s", lp_netbios_name()); + info102->domain_name = talloc_asprintf_strupper_m( + info102, "%s", lp_workgroup()); + info102->lan_root = ""; + info102->logged_on_users = 0; + + return info102; +} + +/******************************************************************** + Handling for RPC Workstation Service request NetWkstaGetInfo + ********************************************************************/ + +WERROR _wkssvc_NetWkstaGetInfo(struct pipes_struct *p, + struct wkssvc_NetWkstaGetInfo *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dom_sid_buf buf; + + switch (r->in.level) { + case 100: + /* Level 100 can be allowed from anyone including anonymous + * so no access checks are needed for this case */ + r->out.info->info100 = create_wks_info_100(p->mem_ctx); + if (r->out.info->info100 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + break; + case 101: + /* Level 101 can be allowed from any logged in user */ + if (!nt_token_check_sid(&global_sid_Authenticated_Users, + session_info->security_token)) { + DEBUG(1,("User not allowed for NetWkstaGetInfo level " + "101\n")); + DEBUGADD(3,(" - does not have sid for Authenticated " + "Users %s:\n", + dom_sid_str_buf( + &global_sid_Authenticated_Users, + &buf))); + security_token_debug(DBGC_CLASS, 3, + session_info->security_token); + return WERR_ACCESS_DENIED; + } + r->out.info->info101 = create_wks_info_101(p->mem_ctx); + if (r->out.info->info101 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + break; + case 102: + /* Level 102 Should only be allowed from a domain administrator */ + if (!nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + DEBUG(1,("User not allowed for NetWkstaGetInfo level " + "102\n")); + DEBUGADD(3,(" - does not have sid for Administrators " + "group %s, sids are:\n", + dom_sid_str_buf( + &global_sid_Builtin_Administrators, + &buf))); + security_token_debug(DBGC_CLASS, 3, + session_info->security_token); + return WERR_ACCESS_DENIED; + } + r->out.info->info102 = create_wks_info_102(p->mem_ctx); + if (r->out.info->info102 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + break; + default: + return WERR_INVALID_LEVEL; + } + + return WERR_OK; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetWkstaSetInfo(struct pipes_struct *p, + struct wkssvc_NetWkstaSetInfo *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + RPC Workstation Service request NetWkstaEnumUsers with level 0: + Returns to the requester: + - the user names of the logged in users. + Returns a filled in wkssvc_NetWkstaEnumUsersCtr0 struct. + ********************************************************************/ + +static struct wkssvc_NetWkstaEnumUsersCtr0 *create_enum_users0( + TALLOC_CTX *mem_ctx) +{ + struct wkssvc_NetWkstaEnumUsersCtr0 *ctr0; + + ctr0 = talloc(mem_ctx, struct wkssvc_NetWkstaEnumUsersCtr0); + if (ctr0 == NULL) { + return NULL; + } + + ctr0->entries_read = 0; + ctr0->user0 = talloc_array(ctr0, struct wkssvc_NetrWkstaUserInfo0, 0); + if (ctr0->user0 == NULL) { + TALLOC_FREE(ctr0); + return NULL; + } + + return ctr0; +} + +/******************************************************************** + RPC Workstation Service request NetWkstaEnumUsers with level 1. + Returns to the requester: + - the user names of the logged in users, + - the domain or machine each is logged into, + - the password server that was used to authenticate each, + - other domains each user is logged into (not currently supported). + Returns a filled in wkssvc_NetWkstaEnumUsersCtr1 struct. + ********************************************************************/ + +static struct wkssvc_NetWkstaEnumUsersCtr1 *create_enum_users1( + TALLOC_CTX *mem_ctx) +{ + struct wkssvc_NetWkstaEnumUsersCtr1 *ctr1; + struct dom_usr *dom_users; + const char *pwd_server; + char *pwd_tmp; + int i, num_dom_users, ret; + + ctr1 = talloc(mem_ctx, struct wkssvc_NetWkstaEnumUsersCtr1); + if (ctr1 == NULL) { + return NULL; + } + + ret = get_domain_userlist(talloc_tos(), &dom_users); + if (ret != 0) { + TALLOC_FREE(ctr1); + errno = ret; + return NULL; + } + num_dom_users = talloc_array_length(dom_users); + + ctr1->user1 = talloc_array(ctr1, struct wkssvc_NetrWkstaUserInfo1, + num_dom_users); + if (ctr1->user1 == NULL) { + TALLOC_FREE(ctr1); + TALLOC_FREE(dom_users); + errno = ENOMEM; + return NULL; + } + + pwd_server = ""; + + if ((pwd_tmp = talloc_strdup(ctr1->user1, lp_password_server()))) { + /* The configured password server is a full DNS name but + * for the logon server we need to return just the first + * component (machine name) of it in upper-case */ + char *p = strchr(pwd_tmp, '.'); + if (p) { + *p = '\0'; + } else { + p = pwd_tmp + strlen(pwd_tmp); + } + while (--p >= pwd_tmp) { + *p = toupper(*p); + } + pwd_server = pwd_tmp; + } + + /* Now domain users */ + for (i=0; i<num_dom_users; i++) { + ctr1->user1[i].user_name = + talloc_strdup(ctr1->user1, dom_users[i].name); + ctr1->user1[i].logon_domain = + talloc_strdup(ctr1->user1, dom_users[i].domain); + ctr1->user1[i].logon_server = pwd_server; + + ctr1->user1[i++].other_domains = NULL; /* Maybe in future? */ + } + + ctr1->entries_read = i; + + TALLOC_FREE(dom_users); + return ctr1; +} + +/******************************************************************** + Handling for RPC Workstation Service request NetWkstaEnumUsers + (a.k.a Windows NetWkstaUserEnum) + ********************************************************************/ + +WERROR _wkssvc_NetWkstaEnumUsers(struct pipes_struct *p, + struct wkssvc_NetWkstaEnumUsers *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + + /* This with any level should only be allowed from a domain administrator */ + if (!nt_token_check_sid(&global_sid_Builtin_Administrators, + session_info->security_token)) { + struct dom_sid_buf buf; + DEBUG(1,("User not allowed for NetWkstaEnumUsers\n")); + DEBUGADD(3,(" - does not have sid for Administrators group " + "%s\n", + dom_sid_str_buf( + &global_sid_Builtin_Administrators, + &buf))); + security_token_debug( + DBGC_CLASS, 3, session_info->security_token); + return WERR_ACCESS_DENIED; + } + + switch (r->in.info->level) { + case 0: + r->out.info->ctr.user0 = create_enum_users0(p->mem_ctx); + if (r->out.info->ctr.user0 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + r->out.info->level = r->in.info->level; + *r->out.entries_read = r->out.info->ctr.user0->entries_read; + if (r->out.resume_handle != NULL) { + *r->out.resume_handle = 0; + } + break; + case 1: + r->out.info->ctr.user1 = create_enum_users1(p->mem_ctx); + if (r->out.info->ctr.user1 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + r->out.info->level = r->in.info->level; + *r->out.entries_read = r->out.info->ctr.user1->entries_read; + if (r->out.resume_handle != NULL) { + *r->out.resume_handle = 0; + } + break; + default: + return WERR_INVALID_LEVEL; + } + + return WERR_OK; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrWkstaUserGetInfo(struct pipes_struct *p, + struct wkssvc_NetrWkstaUserGetInfo *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrWkstaUserSetInfo(struct pipes_struct *p, + struct wkssvc_NetrWkstaUserSetInfo *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetWkstaTransportEnum(struct pipes_struct *p, + struct wkssvc_NetWkstaTransportEnum *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrWkstaTransportAdd(struct pipes_struct *p, + struct wkssvc_NetrWkstaTransportAdd *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrWkstaTransportDel(struct pipes_struct *p, + struct wkssvc_NetrWkstaTransportDel *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrUseAdd(struct pipes_struct *p, + struct wkssvc_NetrUseAdd *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrUseGetInfo(struct pipes_struct *p, + struct wkssvc_NetrUseGetInfo *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrUseDel(struct pipes_struct *p, + struct wkssvc_NetrUseDel *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrUseEnum(struct pipes_struct *p, + struct wkssvc_NetrUseEnum *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrMessageBufferSend(struct pipes_struct *p, + struct wkssvc_NetrMessageBufferSend *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrWorkstationStatisticsGet(struct pipes_struct *p, + struct wkssvc_NetrWorkstationStatisticsGet *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrLogonDomainNameAdd(struct pipes_struct *p, + struct wkssvc_NetrLogonDomainNameAdd *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrLogonDomainNameDel(struct pipes_struct *p, + struct wkssvc_NetrLogonDomainNameDel *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrJoinDomain(struct pipes_struct *p, + struct wkssvc_NetrJoinDomain *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrUnjoinDomain(struct pipes_struct *p, + struct wkssvc_NetrUnjoinDomain *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrRenameMachineInDomain(struct pipes_struct *p, + struct wkssvc_NetrRenameMachineInDomain *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrValidateName(struct pipes_struct *p, + struct wkssvc_NetrValidateName *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrGetJoinInformation(struct pipes_struct *p, + struct wkssvc_NetrGetJoinInformation *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrGetJoinableOus(struct pipes_struct *p, + struct wkssvc_NetrGetJoinableOus *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + _wkssvc_NetrJoinDomain2 + ********************************************************************/ + +WERROR _wkssvc_NetrJoinDomain2(struct pipes_struct *p, + struct wkssvc_NetrJoinDomain2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct libnet_JoinCtx *j = NULL; + char *cleartext_pwd = NULL; + char *admin_domain = NULL; + char *admin_account = NULL; + WERROR werr; + struct security_token *token = session_info->security_token; + NTSTATUS status; + DATA_BLOB session_key; + bool ok; + + if (!r->in.domain_name) { + return WERR_INVALID_PARAMETER; + } + + if (!r->in.admin_account || !r->in.encrypted_password) { + return WERR_INVALID_PARAMETER; + } + + if (!security_token_has_privilege(token, SEC_PRIV_MACHINE_ACCOUNT) && + !nt_token_check_domain_rid(token, DOMAIN_RID_ADMINS) && + !nt_token_check_sid(&global_sid_Builtin_Administrators, token)) { + DEBUG(5,("_wkssvc_NetrJoinDomain2: account doesn't have " + "sufficient privileges\n")); + return WERR_ACCESS_DENIED; + } + + if ((r->in.join_flags & WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED) || + (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) { + return WERR_NOT_SUPPORTED; + } + + status = session_extract_session_key(session_info, + &session_key, + KEY_USE_16BYTES); + if(!NT_STATUS_IS_OK(status)) { + DEBUG(5,("_wkssvc_NetrJoinDomain2: no session key %s\n", + nt_errstr(status))); + return WERR_NO_USER_SESSION_KEY; + } + + werr = decode_wkssvc_join_password_buffer( + p->mem_ctx, r->in.encrypted_password, + &session_key, &cleartext_pwd); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ok = split_domain_user(p->mem_ctx, + r->in.admin_account, + &admin_domain, + &admin_account); + if (!ok) { + return WERR_NOT_ENOUGH_MEMORY; + } + + werr = libnet_init_JoinCtx(p->mem_ctx, &j); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + j->in.domain_name = r->in.domain_name; + j->in.account_ou = r->in.account_ou; + j->in.join_flags = r->in.join_flags; + j->in.admin_account = admin_account; + j->in.admin_password = cleartext_pwd; + j->in.debug = true; + j->in.modify_config = lp_config_backend_is_registry(); + j->in.msg_ctx = p->msg_ctx; + + become_root(); + setenv(KRB5_ENV_CCNAME, "MEMORY:_wkssvc_NetrJoinDomain2", 1); + werr = libnet_Join(p->mem_ctx, j); + unsetenv(KRB5_ENV_CCNAME); + unbecome_root(); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(5,("_wkssvc_NetrJoinDomain2: libnet_Join failed with: %s\n", + j->out.error_string ? j->out.error_string : + win_errstr(werr))); + } + + TALLOC_FREE(j); + return werr; +} + +/******************************************************************** + _wkssvc_NetrUnjoinDomain2 + ********************************************************************/ + +WERROR _wkssvc_NetrUnjoinDomain2(struct pipes_struct *p, + struct wkssvc_NetrUnjoinDomain2 *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct libnet_UnjoinCtx *u = NULL; + char *cleartext_pwd = NULL; + char *admin_domain = NULL; + char *admin_account = NULL; + WERROR werr; + struct security_token *token = session_info->security_token; + NTSTATUS status; + DATA_BLOB session_key; + bool ok; + + if (!r->in.account || !r->in.encrypted_password) { + return WERR_INVALID_PARAMETER; + } + + if (!security_token_has_privilege(token, SEC_PRIV_MACHINE_ACCOUNT) && + !nt_token_check_domain_rid(token, DOMAIN_RID_ADMINS) && + !nt_token_check_sid(&global_sid_Builtin_Administrators, token)) { + DEBUG(5,("_wkssvc_NetrUnjoinDomain2: account doesn't have " + "sufficient privileges\n")); + return WERR_ACCESS_DENIED; + } + + status = session_extract_session_key(session_info, + &session_key, + KEY_USE_16BYTES); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5,("_wkssvc_NetrUnjoinDomain2: no session key %s\n", + nt_errstr(status))); + return WERR_NO_USER_SESSION_KEY; + } + + werr = decode_wkssvc_join_password_buffer( + p->mem_ctx, r->in.encrypted_password, + &session_key, &cleartext_pwd); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ok = split_domain_user(p->mem_ctx, + r->in.account, + &admin_domain, + &admin_account); + if (!ok) { + return WERR_NOT_ENOUGH_MEMORY; + } + + werr = libnet_init_UnjoinCtx(p->mem_ctx, &u); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + u->in.domain_name = lp_realm(); + u->in.unjoin_flags = r->in.unjoin_flags | + WKSSVC_JOIN_FLAGS_JOIN_TYPE; + u->in.admin_account = admin_account; + u->in.admin_password = cleartext_pwd; + u->in.debug = true; + u->in.modify_config = lp_config_backend_is_registry(); + u->in.msg_ctx = p->msg_ctx; + + become_root(); + setenv(KRB5_ENV_CCNAME, "MEMORY:_wkssvc_NetrUnjoinDomain2", 1); + werr = libnet_Unjoin(p->mem_ctx, u); + unsetenv(KRB5_ENV_CCNAME); + unbecome_root(); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(5,("_wkssvc_NetrUnjoinDomain2: libnet_Unjoin failed with: %s\n", + u->out.error_string ? u->out.error_string : + win_errstr(werr))); + } + + TALLOC_FREE(u); + return werr; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrRenameMachineInDomain2(struct pipes_struct *p, + struct wkssvc_NetrRenameMachineInDomain2 *r) +{ + /* for now just return not supported */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrValidateName2(struct pipes_struct *p, + struct wkssvc_NetrValidateName2 *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrGetJoinableOus2(struct pipes_struct *p, + struct wkssvc_NetrGetJoinableOus2 *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrAddAlternateComputerName(struct pipes_struct *p, + struct wkssvc_NetrAddAlternateComputerName *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrRemoveAlternateComputerName(struct pipes_struct *p, + struct wkssvc_NetrRemoveAlternateComputerName *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrSetPrimaryComputername(struct pipes_struct *p, + struct wkssvc_NetrSetPrimaryComputername *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/******************************************************************** + ********************************************************************/ + +WERROR _wkssvc_NetrEnumerateComputerNames(struct pipes_struct *p, + struct wkssvc_NetrEnumerateComputerNames *r) +{ + /* FIXME: Add implementation code here */ + p->fault_state = DCERPC_FAULT_OP_RNG_ERROR; + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_wkssvc_scompat.c" diff --git a/source3/rpc_server/wscript_build b/source3/rpc_server/wscript_build new file mode 100644 index 0000000..904311a --- /dev/null +++ b/source3/rpc_server/wscript_build @@ -0,0 +1,308 @@ +#!/usr/bin/env python + +### RPC_SERVER +bld.SAMBA3_SUBSYSTEM('rpc', + source='', + deps='dcerpc-server-core') + +bld.SAMBA_BINARY('samba-dcerpcd', + source='rpc_host.c', + deps=''' + samba3core + CMDLINE_S3 + dcerpc-binding + npa_tstream + AUTH_COMMON + RPC_SOCK_HELPER + NDR_RPC_HOST + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA_LIBRARY('RPC_WORKER', + private_library=True, + source=''' + rpc_worker.c + ''', + deps=''' + smbd_base + CMDLINE_S3 + NDR_RPC_HOST + RPC_SERVER + RPC_NCACN_NP + npa_tstream + ''') + +bld.SAMBA3_BINARY('rpcd_rpcecho', + source='rpcd_rpcecho.c', + deps=''' + RPC_WORKER + RPC_RPCECHO + ''', + for_selftest=True, + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_classic', + source='rpcd_classic.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_SERVICE + RPC_SOCK_HELPER + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_lsad', + source='rpcd_lsad.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_LSARPC + RPC_SAMR + RPC_DSSETUP + RPC_NETLOGON + RPC_SOCK_HELPER + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_winreg', + source='rpcd_winreg.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_WINREG + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_spoolss', + source='rpcd_spoolss.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_SPOOLSS + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_epmapper', + source='rpcd_epmapper.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_EPMAPPER + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_fsrvp', + source='rpcd_fsrvp.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_FSS_AGENT + smbd_base + ''', + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_BINARY('rpcd_witness', + source='rpcd_witness.c', + deps=''' + CMDLINE_S3 + RPC_WORKER + RPC_WITNESS + ''', + install_path='${SAMBA_LIBEXECDIR}', + enabled=bld.env.with_ctdb) + +bld.SAMBA3_SUBSYSTEM('RPC_CONFIG', + source='rpc_config.c', + deps='talloc') + +bld.SAMBA3_SUBSYSTEM('RPC_NCACN_NP', + source='rpc_ncacn_np.c rpc_handles.c', + deps='auth common_auth npa_tstream') + +bld.SAMBA3_LIBRARY('RPC_SERVER_LOOP', + private_library=True, + source='rpc_server.c', + deps=''' + LIBTSOCKET + dcerpc-server-core + npa_tstream + auth + RPC_NCACN_NP + samba3-util + ''') + +bld.SAMBA3_SUBSYSTEM('SRV_ACCESS_CHECK', + source='srv_access_check.c', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_SERVER', + source='', + deps=''' + dcerpc-server-core + RPC_CONFIG + RPC_SERVER_LOOP + NDR_NAMED_PIPE_AUTH + ''') + +### RPC_SERVICES +bld.SAMBA3_SUBSYSTEM('RPC_DSSETUP', + source='''dssetup/srv_dssetup_nt.c''', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_EPMAPPER', + source='''epmapper/srv_epmapper.c''', + deps='samba-util') + +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''', + deps='samba-util RPC_FSS_STATE') + +bld.SAMBA3_SUBSYSTEM('RPC_EVENTLOG', + source='''eventlog/srv_eventlog_nt.c + eventlog/srv_eventlog_reg.c''', + deps='LIBEVENTLOG LIBCLI_WINREG_INTERNAL') + +bld.SAMBA3_SUBSYSTEM('RPC_INITSHUTDOWN', + source='''initshutdown/srv_initshutdown_nt.c''', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_LSARPC', + source='''lsa/srv_lsa_nt.c''', + deps='SRV_ACCESS_CHECK LIBLSA GNUTLS_HELPERS') + +bld.SAMBA3_SUBSYSTEM('RPC_NETDFS', + source='''dfs/srv_dfs_nt.c''', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_NETLOGON', + source='''netlogon/srv_netlog_nt.c''', + deps='LIBCLI_AUTH DCERPC_SERVER_NETLOGON') + +bld.SAMBA3_SUBSYSTEM('RPC_NTSVCS', + source='''ntsvcs/srv_ntsvcs_nt.c''', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_RPCECHO', + source='''echo/srv_echo_nt.c''', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('RPC_SAMR', + source='''samr/srv_samr_nt.c + samr/srv_samr_util.c + samr/srv_samr_chgpasswd.c''', + deps='PLAINTEXT_AUTH SRV_ACCESS_CHECK DCERPC_HELPER') + +bld.SAMBA3_SUBSYSTEM('RPC_SPOOLSS', + source='''spoolss/srv_spoolss_nt.c + spoolss/srv_spoolss_util.c''', + deps='PRINTING PRINTBACKEND LIBCLI_WINREG_INTERNAL') + +bld.SAMBA3_SUBSYSTEM('RPC_IREMOTEWINSPOOL', + source=''' + spoolss/srv_iremotewinspool_nt.c + spoolss/srv_iremotewinspool.c + spoolss/iremotewinspool_util.c + ''', + deps='RPC_SPOOLSS') + +bld.SAMBA3_SUBSYSTEM('RPC_SRVSVC', + source='''srvsvc/srv_srvsvc_nt.c''', + deps='samba-util tdb') + +bld.SAMBA3_SUBSYSTEM('RPC_SVCCTL', + source='''svcctl/srv_svcctl_nt.c + svcctl/srv_svcctl_reg.c''', + deps='SERVICES LIBCLI_WINREG_INTERNAL') + +bld.SAMBA3_SUBSYSTEM('RPC_WINREG', + source='''winreg/srv_winreg_nt.c''', + deps='REG_FULL REGFIO NDR_PERFCOUNT') + +bld.SAMBA3_SUBSYSTEM('RPC_WKSSVC', + source='''wkssvc/srv_wkssvc_nt.c''', + deps='LIBNET') + +bld.SAMBA3_SUBSYSTEM('RPC_WITNESS', + source='''witness/srv_witness_nt.c''', + deps='samba-util samba-cluster-support samba3core', + enabled=bld.env.with_ctdb) + +bld.SAMBA3_SUBSYSTEM('mdssvc', + source=''' + mdssvc/dalloc.c + mdssvc/marshalling.c + ''') + +rpc_mdssvc_sources = ''' + mdssvc/mdssvc.c + mdssvc/mdssvc_noindex.c + mdssvc/srv_mdssvc_nt.c + ''' +rpc_mdssvc_deps = 'mdssvc samba-util smbd_base ' + +if bld.env.spotlight_backend_tracker: + rpc_mdssvc_sources += ''' + mdssvc/mdssvc_tracker.c + mdssvc/sparql_mapping.c + mdssvc/sparql_parser.y + mdssvc/sparql_lexer.l + ''' + rpc_mdssvc_deps += 'tevent-glib-glue ' + bld.env['libtracker'] + +if bld.env.spotlight_backend_es: + rpc_mdssvc_sources += ''' + mdssvc/mdssvc_es.c + mdssvc/es_mapping.c + mdssvc/es_parser.y + mdssvc/es_lexer.l + ''' + rpc_mdssvc_deps += ' http jansson' + + bld.INSTALL_FILES(bld.env.SAMBA_DATADIR, + 'mdssvc/elasticsearch_mappings.json') + +bld.SAMBA3_BINARY('rpcd_mdssvc', + source='rpcd_mdssvc.c ' + rpc_mdssvc_sources, + deps=''' + CMDLINE_S3 + RPC_WORKER + smbd_base + ''' + rpc_mdssvc_deps, + install_path='${SAMBA_LIBEXECDIR}') + +bld.SAMBA3_SUBSYSTEM('RPC_SERVICE', + source='', + deps=''' + rpc + RPC_SERVER + RPC_SAMR + RPC_LSARPC + RPC_WINREG + RPC_INITSHUTDOWN + RPC_DSSETUP + RPC_WKSSVC + RPC_SVCCTL + RPC_NTSVCS + RPC_NETLOGON + RPC_NETDFS + RPC_SRVSVC + RPC_IREMOTEWINSPOOL + RPC_EVENTLOG + RPC_RPCECHO + RPC_EPMAPPER + RPC_FSS_AGENT + ''') + +# RPC_DAEMONS +bld.SAMBA3_SUBSYSTEM('RPC_SOCK_HELPER', + source='rpc_sock_helper.c', + deps='') |