diff options
33 files changed, 1991 insertions, 251 deletions
@@ -99,7 +99,7 @@ SAMBA_VERSION_RC_RELEASE= # e.g. SAMBA_VERSION_IS_SVN_SNAPSHOT=yes # # -> "3.0.0-SVN-build-199" # ######################################################## -SAMBA_VERSION_IS_GIT_SNAPSHOT=no +SAMBA_VERSION_IS_GIT_SNAPSHOT=yes ######################################################## # This is for specifying a release nickname # diff --git a/ctdb/server/ctdb_recoverd.c b/ctdb/server/ctdb_recoverd.c index ee083e92fb1..3f5d43c1e87 100644 --- a/ctdb/server/ctdb_recoverd.c +++ b/ctdb/server/ctdb_recoverd.c @@ -2078,6 +2078,7 @@ static int verify_local_ip_allocation(struct ctdb_context *ctdb, /* Return early if disabled... */ if (ctdb_config.failover_disabled || ctdb_op_is_disabled(rec->takeover_run)) { + talloc_free(mem_ctx); return 0; } diff --git a/selftest/knownfail.d/kinit_mit b/selftest/knownfail_mit_krb5_pre_1_18 index ef1a3d5aa91..ef1a3d5aa91 100644 --- a/selftest/knownfail.d/kinit_mit +++ b/selftest/knownfail_mit_krb5_pre_1_18 diff --git a/selftest/selftest.pl b/selftest/selftest.pl index b6094fef3b9..b166b28e0cb 100755 --- a/selftest/selftest.pl +++ b/selftest/selftest.pl @@ -451,6 +451,7 @@ my $testenv_default = "none"; if ($opt_mitkrb5 == 1) { $ENV{MITKRB5} = $opt_mitkrb5; + $ENV{KRB5RCACHETYPE} = "none"; } # After this many seconds, the server will self-terminate. All tests diff --git a/selftest/skip b/selftest/skip index 11bf29599fa..549ba202021 100644 --- a/selftest/skip +++ b/selftest/skip @@ -75,6 +75,8 @@ ^samba3.smb2.durable-open-disconnect # Not a test, but a way to create a disconnected durable ^samba3.smb2.scan # No tests ^samba3.smb2.oplock.levelii501 # No test yet +^samba3.smb2.timestamp_resolution # See the comment on the test +^samba4.smb2.timestamp_resolution ^samba3.rpc.samr.passwords.lockout\(ad_dc\) # No point running this version, it just waits 12 times longer the samba4 version of this test, covering the same code ^samba4.base.iometer ^samba4.base.casetable diff --git a/selftest/wscript b/selftest/wscript index 4d03eb76842..501a5df5824 100644 --- a/selftest/wscript +++ b/selftest/wscript @@ -142,6 +142,9 @@ def cmd_testonly(opt): '--flapping=${srcdir}/selftest/flapping ' '--flapping=${srcdir}/selftest/flapping.d') + if CONFIG_GET(opt, 'HAVE_MIT_KRB5_PRE_1_18'): + env.FILTER_XFAIL += ' --expected-failures=${srcdir}/selftest/knownfail_mit_krb5_pre_1_18' + if Options.options.FAIL_IMMEDIATELY: env.FILTER_XFAIL += ' --fail-immediately' diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c index a630c5a0345..f0fcf9fcd56 100755 --- a/source3/libads/ldap.c +++ b/source3/libads/ldap.c @@ -1373,6 +1373,7 @@ char *ads_parent_dn(const char *dn) "userAccountControl", "DnsHostName", "ServicePrincipalName", + "userPrincipalName", "unicodePwd", /* Additional attributes Samba checks */ diff --git a/source3/librpc/crypto/gse.c b/source3/librpc/crypto/gse.c index 6675f4dc597..1cf111bd974 100644 --- a/source3/librpc/crypto/gse.c +++ b/source3/librpc/crypto/gse.c @@ -244,10 +244,6 @@ static NTSTATUS gse_context_init(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; err_out: - if (gse_ctx->k5ctx) { - krb5_free_context(gse_ctx->k5ctx); - } - TALLOC_FREE(gse_ctx); return status; } diff --git a/source3/modules/offload_token.c b/source3/modules/offload_token.c index 3fb84dabdff..03bb3309f38 100644 --- a/source3/modules/offload_token.c +++ b/source3/modules/offload_token.c @@ -280,6 +280,16 @@ NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl, return NT_STATUS_ACCESS_DENIED; } + if (src_fsp->closing) { + DBG_INFO("copy chunk src handle with closing in progress.\n"); + return NT_STATUS_ACCESS_DENIED; + } + + if (dst_fsp->closing) { + DBG_INFO("copy chunk dst handle with closing in progress.\n"); + return NT_STATUS_ACCESS_DENIED; + } + if (src_fsp->is_directory) { DBG_INFO("copy chunk no read on src directory handle (%s).\n", smb_fname_str_dbg(src_fsp->fsp_name)); diff --git a/source3/modules/test_vfs_full_audit.c b/source3/modules/test_vfs_full_audit.c new file mode 100644 index 00000000000..4a12e466ff3 --- /dev/null +++ b/source3/modules/test_vfs_full_audit.c @@ -0,0 +1,49 @@ +/* + * Unix SMB/CIFS implementation. + * + * Unit test for entries in vfs_full_audit arrays. + * + * Copyright (C) Jeremy Allison 2020 + * + * 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/>. + */ + +/* Needed for static build to complete... */ +#include "includes.h" +#include "smbd/smbd.h" +NTSTATUS vfs_full_audit_init(TALLOC_CTX *ctx); + +#include "vfs_full_audit.c" +#include <cmocka.h> + +static void test_full_audit_array(void **state) +{ + unsigned i; + + for (i=0; i<SMB_VFS_OP_LAST; i++) { + assert_non_null(vfs_op_names[i].name); + assert_int_equal(vfs_op_names[i].type, i); + } +} + +int main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_full_audit_array), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source3/modules/vfs_aio_pthread.c b/source3/modules/vfs_aio_pthread.c index d13ce2fdc63..1ccf89a6d8c 100644 --- a/source3/modules/vfs_aio_pthread.c +++ b/source3/modules/vfs_aio_pthread.c @@ -51,6 +51,7 @@ struct aio_open_private_data { const char *fname; char *dname; connection_struct *conn; + struct smbXsrv_connection *xconn; const struct security_unix_token *ux_tok; uint64_t initial_allocation_size; /* Returns. */ @@ -62,6 +63,7 @@ struct aio_open_private_data { static struct aio_open_private_data *open_pd_list; static void aio_open_do(struct aio_open_private_data *opd); +static void opd_free(struct aio_open_private_data *opd); /************************************************************************ Find the open private data by mid. @@ -90,10 +92,40 @@ static void aio_open_handle_completion(struct tevent_req *subreq) tevent_req_callback_data(subreq, struct aio_open_private_data); int ret; - struct smbXsrv_connection *xconn; ret = pthreadpool_tevent_job_recv(subreq); TALLOC_FREE(subreq); + + /* + * We're no longer in flight. Remove the + * destructor used to preserve opd so + * a talloc_free actually removes it. + */ + talloc_set_destructor(opd, NULL); + + if (opd->conn == NULL) { + /* + * We were shutdown closed in flight. No one + * wants the result, and state has been reparented + * to the NULL context, so just free it so we + * don't leak memory. + */ + DBG_NOTICE("aio open request for %s/%s abandoned in flight\n", + opd->dname, + opd->fname); + if (opd->ret_fd != -1) { + close(opd->ret_fd); + opd->ret_fd = -1; + } + /* + * Find outstanding event and reschedule so the client + * gets an error message return from the open. + */ + schedule_deferred_open_message_smb(opd->xconn, opd->mid); + opd_free(opd); + return; + } + if (ret != 0) { bool ok; @@ -127,15 +159,8 @@ static void aio_open_handle_completion(struct tevent_req *subreq) opd->in_progress = false; - /* - * TODO: In future we need a proper algorithm - * to find the correct connection for a fsp. - * For now we only have one connection, so this is correct... - */ - xconn = opd->conn->sconn->client->connections; - /* Find outstanding event and reschedule. */ - if (!schedule_deferred_open_message_smb(xconn, opd->mid)) { + if (!schedule_deferred_open_message_smb(opd->xconn, opd->mid)) { /* * Outstanding event didn't exist or was * cancelled. Free up the fd and throw @@ -145,7 +170,7 @@ static void aio_open_handle_completion(struct tevent_req *subreq) close(opd->ret_fd); opd->ret_fd = -1; } - TALLOC_FREE(opd); + opd_free(opd); } } @@ -207,27 +232,28 @@ static void aio_open_do(struct aio_open_private_data *opd) } /************************************************************************ - Open private data destructor. + Open private data teardown. ***********************************************************************/ -static int opd_destructor(struct aio_open_private_data *opd) +static void opd_free(struct aio_open_private_data *opd) { if (opd->dir_fd != -1) { close(opd->dir_fd); } DLIST_REMOVE(open_pd_list, opd); - return 0; + TALLOC_FREE(opd); } /************************************************************************ Create and initialize a private data struct for async open. ***********************************************************************/ -static struct aio_open_private_data *create_private_open_data(const files_struct *fsp, +static struct aio_open_private_data *create_private_open_data(TALLOC_CTX *ctx, + const files_struct *fsp, int flags, mode_t mode) { - struct aio_open_private_data *opd = talloc_zero(NULL, + struct aio_open_private_data *opd = talloc_zero(ctx, struct aio_open_private_data); const char *fname = NULL; @@ -244,13 +270,19 @@ static struct aio_open_private_data *create_private_open_data(const files_struct .mid = fsp->mid, .in_progress = true, .conn = fsp->conn, + /* + * TODO: In future we need a proper algorithm + * to find the correct connection for a fsp. + * For now we only have one connection, so this is correct... + */ + .xconn = fsp->conn->sconn->client->connections, .initial_allocation_size = fsp->initial_allocation_size, }; /* Copy our current credentials. */ opd->ux_tok = copy_unix_token(opd, get_current_utok(fsp->conn)); if (opd->ux_tok == NULL) { - TALLOC_FREE(opd); + opd_free(opd); return NULL; } @@ -262,12 +294,12 @@ static struct aio_open_private_data *create_private_open_data(const files_struct fsp->fsp_name->base_name, &opd->dname, &fname) == false) { - TALLOC_FREE(opd); + opd_free(opd); return NULL; } opd->fname = talloc_strdup(opd, fname); if (opd->fname == NULL) { - TALLOC_FREE(opd); + opd_free(opd); return NULL; } @@ -277,15 +309,30 @@ static struct aio_open_private_data *create_private_open_data(const files_struct opd->dir_fd = open(opd->dname, O_RDONLY); #endif if (opd->dir_fd == -1) { - TALLOC_FREE(opd); + opd_free(opd); return NULL; } - talloc_set_destructor(opd, opd_destructor); DLIST_ADD_END(open_pd_list, opd); return opd; } +static int opd_inflight_destructor(struct aio_open_private_data *opd) +{ + /* + * Setting conn to NULL allows us to + * discover the connection was torn + * down which kills the fsp that owns + * opd. + */ + DBG_NOTICE("aio open request for %s/%s cancelled\n", + opd->dname, + opd->fname); + opd->conn = NULL; + /* Don't let opd go away. */ + return -1; +} + /***************************************************************** Setup an async open. *****************************************************************/ @@ -297,7 +344,18 @@ static int open_async(const files_struct *fsp, struct aio_open_private_data *opd = NULL; struct tevent_req *subreq = NULL; - opd = create_private_open_data(fsp, flags, mode); + /* + * Allocate off fsp->conn, not NULL or fsp. As we're going + * async fsp will get talloc_free'd when we return + * EINPROGRESS/NT_STATUS_MORE_PROCESSING_REQUIRED. A new fsp + * pointer gets allocated on every re-run of the + * open code path. Allocating on fsp->conn instead + * of NULL allows use to get notified via destructor + * if the conn is force-closed or we shutdown. + * opd is always safely freed in all codepath so no + * memory leaks. + */ + opd = create_private_open_data(fsp->conn, fsp, flags, mode); if (opd == NULL) { DEBUG(10, ("open_async: Could not create private data.\n")); return -1; @@ -308,6 +366,7 @@ static int open_async(const files_struct *fsp, fsp->conn->sconn->pool, aio_open_worker, opd); if (subreq == NULL) { + opd_free(opd); return -1; } tevent_req_set_callback(subreq, aio_open_handle_completion, opd); @@ -317,6 +376,12 @@ static int open_async(const files_struct *fsp, opd->dname, opd->fname)); + /* + * Add a destructor to protect us from connection + * teardown whilst the open thread is in flight. + */ + talloc_set_destructor(opd, opd_inflight_destructor); + /* Cause the calling code to reschedule us. */ errno = EINPROGRESS; /* Maps to NT_STATUS_MORE_PROCESSING_REQUIRED. */ return -1; @@ -364,7 +429,7 @@ static bool find_completed_open(files_struct *fsp, smb_fname_str_dbg(fsp->fsp_name))); /* Now we can free the opd. */ - TALLOC_FREE(opd); + opd_free(opd); return true; } diff --git a/source3/modules/vfs_full_audit.c b/source3/modules/vfs_full_audit.c index 5c8267dea9f..b0237cdacba 100644 --- a/source3/modules/vfs_full_audit.c +++ b/source3/modules/vfs_full_audit.c @@ -295,6 +295,7 @@ static struct { { SMB_VFS_OP_FALLOCATE,"fallocate" }, { SMB_VFS_OP_LOCK, "lock" }, { SMB_VFS_OP_KERNEL_FLOCK, "kernel_flock" }, + { SMB_VFS_OP_FCNTL, "fcntl" }, { SMB_VFS_OP_LINUX_SETLEASE, "linux_setlease" }, { SMB_VFS_OP_GETLOCK, "getlock" }, { SMB_VFS_OP_SYMLINKAT, "symlinkat" }, diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build index 41d8568e43a..d4c87209106 100644 --- a/source3/modules/wscript_build +++ b/source3/modules/wscript_build @@ -91,6 +91,11 @@ bld.SAMBA3_MODULE('vfs_full_audit', internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_full_audit'), enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_full_audit')) +bld.SAMBA3_BINARY('test_vfs_full_audit', + source='test_vfs_full_audit.c', + deps='smbd_base cmocka', + for_selftest=True) + bld.SAMBA3_MODULE('vfs_fake_perms', subsystem='vfs', source='vfs_fake_perms.c', diff --git a/source3/script/tests/test_force_close_share.sh b/source3/script/tests/test_force_close_share.sh new file mode 100755 index 00000000000..da78b5a752e --- /dev/null +++ b/source3/script/tests/test_force_close_share.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# +# Test smbcontrol close-share command. +# +# Copyright (C) 2020 Volker Lendecke +# Copyright (C) 2020 Jeremy Allison +# +# Note this is designed to be run against +# the aio_delay_inject share which is preconfigured +# with 2 second delays on pread/pwrite. + +if [ $# -lt 5 ]; then + echo Usage: test_share_force_close.sh \ + SERVERCONFFILE SMBCLIENT SMBCONTROL IP aio_delay_inject_sharename +exit 1 +fi + +CONF=$1 +SMBCLIENT=$2 +SMBCONTROL=$3 +SERVER=$4 +SHARE=$5 + +incdir=$(dirname $0)/../../../testprogs/blackbox +. $incdir/subunit.sh + +failed=0 + +# Create the smbclient communication pipes. +rm -f smbclient-stdin smbclient-stdout smbclient-stderr +mkfifo smbclient-stdin smbclient-stdout smbclient-stderr + +# Create a large-ish testfile +rm testfile +head -c 20MB /dev/zero >testfile + +CLI_FORCE_INTERACTIVE=1; export CLI_FORCE_INTERACTIVE + +${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \ + < smbclient-stdin > smbclient-stdout 2>smbclient-stderr & +CLIENT_PID=$! + +sleep 1 + +exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr + +# consume the smbclient startup messages +head -n 1 <&101 +head -n 1 <&102 + +# Ensure we're putting a fresh file. +echo "del testfile" >&100 +echo "put testfile" >&100 + +sleep 1 + +# Close the aio_delay_inject share whilst we have outstanding writes. + +testit "smbcontrol" ${SMBCONTROL} ${CONF} smbd close-share ${SHARE} || + failed=$(expr $failed + 1) + +sleep 1 + +# If we get one or more NT_STATUS_NETWORK_NAME_DELETED +# or NT_STATUS_INVALID_HANDLE on stderr from the writes we +# know the server stayed up and didn't crash when the +# close-share removed the share. +# +# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14301 +# +COUNT=$(head -n 2 <&102 | + grep -e NT_STATUS_NETWORK_NAME_DELETED -e NT_STATUS_INVALID_HANDLE | + wc -l) + +testit "Verify close-share did cancel the file put" \ + test $COUNT -ge 1 || failed=$(expr $failed + 1) + +kill ${CLIENT_PID} + +# Rerun smbclient to remove the testfile on the server. +rm -f smbclient-stdin smbclient-stdout smbclient-stderr testfile +mkfifo smbclient-stdin smbclient-stdout + +${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \ + < smbclient-stdin > smbclient-stdout & +CLIENT_PID=$! + +sleep 1 + +exec 100>smbclient-stdin 101<smbclient-stdout + +echo "del testfile" >&100 + +sleep 1 + +kill ${CLIENT_PID} + +rm -f smbclient-stdin smbclient-stdout testfile + +testok $0 $failed diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py index dddcb03230c..68ca1eac6e8 100755 --- a/source3/selftest/tests.py +++ b/source3/selftest/tests.py @@ -466,6 +466,10 @@ plantestsuite("samba3.test_nfs4_acl", "none", [os.path.join(bindir(), "test_nfs4_acls"), "$SMB_CONF_PATH"]) +plantestsuite("samba3.test_vfs_full_audit", "none", + [os.path.join(bindir(), "test_vfs_full_audit"), + "$SMB_CONF_PATH"]) + plantestsuite( "samba3.resolvconf", "none", [os.path.join(samba3srcdir, "script/tests/test_resolvconf.sh")]) @@ -802,6 +806,15 @@ plantestsuite("samba3.blackbox.close-denied-share", "simpleserver:local", '$SERVER_IP', "tmp"]) +plantestsuite("samba3.blackbox.force-close-share", "simpleserver:local", + [os.path.join(samba3srcdir, + "script/tests/test_force_close_share.sh"), + configuration, + os.path.join(bindir(), "smbclient"), + os.path.join(bindir(), "smbcontrol"), + '$SERVER_IP', + "aio_delay_inject"]) + plantestsuite("samba3.blackbox.open-eintr", "simpleserver:local", [os.path.join(samba3srcdir, "script/tests/test_open_eintr.sh"), diff --git a/source3/smbd/aio.c b/source3/smbd/aio.c index 0f824f5aa1f..cf35f3297ec 100644 --- a/source3/smbd/aio.c +++ b/source3/smbd/aio.c @@ -103,6 +103,7 @@ static int aio_del_req_from_fsp(struct aio_req_fsp_link *lnk) if (fsp->num_aio_requests == 0) { tevent_wait_done(fsp->deferred_close); + TALLOC_FREE(fsp->aio_requests); } return 0; } @@ -121,9 +122,19 @@ bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req) if (array_len <= fsp->num_aio_requests) { struct tevent_req **tmp; + if (fsp->num_aio_requests + 10 < 10) { + /* Integer wrap. */ + TALLOC_FREE(lnk); + return false; + } + + /* + * Allocate in blocks of 10 so we don't allocate + * on every aio request. + */ tmp = talloc_realloc( fsp, fsp->aio_requests, struct tevent_req *, - fsp->num_aio_requests+1); + fsp->num_aio_requests+10); if (tmp == NULL) { TALLOC_FREE(lnk); return false; diff --git a/source3/smbd/close.c b/source3/smbd/close.c index f45371e656c..d5af62a277c 100644 --- a/source3/smbd/close.c +++ b/source3/smbd/close.c @@ -31,6 +31,7 @@ #include "auth.h" #include "messages.h" #include "../librpc/gen_ndr/open_files.h" +#include "lib/util/tevent_ntstatus.h" /**************************************************************************** Run a file if it is a magic script. @@ -635,6 +636,20 @@ static NTSTATUS ntstatus_keeperror(NTSTATUS s1, NTSTATUS s2) return s2; } +static void assert_no_pending_aio(struct files_struct *fsp, + enum file_close_type close_type) +{ + unsigned num_requests = fsp->num_aio_requests; + + if (num_requests == 0) { + return; + } + + DBG_ERR("fsp->num_aio_requests=%u\n", num_requests); + smb_panic("can not close with outstanding aio requests"); + return; +} + /**************************************************************************** Close a file. @@ -651,45 +666,7 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp, connection_struct *conn = fsp->conn; bool is_durable = false; - if (fsp->num_aio_requests != 0) { - - if (close_type != SHUTDOWN_CLOSE) { - /* - * reply_close and the smb2 close must have - * taken care of this. No other callers of - * close_file should ever have created async - * I/O. - * - * We need to panic here because if we close() - * the fd while we have outstanding async I/O - * requests, in the worst case we could end up - * writing to the wrong file. - */ - DEBUG(0, ("fsp->num_aio_requests=%u\n", - fsp->num_aio_requests)); - smb_panic("can not close with outstanding aio " - "requests"); - } - - /* - * For shutdown close, just drop the async requests - * including a potential close request pending for - * this fsp. Drop the close request first, the - * destructor for the aio_requests would execute it. - */ - TALLOC_FREE(fsp->deferred_close); - - while (fsp->num_aio_requests != 0) { - /* - * The destructor of the req will remove - * itself from the fsp. - * Don't use TALLOC_FREE here, this will overwrite - * what the destructor just wrote into - * aio_requests[0]. - */ - talloc_free(fsp->aio_requests[0]); - } - } + assert_no_pending_aio(fsp, close_type); while (talloc_array_length(fsp->blocked_smb1_lock_reqs) != 0) { smbd_smb1_brl_finish_by_req( @@ -1134,30 +1111,7 @@ static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp, notify_status = NT_STATUS_OK; } - if (fsp->num_aio_requests != 0) { - if (close_type != SHUTDOWN_CLOSE) { - /* - * We panic here because if we close() the fd while we - * have outstanding async I/O requests, an async IO - * request might use the fd. For directories the fd is - * read-only, so this is not as bad as with files, but - * still, better safe then sorry. - */ - DBG_ERR("fsp->num_aio_requests=%u\n", - fsp->num_aio_requests); - smb_panic("close with outstanding aio requests"); - return NT_STATUS_INTERNAL_ERROR; - } - - while (fsp->num_aio_requests != 0) { - /* - * The destructor of the req will remove itself from the - * fsp. Don't use TALLOC_FREE here, this will overwrite - * what the destructor just wrote into aio_requests[0]. - */ - talloc_free(fsp->aio_requests[0]); - } - } + assert_no_pending_aio(fsp, close_type); /* * NT can set delete_on_close of the last open diff --git a/source3/smbd/conn_idle.c b/source3/smbd/conn_idle.c index cd12e3f1266..cf5a417bff7 100644 --- a/source3/smbd/conn_idle.c +++ b/source3/smbd/conn_idle.c @@ -23,6 +23,7 @@ #include "smbd/smbd.h" #include "smbd/globals.h" #include "rpc_server/rpc_pipes.h" +#include "lib/util/tevent_ntstatus.h" /**************************************************************************** Update last used timestamps. @@ -76,61 +77,201 @@ bool conn_idle_all(struct smbd_server_connection *sconn, time_t t) } /**************************************************************************** - Forcibly unmount a share. + Forcibly unmount a share - async All instances of the parameter 'sharename' share are unmounted. The special sharename '*' forces unmount of all shares. ****************************************************************************/ +static struct tevent_req *conn_force_tdis_send(connection_struct *conn); +static void conn_force_tdis_done(struct tevent_req *req); + void conn_force_tdis( struct smbd_server_connection *sconn, bool (*check_fn)(struct connection_struct *conn, void *private_data), void *private_data) { - connection_struct *conn, *next; + connection_struct *conn; /* SMB1 and SMB 2*/ - for (conn = sconn->connections; conn; conn = next) { + for (conn = sconn->connections; conn; conn = conn->next) { struct smbXsrv_tcon *tcon; bool do_close = false; - NTSTATUS status; - uint64_t vuid = UID_FIELD_INVALID; - - next = conn->next; + struct tevent_req *req; if (conn->tcon == NULL) { continue; } tcon = conn->tcon; + if (!NT_STATUS_IS_OK(tcon->status)) { + /* In the process of already being disconnected. */ + continue; + } + do_close = check_fn(conn, private_data); if (!do_close) { continue; } + req = conn_force_tdis_send(conn); + if (req == NULL) { + DBG_WARNING("talloc_fail forcing async close of " + "share '%s'\n", + tcon->global->share_name); + continue; + } + DBG_WARNING("Forcing close of " "share '%s' (wire_id=0x%08x)\n", tcon->global->share_name, tcon->global->tcon_wire_id); - if (sconn->using_smb2) { - vuid = conn->vuid; + tevent_req_set_callback(req, conn_force_tdis_done, conn); + } +} + +struct conn_force_tdis_state { + struct tevent_queue *wait_queue; +}; + +static void conn_force_tdis_wait_done(struct tevent_req *subreq); + +static struct tevent_req *conn_force_tdis_send(connection_struct *conn) +{ + struct tevent_req *req; + struct conn_force_tdis_state *state; + struct tevent_req *subreq; + files_struct *fsp; + + /* Create this off the NULL context. We must clean up on return. */ + req = tevent_req_create(NULL, &state, + struct conn_force_tdis_state); + if (req == NULL) { + return NULL; + } + state->wait_queue = tevent_queue_create(state, + "conn_force_tdis_wait_queue"); + if (tevent_req_nomem(state->wait_queue, req)) { + TALLOC_FREE(req); + return NULL; + } + + /* + * Make sure that no new request will be able to use this tcon. + * This ensures that once all outstanding fsp->aio_requests + * on this tcon are done, we are safe to close it. + */ + conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED; + + for (fsp = conn->sconn->files; fsp; fsp = fsp->next) { + if (fsp->conn != conn) { + continue; } + /* + * Flag the file as close in progress. + * This will prevent any more IO being + * done on it. Not strictly needed, but + * doesn't hurt to flag it as closing. + */ + fsp->closing = true; - conn = NULL; - status = smbXsrv_tcon_disconnect(tcon, vuid); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(0, ("conn_force_tdis: " - "smbXsrv_tcon_disconnect() of share '%s' " - "(wire_id=0x%08x) failed: %s\n", - tcon->global->share_name, - tcon->global->tcon_wire_id, - nt_errstr(status))); + if (fsp->num_aio_requests > 0) { + /* + * Now wait until all aio requests on this fsp are + * finished. + * + * We don't set a callback, as we just want to block the + * wait queue and the talloc_free() of fsp->aio_request + * will remove the item from the wait queue. + */ + subreq = tevent_queue_wait_send(fsp->aio_requests, + conn->sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } } + } + /* + * Now we add our own waiter to the end of the queue, + * this way we get notified when all pending requests are finished + * and reply to the outstanding SMB1 request. + */ + subreq = tevent_queue_wait_send(state, + conn->sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + + tevent_req_set_callback(subreq, conn_force_tdis_wait_done, req); + return req; +} + +static void conn_force_tdis_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + + tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static NTSTATUS conn_force_tdis_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static void conn_force_tdis_done(struct tevent_req *req) +{ + connection_struct *conn = tevent_req_callback_data( + req, connection_struct); + NTSTATUS status; + uint64_t vuid = UID_FIELD_INVALID; + struct smbXsrv_tcon *tcon = conn->tcon; + struct smbd_server_connection *sconn = conn->sconn; + + status = conn_force_tdis_recv(req); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("conn_force_tdis_recv of share '%s' " + "(wire_id=0x%08x) failed: %s\n", + tcon->global->share_name, + tcon->global->tcon_wire_id, + nt_errstr(status)); + return; + } - TALLOC_FREE(tcon); + if (conn->sconn->using_smb2) { + vuid = conn->vuid; } + DBG_WARNING("Closing " + "share '%s' (wire_id=0x%08x)\n", + tcon->global->share_name, + tcon->global->tcon_wire_id); + + conn = NULL; + status = smbXsrv_tcon_disconnect(tcon, vuid); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("smbXsrv_tcon_disconnect() of share '%s' " + "(wire_id=0x%08x) failed: %s\n", + tcon->global->share_name, + tcon->global->tcon_wire_id, + nt_errstr(status)); + return; + } + + TALLOC_FREE(tcon); + + /* + * As we've been awoken, we may have changed + * uid in the meantime. Ensure we're still root. + */ change_to_root_user(); reload_services(sconn, conn_snum_used, true); } diff --git a/source3/smbd/fileio.c b/source3/smbd/fileio.c index 029965282f1..31d5b7510b7 100644 --- a/source3/smbd/fileio.c +++ b/source3/smbd/fileio.c @@ -104,15 +104,7 @@ void fsp_flush_write_time_update(struct files_struct *fsp) DEBUG(5, ("Update write time on %s\n", fsp_str_dbg(fsp))); - /* change the write time in the open file db. */ - (void)set_write_time(fsp->file_id, timespec_current()); - - /* And notify. */ - notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED, - FILE_NOTIFY_CHANGE_LAST_WRITE, fsp->fsp_name->base_name); - - /* Remove the timed event handler. */ - TALLOC_FREE(fsp->update_write_time_event); + trigger_write_time_update_immediate(fsp); } static void update_write_time_handler(struct tevent_context *ctx, @@ -214,17 +206,14 @@ void mark_file_modified(files_struct *fsp) { int dosmode; + trigger_write_time_update(fsp); + if (fsp->modified) { return; } fsp->modified = true; - if (SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st) != 0) { - return; - } - trigger_write_time_update(fsp); - if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) { return; } diff --git a/source3/smbd/files.c b/source3/smbd/files.c index 99b2f343685..a982c0a5980 100644 --- a/source3/smbd/files.c +++ b/source3/smbd/files.c @@ -189,23 +189,6 @@ void file_close_conn(connection_struct *conn) } /**************************************************************************** - Close all open files for a pid and a vuid. -****************************************************************************/ - -void file_close_pid(struct smbd_server_connection *sconn, uint16_t smbpid, - uint64_t vuid) -{ - files_struct *fsp, *next; - - for (fsp=sconn->files;fsp;fsp=next) { - next = fsp->next; - if ((fsp->file_pid == smbpid) && (fsp->vuid == vuid)) { - close_file(NULL, fsp, SHUTDOWN_CLOSE); - } - } -} - -/**************************************************************************** Initialise file structures. ****************************************************************************/ @@ -598,6 +581,9 @@ files_struct *file_fsp(struct smb_request *req, uint16_t fid) if (req->chain_fsp->deferred_close) { return NULL; } + if (req->chain_fsp->closing) { + return NULL; + } return req->chain_fsp; } @@ -622,6 +608,10 @@ files_struct *file_fsp(struct smb_request *req, uint16_t fid) return NULL; } + if (fsp->closing) { + return NULL; + } + req->chain_fsp = fsp; return fsp; } @@ -669,6 +659,10 @@ struct files_struct *file_fsp_get(struct smbd_smb2_request *smb2req, return NULL; } + if (fsp->closing) { + return NULL; + } + return fsp; } @@ -682,6 +676,9 @@ struct files_struct *file_fsp_smb2(struct smbd_smb2_request *smb2req, if (smb2req->compat_chain_fsp->deferred_close) { return NULL; } + if (smb2req->compat_chain_fsp->closing) { + return NULL; + } return smb2req->compat_chain_fsp; } diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index 96d574023a5..911fe6dad3c 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -379,8 +379,6 @@ void fsp_set_gen_id(files_struct *fsp); NTSTATUS file_new(struct smb_request *req, connection_struct *conn, files_struct **result); void file_close_conn(connection_struct *conn); -void file_close_pid(struct smbd_server_connection *sconn, uint16_t smbpid, - uint64_t vuid); bool file_init_global(void); bool file_init(struct smbd_server_connection *sconn); void file_close_user(struct smbd_server_connection *sconn, uint64_t vuid); diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index 40cd7483750..54f3d330c95 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -42,6 +42,7 @@ #include "smbprofile.h" #include "../lib/tsocket/tsocket.h" #include "lib/tevent_wait.h" +#include "lib/util/tevent_ntstatus.h" #include "libcli/smb/smb_signing.h" #include "lib/util/sys_rw_data.h" #include "librpc/gen_ndr/open_files.h" @@ -2611,40 +2612,196 @@ void reply_open_and_X(struct smb_request *req) Reply to a SMBulogoffX. ****************************************************************************/ -void reply_ulogoffX(struct smb_request *req) +static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req, + struct smbXsrv_session *session); +static void reply_ulogoffX_done(struct tevent_req *req); + +void reply_ulogoffX(struct smb_request *smb1req) { struct timeval now = timeval_current(); struct smbXsrv_session *session = NULL; + struct tevent_req *req; NTSTATUS status; - START_PROFILE(SMBulogoffX); + /* + * Don't setup the profile charge here, take + * it in reply_ulogoffX_done(). Not strictly correct + * but better than the other SMB1 async + * code that double-charges at the moment. + */ - status = smb1srv_session_lookup(req->xconn, - req->vuid, + status = smb1srv_session_lookup(smb1req->xconn, + smb1req->vuid, timeval_to_nttime(&now), &session); if (!NT_STATUS_IS_OK(status)) { - DEBUG(3,("ulogoff, vuser id %llu does not map to user.\n", - (unsigned long long)req->vuid)); + /* Not going async, profile here. */ + START_PROFILE(SMBulogoffX); + DBG_WARNING("ulogoff, vuser id %llu does not map to user.\n", + (unsigned long long)smb1req->vuid); - req->vuid = UID_FIELD_INVALID; - reply_force_doserror(req, ERRSRV, ERRbaduid); + smb1req->vuid = UID_FIELD_INVALID; + reply_force_doserror(smb1req, ERRSRV, ERRbaduid); + END_PROFILE(SMBulogoffX); + return; + } + + req = reply_ulogoffX_send(smb1req, session); + if (req == NULL) { + /* Not going async, profile here. */ + START_PROFILE(SMBulogoffX); + reply_force_doserror(smb1req, ERRDOS, ERRnomem); END_PROFILE(SMBulogoffX); return; } + /* We're async. This will complete later. */ + tevent_req_set_callback(req, reply_ulogoffX_done, smb1req); + return; +} + +struct reply_ulogoffX_state { + struct tevent_queue *wait_queue; + struct smbXsrv_session *session; +}; + +static void reply_ulogoffX_wait_done(struct tevent_req *subreq); + +/**************************************************************************** + Async SMB1 ulogoffX. + Note, on failure here we deallocate and return NULL to allow the caller to + SMB1 return an error of ERRnomem immediately. +****************************************************************************/ + +static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req, + struct smbXsrv_session *session) +{ + struct tevent_req *req; + struct reply_ulogoffX_state *state; + struct tevent_req *subreq; + files_struct *fsp; + struct smbd_server_connection *sconn = session->client->sconn; + uint64_t vuid = session->global->session_wire_id; + + req = tevent_req_create(smb1req, &state, + struct reply_ulogoffX_state); + if (req == NULL) { + return NULL; + } + state->wait_queue = tevent_queue_create(state, + "reply_ulogoffX_wait_queue"); + if (tevent_req_nomem(state->wait_queue, req)) { + TALLOC_FREE(req); + return NULL; + } + state->session = session; + /* - * TODO: cancel all outstanding requests on the session + * Make sure that no new request will be able to use this session. + * This ensures that once all outstanding fsp->aio_requests + * on this session are done, we are safe to close it. */ - status = smbXsrv_session_logoff(session); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(0, ("reply_ulogoff: " - "smbXsrv_session_logoff() failed: %s\n", - nt_errstr(status))); + session->status = NT_STATUS_USER_SESSION_DELETED; + + for (fsp = sconn->files; fsp; fsp = fsp->next) { + if (fsp->vuid != vuid) { + continue; + } /* - * If we hit this case, there is something completely - * wrong, so we better disconnect the transport connection. + * Flag the file as close in progress. + * This will prevent any more IO being + * done on it. */ + fsp->closing = true; + + if (fsp->num_aio_requests > 0) { + /* + * Now wait until all aio requests on this fsp are + * finished. + * + * We don't set a callback, as we just want to block the + * wait queue and the talloc_free() of fsp->aio_request + * will remove the item from the wait queue. + */ + subreq = tevent_queue_wait_send(fsp->aio_requests, + sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + } + } + + /* + * Now we add our own waiter to the end of the queue, + * this way we get notified when all pending requests are finished + * and reply to the outstanding SMB1 request. + */ + subreq = tevent_queue_wait_send(state, + sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + + /* + * We're really going async - move the SMB1 request from + * a talloc stackframe above us to the sconn talloc-context. + * We need this to stick around until the wait_done + * callback is invoked. + */ + smb1req = talloc_move(sconn, &smb1req); + + tevent_req_set_callback(subreq, reply_ulogoffX_wait_done, req); + + return req; +} + +static void reply_ulogoffX_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + + tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static NTSTATUS reply_ulogoffX_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static void reply_ulogoffX_done(struct tevent_req *req) +{ + struct smb_request *smb1req = tevent_req_callback_data( + req, struct smb_request); + struct reply_ulogoffX_state *state = tevent_req_data(req, + struct reply_ulogoffX_state); + struct smbXsrv_session *session = state->session; + NTSTATUS status; + + /* + * Take the profile charge here. Not strictly + * correct but better than the other SMB1 async + * code that double-charges at the moment. + */ + START_PROFILE(SMBulogoffX); + + status = reply_ulogoffX_recv(req); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb1req); + END_PROFILE(SMBulogoffX); + exit_server(__location__ ": reply_ulogoffX_recv failed"); + return; + } + + status = smbXsrv_session_logoff(session); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb1req); END_PROFILE(SMBulogoffX); exit_server(__location__ ": smbXsrv_session_logoff failed"); return; @@ -2652,15 +2809,21 @@ void reply_ulogoffX(struct smb_request *req) TALLOC_FREE(session); - reply_outbuf(req, 2, 0); - SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */ - SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */ + reply_outbuf(smb1req, 2, 0); + SSVAL(smb1req->outbuf, smb_vwv0, 0xff); /* andx chain ends */ + SSVAL(smb1req->outbuf, smb_vwv1, 0); /* no andx offset */ - DEBUG(3, ("ulogoffX vuid=%llu\n", - (unsigned long long)req->vuid)); + DBG_NOTICE("ulogoffX vuid=%llu\n", + (unsigned long long)smb1req->vuid); + smb1req->vuid = UID_FIELD_INVALID; + /* + * The following call is needed to push the + * reply data back out the socket after async + * return. Plus it frees smb1req. + */ + smb_request_done(smb1req); END_PROFILE(SMBulogoffX); - req->vuid = UID_FIELD_INVALID; } /**************************************************************************** @@ -5538,6 +5701,10 @@ static struct files_struct *file_sync_one_fn(struct files_struct *fsp, } sync_file(conn, fsp, True /* write through */); + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NULL; } @@ -5576,6 +5743,9 @@ void reply_flush(struct smb_request *req) END_PROFILE(SMBflush); return; } + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } } reply_outbuf(req, 0, 0); @@ -5590,16 +5760,228 @@ void reply_flush(struct smb_request *req) conn POINTER CAN BE NULL HERE ! ****************************************************************************/ -void reply_exit(struct smb_request *req) +static struct tevent_req *reply_exit_send(struct smb_request *smb1req); +static void reply_exit_done(struct tevent_req *req); + +void reply_exit(struct smb_request *smb1req) +{ + struct tevent_req *req; + + /* + * Don't setup the profile charge here, take + * it in reply_exit_done(). Not strictly correct + * but better than the other SMB1 async + * code that double-charges at the moment. + */ + req = reply_exit_send(smb1req); + if (req == NULL) { + /* Not going async, profile here. */ + START_PROFILE(SMBexit); + reply_force_doserror(smb1req, ERRDOS, ERRnomem); + END_PROFILE(SMBexit); + return; + } + + /* We're async. This will complete later. */ + tevent_req_set_callback(req, reply_exit_done, smb1req); + return; +} + +struct reply_exit_state { + struct tevent_queue *wait_queue; +}; + +static void reply_exit_wait_done(struct tevent_req *subreq); + +/**************************************************************************** + Async SMB1 exit. + Note, on failure here we deallocate and return NULL to allow the caller to + SMB1 return an error of ERRnomem immediately. +****************************************************************************/ + +static struct tevent_req *reply_exit_send(struct smb_request *smb1req) +{ + struct tevent_req *req; + struct reply_exit_state *state; + struct tevent_req *subreq; + files_struct *fsp; + struct smbd_server_connection *sconn = smb1req->sconn; + + req = tevent_req_create(smb1req, &state, + struct reply_exit_state); + if (req == NULL) { + return NULL; + } + state->wait_queue = tevent_queue_create(state, + "reply_exit_wait_queue"); + if (tevent_req_nomem(state->wait_queue, req)) { + TALLOC_FREE(req); + return NULL; + } + + for (fsp = sconn->files; fsp; fsp = fsp->next) { + if (fsp->file_pid != smb1req->smbpid) { + continue; + } + if (fsp->vuid != smb1req->vuid) { + continue; + } + /* + * Flag the file as close in progress. + * This will prevent any more IO being + * done on it. + */ + fsp->closing = true; + + if (fsp->num_aio_requests > 0) { + /* + * Now wait until all aio requests on this fsp are + * finished. + * + * We don't set a callback, as we just want to block the + * wait queue and the talloc_free() of fsp->aio_request + * will remove the item from the wait queue. + */ + subreq = tevent_queue_wait_send(fsp->aio_requests, + sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + } + } + + /* + * Now we add our own waiter to the end of the queue, + * this way we get notified when all pending requests are finished + * and reply to the outstanding SMB1 request. + */ + subreq = tevent_queue_wait_send(state, + sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + + /* + * We're really going async - move the SMB1 request from + * a talloc stackframe above us to the conn talloc-context. + * We need this to stick around until the wait_done + * callback is invoked. + */ + smb1req = talloc_move(sconn, &smb1req); + + tevent_req_set_callback(subreq, reply_exit_wait_done, req); + + return req; +} + +static void reply_exit_wait_done(struct tevent_req *subreq) { + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + + tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static NTSTATUS reply_exit_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static void reply_exit_done(struct tevent_req *req) +{ + struct smb_request *smb1req = tevent_req_callback_data( + req, struct smb_request); + struct smbd_server_connection *sconn = smb1req->sconn; + struct smbXsrv_connection *xconn = smb1req->xconn; + NTTIME now = timeval_to_nttime(&smb1req->request_time); + struct smbXsrv_session *session = NULL; + files_struct *fsp, *next; + NTSTATUS status; + + /* + * Take the profile charge here. Not strictly + * correct but better than the other SMB1 async + * code that double-charges at the moment. + */ START_PROFILE(SMBexit); - file_close_pid(req->sconn, req->smbpid, req->vuid); + status = reply_exit_recv(req); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb1req); + END_PROFILE(SMBexit); + exit_server(__location__ ": reply_exit_recv failed"); + return; + } - reply_outbuf(req, 0, 0); + /* + * Ensure the session is still valid. + */ + status = smb1srv_session_lookup(xconn, + smb1req->vuid, + now, + &session); + if (!NT_STATUS_IS_OK(status)) { + reply_force_doserror(smb1req, ERRSRV, ERRinvnid); + smb_request_done(smb1req); + END_PROFILE(SMBexit); + } + + /* + * Ensure the vuid is still valid - no one + * called reply_ulogoffX() in the meantime. + * reply_exit() doesn't have AS_USER set, so + * use set_current_user_info() directly. + * This is the same logic as in switch_message(). + */ + if (session->global->auth_session_info != NULL) { + set_current_user_info( + session->global->auth_session_info->unix_info->sanitized_username, + session->global->auth_session_info->unix_info->unix_name, + session->global->auth_session_info->info->domain_name); + } + + /* No more aio - do the actual closes. */ + for (fsp = sconn->files; fsp; fsp = next) { + bool ok; + next = fsp->next; + + if (fsp->file_pid != smb1req->smbpid) { + continue; + } + if (fsp->vuid != smb1req->vuid) { + continue; + } + if (!fsp->closing) { + continue; + } - DEBUG(3,("exit\n")); + /* + * reply_exit() has the DO_CHDIR flag set. + */ + ok = chdir_current_service(fsp->conn); + if (!ok) { + reply_force_doserror(smb1req, ERRSRV, ERRinvnid); + smb_request_done(smb1req); + END_PROFILE(SMBexit); + } + close_file(NULL, fsp, SHUTDOWN_CLOSE); + } + reply_outbuf(smb1req, 0, 0); + /* + * The following call is needed to push the + * reply data back out the socket after async + * return. Plus it frees smb1req. + */ + smb_request_done(smb1req); + DBG_INFO("reply_exit complete\n"); END_PROFILE(SMBexit); return; } @@ -5660,6 +6042,13 @@ void reply_close(struct smb_request *req) fsp->num_aio_requests)); /* + * Flag the file as close in progress. + * This will prevent any more IO being + * done on it. + */ + fsp->closing = true; + + /* * We depend on the aio_extra destructor to take care of this * close request once fsp->num_aio_request drops to 0. */ @@ -6018,46 +6407,211 @@ void reply_unlock(struct smb_request *req) conn POINTER CAN BE NULL HERE ! ****************************************************************************/ -void reply_tdis(struct smb_request *req) +static struct tevent_req *reply_tdis_send(struct smb_request *smb1req); +static void reply_tdis_done(struct tevent_req *req); + +void reply_tdis(struct smb_request *smb1req) { - NTSTATUS status; - connection_struct *conn = req->conn; - struct smbXsrv_tcon *tcon; + connection_struct *conn = smb1req->conn; + struct tevent_req *req; - START_PROFILE(SMBtdis); + /* + * Don't setup the profile charge here, take + * it in reply_tdis_done(). Not strictly correct + * but better than the other SMB1 async + * code that double-charges at the moment. + */ - if (!conn) { - DEBUG(4,("Invalid connection in tdis\n")); - reply_force_doserror(req, ERRSRV, ERRinvnid); + if (conn == NULL) { + /* Not going async, profile here. */ + START_PROFILE(SMBtdis); + DBG_INFO("Invalid connection in tdis\n"); + reply_force_doserror(smb1req, ERRSRV, ERRinvnid); + END_PROFILE(SMBtdis); + return; + } + + req = reply_tdis_send(smb1req); + if (req == NULL) { + /* Not going async, profile here. */ + START_PROFILE(SMBtdis); + reply_force_doserror(smb1req, ERRDOS, ERRnomem); END_PROFILE(SMBtdis); return; } + /* We're async. This will complete later. */ + tevent_req_set_callback(req, reply_tdis_done, smb1req); + return; +} + +struct reply_tdis_state { + struct tevent_queue *wait_queue; +}; + +static void reply_tdis_wait_done(struct tevent_req *subreq); + +/**************************************************************************** + Async SMB1 tdis. + Note, on failure here we deallocate and return NULL to allow the caller to + SMB1 return an error of ERRnomem immediately. +****************************************************************************/ + +static struct tevent_req *reply_tdis_send(struct smb_request *smb1req) +{ + struct tevent_req *req; + struct reply_tdis_state *state; + struct tevent_req *subreq; + connection_struct *conn = smb1req->conn; + files_struct *fsp; - tcon = conn->tcon; - req->conn = NULL; + req = tevent_req_create(smb1req, &state, + struct reply_tdis_state); + if (req == NULL) { + return NULL; + } + state->wait_queue = tevent_queue_create(state, "reply_tdis_wait_queue"); + if (tevent_req_nomem(state->wait_queue, req)) { + TALLOC_FREE(req); + return NULL; + } /* - * TODO: cancel all outstanding requests on the tcon + * Make sure that no new request will be able to use this tcon. + * This ensures that once all outstanding fsp->aio_requests + * on this tcon are done, we are safe to close it. */ - status = smbXsrv_tcon_disconnect(tcon, req->vuid); - if (!NT_STATUS_IS_OK(status)) { - DEBUG(0, ("reply_tdis: " - "smbXsrv_tcon_disconnect() failed: %s\n", - nt_errstr(status))); + conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED; + + for (fsp = conn->sconn->files; fsp; fsp = fsp->next) { + if (fsp->conn != conn) { + continue; + } /* - * If we hit this case, there is something completely - * wrong, so we better disconnect the transport connection. + * Flag the file as close in progress. + * This will prevent any more IO being + * done on it. Not strictly needed, but + * doesn't hurt to flag it as closing. */ + fsp->closing = true; + + if (fsp->num_aio_requests > 0) { + /* + * Now wait until all aio requests on this fsp are + * finished. + * + * We don't set a callback, as we just want to block the + * wait queue and the talloc_free() of fsp->aio_request + * will remove the item from the wait queue. + */ + subreq = tevent_queue_wait_send(fsp->aio_requests, + conn->sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + } + } + + /* + * Now we add our own waiter to the end of the queue, + * this way we get notified when all pending requests are finished + * and reply to the outstanding SMB1 request. + */ + subreq = tevent_queue_wait_send(state, + conn->sconn->ev_ctx, + state->wait_queue); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(req); + return NULL; + } + + /* + * We're really going async - move the SMB1 request from + * a talloc stackframe above us to the sconn talloc-context. + * We need this to stick around until the wait_done + * callback is invoked. + */ + smb1req = talloc_move(smb1req->sconn, &smb1req); + + tevent_req_set_callback(subreq, reply_tdis_wait_done, req); + + return req; +} + +static void reply_tdis_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + + tevent_queue_wait_recv(subreq); + TALLOC_FREE(subreq); + tevent_req_done(req); +} + +static NTSTATUS reply_tdis_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +static void reply_tdis_done(struct tevent_req *req) +{ + struct smb_request *smb1req = tevent_req_callback_data( + req, struct smb_request); + NTSTATUS status; + struct smbXsrv_tcon *tcon = smb1req->conn->tcon; + bool ok; + + /* + * Take the profile charge here. Not strictly + * correct but better than the other SMB1 async + * code that double-charges at the moment. + */ + START_PROFILE(SMBtdis); + + status = reply_tdis_recv(req); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb1req); + END_PROFILE(SMBtdis); + exit_server(__location__ ": reply_tdis_recv failed"); + return; + } + + /* + * As we've been awoken, we may have changed + * directory in the meantime. + * reply_tdis() has the DO_CHDIR flag set. + */ + ok = chdir_current_service(smb1req->conn); + if (!ok) { + reply_force_doserror(smb1req, ERRSRV, ERRinvnid); + smb_request_done(smb1req); + END_PROFILE(SMBtdis); + } + + status = smbXsrv_tcon_disconnect(tcon, + smb1req->vuid); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb1req); END_PROFILE(SMBtdis); exit_server(__location__ ": smbXsrv_tcon_disconnect failed"); return; } + /* smbXsrv_tcon_disconnect frees smb1req->conn. */ + smb1req->conn = NULL; + TALLOC_FREE(tcon); - reply_outbuf(req, 0, 0); + reply_outbuf(smb1req, 0, 0); + /* + * The following call is needed to push the + * reply data back out the socket after async + * return. Plus it frees smb1req. + */ + smb_request_done(smb1req); END_PROFILE(SMBtdis); - return; } /**************************************************************************** @@ -8857,6 +9411,10 @@ void reply_setattrE(struct smb_request *req) goto out; } + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + DEBUG( 3, ( "reply_setattrE %s actime=%u modtime=%u " " createtime=%u\n", fsp_fnum_dbg(fsp), diff --git a/source3/smbd/service.c b/source3/smbd/service.c index 1abc23ad422..03125a30dad 100644 --- a/source3/smbd/service.c +++ b/source3/smbd/service.c @@ -146,55 +146,46 @@ bool chdir_current_service(connection_struct *conn) const struct smb_filename origpath_fname = { .base_name = conn->origpath, }; + int saved_errno = 0; + char *utok_str = NULL; int ret; conn->lastused_count++; ret = vfs_ChDir(conn, &connectpath_fname); - if (ret != 0) { - int saved_errno = errno; - - if (saved_errno == EACCES) { - char *str = utok_string( - talloc_tos(), - conn->session_info->unix_token); - DBG_WARNING("vfs_ChDir(%s) got " - "permission denied, current " - "token: %s\n", - conn->connectpath, str); - TALLOC_FREE(str); - } else { - DBG_ERR("vfs_ChDir(%s) failed: " - "%s!\n", - conn->connectpath, - strerror(saved_errno)); - } + if (ret == 0) { + return true; + } + saved_errno = errno; + + utok_str = utok_string(talloc_tos(), + conn->session_info->unix_token); + if (utok_str == NULL) { + errno = saved_errno; return false; } + DBG_ERR("vfs_ChDir(%s) failed: %s. Current token: %s\n", + conn->connectpath, + strerror(saved_errno), + utok_str); + ret = vfs_ChDir(conn, &origpath_fname); - if (ret != 0) { - int saved_errno = errno; - - if (saved_errno == EACCES) { - char *str = utok_string( - talloc_tos(), - conn->session_info->unix_token); - DBG_WARNING("vfs_ChDir(%s) got " - "permission denied, current " - "token: %s\n", - conn->origpath, str); - TALLOC_FREE(str); - } else { - DBG_ERR("vfs_ChDir(%s) failed: " - "%s!\n", - conn->origpath, - strerror(saved_errno)); - } - return false; + if (ret == 0) { + TALLOC_FREE(utok_str); + return true; } + saved_errno = errno; - return true; + DBG_ERR("vfs_ChDir(%s) failed: %s. Current token: %s\n", + conn->origpath, + strerror(saved_errno), + utok_str); + + if (saved_errno != 0) { + errno = saved_errno; + } + return false; } /**************************************************************************** diff --git a/source3/smbd/smb2_flush.c b/source3/smbd/smb2_flush.c index 86d5bbc58f0..08539e95807 100644 --- a/source3/smbd/smb2_flush.c +++ b/source3/smbd/smb2_flush.c @@ -112,6 +112,7 @@ static void smbd_smb2_request_flush_done(struct tevent_req *subreq) struct smbd_smb2_flush_state { struct smbd_smb2_request *smb2req; + struct files_struct *fsp; }; static void smbd_smb2_flush_done(struct tevent_req *subreq); @@ -132,6 +133,7 @@ static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx, return NULL; } state->smb2req = smb2req; + state->fsp = fsp; DEBUG(10,("smbd_smb2_flush: %s - %s\n", fsp_str_dbg(fsp), fsp_fnum_dbg(fsp))); @@ -207,6 +209,8 @@ static void smbd_smb2_flush_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); + struct smbd_smb2_flush_state *state = tevent_req_data( + req, struct smbd_smb2_flush_state); int ret; struct vfs_aio_state vfs_aio_state; @@ -216,6 +220,9 @@ static void smbd_smb2_flush_done(struct tevent_req *subreq) tevent_req_nterror(req, map_nt_error_from_unix(vfs_aio_state.error)); return; } + if (state->fsp->modified) { + trigger_write_time_update_immediate(state->fsp); + } tevent_req_done(req); } diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c index 2cf669f4b4d..339dd36b6af 100644 --- a/source3/smbd/trans2.c +++ b/source3/smbd/trans2.c @@ -6675,6 +6675,13 @@ static NTSTATUS smb_set_file_size(connection_struct *conn, get_file_size_stat(psbuf)); if (size == get_file_size_stat(psbuf)) { + if (fsp == NULL) { + return NT_STATUS_OK; + } + if (!fsp->modified) { + return NT_STATUS_OK; + } + trigger_write_time_update_immediate(fsp); return NT_STATUS_OK; } @@ -7817,8 +7824,15 @@ static NTSTATUS smb_set_file_basic_info(connection_struct *conn, DEBUG(10, ("smb_set_file_basic_info: file %s\n", smb_fname_str_dbg(smb_fname))); - return smb_set_file_time(conn, fsp, smb_fname, &ft, - true); + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp != NULL && fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; } /**************************************************************************** @@ -7855,11 +7869,15 @@ static NTSTATUS smb_set_info_standard(connection_struct *conn, return status; } - return smb_set_file_time(conn, - fsp, - smb_fname, - &ft, - true); + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp != NULL && fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; } /**************************************************************************** diff --git a/source4/kdc/mit-kdb/kdb_samba.c b/source4/kdc/mit-kdb/kdb_samba.c index c5157d6ed1b..02bbdca9f54 100644 --- a/source4/kdc/mit-kdb/kdb_samba.c +++ b/source4/kdc/mit-kdb/kdb_samba.c @@ -139,7 +139,7 @@ static void kdb_samba_db_free_principal_e_data(krb5_context context, kdb_vftabl kdb_function_table = { .maj_ver = KRB5_KDB_DAL_MAJOR_VERSION, - .min_ver = 1, + .min_ver = KRB5_KDB_DAL_MAJOR_VERSION == 6 ? 1 : 0, .init_library = kdb_samba_init_library, .fini_library = kdb_samba_fini_library, diff --git a/source4/kdc/mit-kdb/kdb_samba.h b/source4/kdc/mit-kdb/kdb_samba.h index 22ef9085b6a..ad4f6e27573 100644 --- a/source4/kdc/mit-kdb/kdb_samba.h +++ b/source4/kdc/mit-kdb/kdb_samba.h @@ -114,6 +114,7 @@ krb5_error_code kdb_samba_dbekd_encrypt_key_data(krb5_context context, /* from kdb_samba_policies.c */ +#if KRB5_KDB_API_VERSION < 10 krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, unsigned int flags, krb5_const_principal client_princ, @@ -127,6 +128,26 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, krb5_timestamp authtime, krb5_authdata **tgt_auth_data, krb5_authdata ***signed_auth_data); +#else +krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_const_principal server_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_db_entry *local_krbtgt, + krb5_keyblock *client_key, + krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, + krb5_keyblock *local_krbtgt_key, + krb5_keyblock *session_key, + krb5_timestamp authtime, + krb5_authdata **tgt_auth_data, + void *authdata_info, + krb5_data ***auth_indicators, + krb5_authdata ***signed_auth_data); +#endif krb5_error_code kdb_samba_db_check_policy_as(krb5_context context, krb5_kdc_req *kdcreq, diff --git a/source4/kdc/mit-kdb/kdb_samba_policies.c b/source4/kdc/mit-kdb/kdb_samba_policies.c index fc80329f221..9197551ed61 100644 --- a/source4/kdc/mit-kdb/kdb_samba_policies.c +++ b/source4/kdc/mit-kdb/kdb_samba_policies.c @@ -287,6 +287,7 @@ done: return code; } +#if KRB5_KDB_API_VERSION < 10 krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, unsigned int flags, krb5_const_principal client_princ, @@ -301,18 +302,41 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, krb5_authdata **tgt_auth_data, krb5_authdata ***signed_auth_data) { - krb5_const_principal ks_client_princ; +#else +krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_const_principal server_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_db_entry *local_krbtgt, + krb5_keyblock *client_key, + krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, + krb5_keyblock *local_krbtgt_key, + krb5_keyblock *session_key, + krb5_timestamp authtime, + krb5_authdata **tgt_auth_data, + void *authdata_info, + krb5_data ***auth_indicators, + krb5_authdata ***signed_auth_data) +{ +#endif krb5_authdata **authdata = NULL; krb5_boolean is_as_req; krb5_error_code code; krb5_pac pac = NULL; krb5_data pac_data; - /* Prefer canonicalised name from client entry */ - if (client != NULL) { - ks_client_princ = client->princ; - } else { - ks_client_princ = client_princ; +#if KRB5_KDB_API_VERSION >= 10 + krbtgt = krbtgt == NULL ? local_krbtgt : krbtgt; + krbtgt_key = krbtgt_key == NULL ? local_krbtgt_key : krbtgt_key; +#endif + + /* FIXME: We don't support S4U yet */ + if (flags & KRB5_KDB_FLAGS_S4U) { + return KRB5_KDB_DBTYPE_NOSUP; } is_as_req = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0); @@ -327,7 +351,7 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, if (!is_as_req) { code = ks_verify_pac(context, flags, - ks_client_princ, + client_princ, client, server, krbtgt, @@ -354,7 +378,7 @@ krb5_error_code kdb_samba_db_sign_auth_data(krb5_context context, goto done; } - code = krb5_pac_sign(context, pac, authtime, ks_client_princ, + code = krb5_pac_sign(context, pac, authtime, client_princ, server_key, krbtgt_key, &pac_data); if (code != 0) { DBG_ERR("krb5_pac_sign failed: %d\n", code); diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c index c258c15ff91..41b9ffbc356 100644 --- a/source4/torture/smb2/smb2.c +++ b/source4/torture/smb2/smb2.c @@ -198,6 +198,7 @@ NTSTATUS torture_smb2_init(TALLOC_CTX *ctx) torture_suite_add_suite(suite, torture_smb2_multichannel_init(suite)); torture_suite_add_suite(suite, torture_smb2_samba3misc_init(suite)); torture_suite_add_suite(suite, torture_smb2_timestamps_init(suite)); + torture_suite_add_suite(suite, torture_smb2_timestamp_resolution_init(suite)); torture_suite_add_1smb2_test(suite, "openattr", torture_smb2_openattrtest); torture_suite_add_1smb2_test(suite, "winattr", torture_smb2_winattrtest); torture_suite_add_suite(suite, torture_smb2_readwrite_init(suite)); diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index 9655e5bc164..89b64886e4d 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -27,6 +27,7 @@ #include "torture/smb2/proto.h" #define BASEDIR "smb2-timestamps" +#define FNAME "testfile.dat" static bool test_time_t(struct torture_context *tctx, struct smb2_tree *tree, @@ -235,11 +236,11 @@ done: return ret; } -static bool test_time_t_100000000000(struct torture_context *tctx, +static bool test_time_t_15032385535(struct torture_context *tctx, struct smb2_tree *tree) { - return test_time_t(tctx, tree, "test_time_t_100000000000.txt", - 100000000000 /* >> INT32_MAX */); + return test_time_t(tctx, tree, "test_time_t_15032385535.txt", + 15032385535 /* >> INT32_MAX, limit on ext */); } static bool test_time_t_10000000000(struct torture_context *tctx, @@ -287,6 +288,618 @@ static bool test_time_t_1968(struct torture_context *tctx, -63158400 /* 1968 */); } +static bool test_delayed_write_vs_seteof(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTTIME create_time; + NTTIME set_time; + union smb_fileinfo finfo; + union smb_setfileinfo setinfo; + struct smb2_close c; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != set_time (wrong!)\n"); + + torture_comment(tctx, "Setinfo EOF on file-handle 1," + " should flush pending writetime update\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION, + }; + setinfo.end_of_file_info.in.file.handle = h1; + setinfo.end_of_file_info.in.size = 1; /* same size! */ + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo EOF hasn't updated writetime\n"); + } + + torture_comment(tctx, "Open file-handle 2\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FILE_WRITE_ATTRIBUTE, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h2 = cr.out.file.handle; + + torture_comment(tctx, "Set write time on file-handle 2\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_FILEINFO_BASIC_INFORMATION, + }; + setinfo.generic.in.file.handle = h2; + unix_to_nt_time(&set_time, time(NULL) + 86400); + setinfo.basic_info.in.write_time = set_time; + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + status = smb2_util_close(tree, h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h2); + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + set_time, + "Writetime != set_time (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_write_vs_flush(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_flush f; + struct smb2_close c; + NTTIME create_time; + NTTIME flush_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Flush file, " + "should flush pending writetime update\n"); + + f = (struct smb2_flush) { + .in.file.handle = h1, + }; + + status = smb2_flush(tree, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "flush failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + flush_time = finfo.all_info.out.write_time; + if (!(flush_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "flush hasn't updated writetime\n"); + } + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + flush_time, + "writetime != flushtime (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_write_vs_setbasic_do(struct torture_context *tctx, + struct smb2_tree *tree, + union smb_setfileinfo *setinfo, + bool expect_update) +{ + char *path = NULL; + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + NTTIME create_time; + union smb_fileinfo finfo; + NTSTATUS status; + bool ret = true; + + torture_comment(tctx, "Create testfile\n"); + + path = talloc_asprintf(tree, BASEDIR "\\" FNAME ".%" PRIu32, + generate_random()); + torture_assert_not_null_goto(tctx, path, ret, done, "OOM\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = path, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + + torture_comment(tctx, "Write to file\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Get timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Set timestamps\n"); + + setinfo->end_of_file_info.in.file.handle = h1; + status = smb2_setinfo_file(tree, setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + if (expect_update) { + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } else { + if (finfo.all_info.out.write_time != create_time) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + status = smb2_util_unlink(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + +done: + TALLOC_FREE(path); + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + return ret; +} + +static bool test_delayed_write_vs_setbasic(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h1 = {{0}}; + union smb_setfileinfo setinfo; + time_t t = time(NULL) - 86400; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + /* + * Yes, this is correct, tested against Windows 2016: even if all + * timestamp fields are 0, a pending write time is flushed. + */ + torture_comment(tctx, "Test: setting all-0 timestamps flushes?\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_BASIC_INFORMATION, + }; + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting create_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.create_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting access_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.access_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting change_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.change_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_1write(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_close c; + NTTIME create_time; + NTTIME write_time; + NTTIME close_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time = finfo.all_info.out.write_time; + + if (!(write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + + torture_comment(tctx, "Close file-handle 1\n"); + sleep(1); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + close_time = c.out.write_time; + + torture_assert_nttime_equal(tctx, close_time, write_time, + "Writetime != close_time (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +static bool test_delayed_2write(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_close c; + NTTIME create_time; + NTTIME write_time; + NTTIME write_time2; + struct timespec now; + NTTIME send_close_time; + NTTIME close_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time = finfo.all_info.out.write_time; + + if (!(write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + + torture_comment(tctx, "Write a second time\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + sleep(3); + + torture_comment(tctx, "Check writetime has NOT been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + write_time2 = finfo.all_info.out.write_time; + + torture_assert_nttime_equal(tctx, write_time2, write_time, + "second write updated write-time (wrong!)\n"); + + torture_comment(tctx, "Close file-handle 1\n"); + sleep(2); + + now = timespec_current(); + send_close_time = full_timespec_to_nt_time(&now); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + close_time = c.out.write_time; + + if (!(close_time > send_close_time)) { + ret = false; + torture_fail_goto(tctx, done, + "Write-time not updated (wrong!)\n"); + } + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + /* basic testing of SMB2 timestamps */ @@ -294,7 +907,7 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "timestamps"); - torture_suite_add_1smb2_test(suite, "time_t_100000000000", test_time_t_100000000000); + torture_suite_add_1smb2_test(suite, "time_t_15032385535", test_time_t_15032385535); torture_suite_add_1smb2_test(suite, "time_t_10000000000", test_time_t_10000000000); torture_suite_add_1smb2_test(suite, "time_t_4294967295", test_time_t_4294967295); torture_suite_add_1smb2_test(suite, "time_t_1", test_time_t_1); @@ -303,6 +916,160 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "time_t_-2", test_time_t_minus_2); torture_suite_add_1smb2_test(suite, "time_t_1968", test_time_t_1968); + /* + * Testing of delayed write-time udpates + */ + torture_suite_add_1smb2_test(suite, "delayed-write-vs-seteof", test_delayed_write_vs_seteof); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-flush", test_delayed_write_vs_flush); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-setbasic", test_delayed_write_vs_setbasic); + torture_suite_add_1smb2_test(suite, "delayed-1write", test_delayed_1write); + torture_suite_add_1smb2_test(suite, "delayed-2write", test_delayed_2write); + + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); + + return suite; +} + +/* + * This test shows that Windows has a timestamp resolution of ~15ms. When so + * when a smaller amount of time than that has passed it's not necessarily + * detectable on a Windows 2019 and newer who implement immediate timestamp + * updates. + * + * Note that this test relies on a low latency SMB connection. Even with a low + * latency connection of eg 1m there's a chance of 1/15 that the first part of + * the test expecting no timestamp change fails as the writetime is updated. + * + * Due to this timing dependency this test is skipped in Samba CI, but it is + * preserved here for future SMB2 timestamps behaviour archealogists. + * + * See also: https://lists.samba.org/archive/cifs-protocol/2019-December/003358.html + */ +static bool test_timestamp_resolution1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_fileinfo finfo1; + const char *fname = BASEDIR "\\" FNAME; + struct smb2_create cr; + struct smb2_handle h = {{0}}; + struct smb2_close cl; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Write without delay, expect no " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_CREATE, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_equal_goto(tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time changed (wrong!)\n"); + + torture_comment(tctx, "Write with 20 ms delay, expect " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_OPEN, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + smb_msleep(20); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_not_equal_goto( + tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time did not change (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 timestamps +*/ +struct torture_suite *torture_smb2_timestamp_resolution_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "timestamp_resolution"); + + torture_suite_add_1smb2_test(suite, "resolution1", test_timestamp_resolution1); + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); return suite; diff --git a/testprogs/blackbox/test_kinit_mit.sh b/testprogs/blackbox/test_kinit_mit.sh index d28caecd603..61029a5e04c 100755 --- a/testprogs/blackbox/test_kinit_mit.sh +++ b/testprogs/blackbox/test_kinit_mit.sh @@ -134,10 +134,6 @@ testit "enable user with kerberos cache" $VALGRIND $PYTHON $samba_enableaccount ### Test kinit with canonicalization ########################################################### -# This is currently not working due to an upstream bug in MIT Kerberos. The -# test will ensure that we get notified when we can turn on canonicalization -# in ads_krb5_chg_password(). -# https://bugzilla.samba.org/show_bug.cgi?id=14155 upperusername=$(echo $USERNAME | tr '[a-z]' '[A-Z]') testit "kinit with canonicalize" $samba_texpect $PREFIX/tmpkinitscript $samba_kinit -C $upperusername@$REALM -S kadmin/changepw@$REALM || failed=`expr $failed + 1` diff --git a/testprogs/blackbox/test_net_ads.sh b/testprogs/blackbox/test_net_ads.sh index 8bcff006b8e..95c0cf76f90 100755 --- a/testprogs/blackbox/test_net_ads.sh +++ b/testprogs/blackbox/test_net_ads.sh @@ -237,6 +237,23 @@ testit "leave+createcomputer" $VALGRIND $net_tool ads leave -U$DC_USERNAME%$DC_P testit "Remove OU=Servers" $VALGRIND $ldbdel -U$DC_USERNAME%$DC_PASSWORD -H ldap://$SERVER "OU=Servers,$base_dn" +# +# Test createupn option of 'net ads join' +# +testit "join+createupn" $VALGRIND $net_tool ads join -U$DC_USERNAME%$DC_PASSWORD createupn="host/test-$HOSTNAME@$REALM" || failed=`expr $failed + 1` + +testit_grep "checkupn" "userPrincipalName: host/test-$HOSTNAME@$REALM" $ldbsearch -U$DC_USERNAME%$DC_PASSWORD -H ldap://$SERVER.$REALM -s base -b "CN=$HOSTNAME,CN=Computers,$base_dn" || failed=`expr $failed + 1` + +dedicated_keytab_file="$PREFIX_ABS/test_net_create_dedicated_krb5.keytab" + +testit "create_keytab" $VALGRIND $net_tool ads keytab create --option="kerberosmethod=dedicatedkeytab" --option="dedicatedkeytabfile=$dedicated_keytab_file" || failed=`expr $failed + 1` + +testit_grep "checkupn+keytab" "host/test-$HOSTNAME@$REALM" $net_tool ads keytab list --option="kerberosmethod=dedicatedkeytab" --option="dedicatedkeytabfile=$dedicated_keytab_file" || failed=`expr $failed + 1` + +rm -f $dedicated_keytab_file + +testit "leave+createupn" $VALGRIND $net_tool ads leave -U$DC_USERNAME%$DC_PASSWORD || failed=`expr $failed + 1` + rm -rf $BASEDIR/$WORKDIR exit $failed diff --git a/wscript_configure_system_mitkrb5 b/wscript_configure_system_mitkrb5 index b05ac3f3e50..23587797119 100644 --- a/wscript_configure_system_mitkrb5 +++ b/wscript_configure_system_mitkrb5 @@ -77,6 +77,9 @@ if conf.env.KRB5_CONFIG: else: Logs.info('MIT Kerberos %s detected, MIT krb5 build can proceed' % (krb5_version)) + if parse_version(krb5_version) < parse_version('1.18'): + conf.DEFINE('HAVE_MIT_KRB5_PRE_1_18', 1) + conf.CHECK_CFG(args="--cflags --libs", package="com_err", uselib_store="com_err") conf.CHECK_FUNCS_IN('_et_list', 'com_err') conf.CHECK_HEADERS('com_err.h', lib='com_err') |